From 7f739f7f5e53f67ce19c7442668bc815809ef089 Mon Sep 17 00:00:00 2001 From: Saddam Azad Date: Sun, 18 Aug 2024 02:10:06 +0600 Subject: [PATCH] consistency --- README.md | 126 ++++++---------------- cdk-spa-init.mjs | 20 ++++ examples/astro/stack/index.ts | 8 +- examples/gatsby/stack/index.ts | 8 +- examples/vitepress/stack.ts | 8 +- examples/vuejs/stack.ts | 8 +- index.ts | 4 +- package.json | 4 + stack/{StaticSiteProps.ts => SPAProps.ts} | 3 +- stack/{StaticSiteStack.ts => SPAStack.ts} | 6 +- templates/spa.ts | 6 +- 11 files changed, 82 insertions(+), 119 deletions(-) create mode 100644 cdk-spa-init.mjs rename stack/{StaticSiteProps.ts => SPAProps.ts} (98%) rename stack/{StaticSiteStack.ts => SPAStack.ts} (95%) diff --git a/README.md b/README.md index c1fefb1..6e5adb7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ # CDK-SPA -Deploy SSG (static site generators) and client-only SPA (single page applications) on AWS using CDK. +Deploy any client-only SPA (single page application) on AWS using CDK. - Fast responses from [CloudFront](https://aws.amazon.com/cloudfront/) - Automatic upload of the build files for CSR and static assets to [S3](https://aws.amazon.com/s3/) with optimized caching rules - Automatic build and deploy with [CodeBuild](https://aws.amazon.com/codebuild/) and [CodePipeline](https://aws.amazon.com/codepipeline/) from [Github](https://github.com/) repository. - Publicly available by a custom domain (or subdomain) via [Route53](https://aws.amazon.com/route53/) -- Automatic cleanup of outdated static assets and build files. + ## Prerequisites You need an [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) to create and deploy the required resources for the site on AWS. -This package uses the `npm` package manager and is an ES Module. +This package uses the `npm` package manager and is an ES6+ Module. ## Installation Install the package and its required dependencies: ```bash -npm i @thunderso/stacks --save-dev +npm i @thunderso/cdk-spa --save-dev ``` If you do not have already, your `package.json` must also contain `aws-cdk-lib` and `tsx`: @@ -32,22 +32,14 @@ npm i aws-cdk-lib tsx --save-dev 2. Run the following command to automatically create the required CDK stack entrypoint at `stack/index.ts`. This file defines the config how the app will be deployed via CDK. You should adapt the file to the project's needs, especially the props `env.account` (setup step 1). - ```bash npx cdk-spa-init ``` -The executable file can be found at - -```bash -node_modules/.bin/cdk-spa-init -``` ### Enable Automatic Deployments 1. [Create a Github Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for your Github account. This token must be kept secure. - - 2. [Create a Secrets Manager secret](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html) with the Personal Access Token you created earlier. Note the `ARN` of the secret. E.g. `arn:aws:secretsmanager:::secret:`. 3. Input the noted `ARN` to the `githubAccessTokenArn` field in your stack. @@ -55,27 +47,20 @@ node_modules/.bin/cdk-spa-init ### Manage Domain with Route53 (Optional) 1. [Create a hosted zone in Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/AboutHZWorkingWith.html) for the desired domain, if you don't have one yet. -This is required to create DNS records for the domain to make the app publicly available on that domain. -On the hosted zone details you should see the `Hosted zone ID` of the hosted zone. + + This is required to create DNS records for the domain to make the app publicly available on that domain. On the hosted zone details you should see the `Hosted zone ID` of the hosted zone. 2. [Request a public global certificate in the AWS Certificate Manager (ACM)](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html) for the desired domain in `us-east-1` *(global)* and validate it, if you don't have one yet. -This is required to provide the app via HTTPS on the public internet. Take note of the displayed `ARN` for the certificate. + + This is required to provide the app via HTTPS on the public internet. Take note of the displayed `ARN` for the certificate. > [!IMPORTANT] > The certificate must be issued in `us-east-1` *(global)* regardless of the region used for the app itself as it will be attached to the CloudFront distribution which works globally. -### Custom `buildspec.yml` and CloudFront Functions (Optional) - -1. [Build specification reference for CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html) - -2. [CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CloudFront-functions.html) are used for URL rewriting. - -> Check out the resources from [aws-samples/amazon-cloudfront-functions](https://github.com/aws-samples/amazon-cloudfront-functions) for examples. - ## Configuration -The `StaticSiteStack` construct can be configured via the following props: +The `SPAStack` construct can be configured via the following props: @@ -173,7 +158,7 @@ The `StaticSiteStack` construct can be configured via the following props: - @@ -234,31 +219,10 @@ The `StaticSiteStack` construct can be configured via the following props: - - - - - - - - - - - -
string Optional. If you have a custom CodeBuild Buildspec file for your app, provide relative path to the file. E.g. buildspec.yml or ./buildspec.yml + Optional. If you have a custom CodeBuild Buildspec file for your app, provide relative path to the file. E.g. stack/buildspec.yml
string Optional. If you have a custom CloudFront Functions file for your app, provide relative path to the file. E.g. ./index.js + Optional. If you have a custom CloudFront Functions file for your app, provide relative path to the file. E.g. stack/urlrewrite.js
Addon functions:
- enableAnalytics - - boolean - Optional. Whether to provision Athena for CloudFront access logs analysis. Defaults to false -
- enableAssetCleanup - - boolean - Optional. Whether to provision Lambda and EventBridge rule to cleanup stale static assets. Defaults to false -
@@ -279,18 +243,7 @@ See https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html for details. ### 2. Build and Deploy -By running the following script, the app will be built automatically via `npm build` and the CDK stack will be deployed to AWS. - -```bash -npx cdk-spa-deploy -``` -The executable file: - -```bash -node_modules/.bin/cdk-spa-deploy -``` - -Alternatively, you can run the following commands separately to customize the deployment process: +By running the following script, the CDK stack will be deployed to AWS. ```bash npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts" @@ -300,29 +253,22 @@ npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts" If you want to destroy the stack and all its resources (including storage, e.g., access logs), run the following script: - -```bash -npx cdk-spa-destroy -``` -The executable file: ```bash -node_modules/.bin/cdk-spa-destroy +npx cdk destroy --require-approval never --all --app="npx tsx stack/index.ts" ``` ## Reference: Created AWS Resources In the following, you can find an overview of the AWS resources that will be created by this package for reference. -### StaticSiteStack +### SPAStack This stack is responsible for deploying static sites and dynamic SPA apps to AWS. The following AWS resources will be created by this stack: - [CloudFront](https://aws.amazon.com/CloudFront/): A distribution to route incoming requests to the S3 bucket to serve the static assets for the app. -- [S3](https://aws.amazon.com/s3/): - - A bucket to store the client files and static assets of the build with optimized cache settings. - - A bucket to store the CloudFront access logs for analysis via Athena. Only created if `enableAnalytics` is set to `true`. +- [S3](https://aws.amazon.com/s3/): A bucket to store the client files and static assets of the build with optimized cache settings. - [CodeBuild](https://aws.amazon.com/codebuild/): A build project to automatically build and package the static assets from the source code. @@ -330,31 +276,21 @@ The following AWS resources will be created by this stack: - [Route53](https://aws.amazon.com/route53/): Two DNS records (`A` for IPv4 and `AAAA` for IPv6) in the configured hosted zone to make the app available on the internet via the configured custom domain. -- [Lambda](https://aws.amazon.com/lambda/): - - A Lambda function that deletes the outdated static assets of the app from S3. - -- [EventBridge](https://aws.amazon.com/eventbridge/): - - A scheduled rule to trigger the cleanup Lambda function for deleting the outdated static assets of the app from S3 every tuesday at 03:30 AM GMT. - -- [Athena](https://aws.amazon.com/athena/): A database and table to analyze the access logs of the app's CloudFront distribution. Only created if `enableAnalytics` is set to `true`. - -- [AppConfig](https://aws.amazon.com/appconfig/): An application and environment configuration for deployment settings management. - ## Manually setting up CDK -You can use `StaticSiteStack` as a CDK construct within your CDK code to seamlessly integrate hosting. Here's an example of how to use it: +You can use `SPAStack` as a CDK construct within your CDK code to seamlessly integrate hosting. Here's an example of how to use it: -Create a `stacks` directory in your project root and an empty file `index.ts` and fill in the props accordingly. +Create a `stack` directory in your project root and an empty file `index.ts` and fill in the props accordingly. > Use different filenames such as `production.ts` and `testing.ts` for environments. ```ts #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: 'your-account-id', region: 'us-east-1' @@ -378,12 +314,15 @@ const appStackProps: StaticSiteProps = { // Either provide a buildspec.yml file OR leave empty and fill out buildProps buildSpecFilePath: './buildspec.yml', buildProps: { - runtime: 20, // nodejs version + runtime: 20, // nodejs versions 16, 18 and 20 supported installcmd: "npm ci", buildcmd: "npm run build", outputDir: "dist" }, + // Custom CloudFront Functions for URL rewrite + edgeFunctionFilePath: 'stack/urlrewrite.js', + // Optional: Domain settings // - create a hosted zone for your domain // - issue a global tls certificate in us-east-1 @@ -391,28 +330,29 @@ const appStackProps: StaticSiteProps = { hostedZoneId: 'Z1D633PJRANDOM', globalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abcd1234-abcd-1234-abcd-1234abcd1234', - // Custom CloudFront Functions for URL rewrite - edgeFunctionFilePath: './edge.js', - - // Optional: functions - enableAnalytics: true, - enableAssetCleanup: true, - // all resources created in the stack will be tagged // tags: { // key: 'value' // }, }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); ``` -Run the following command to deploy stacks separately. +Run the following command to deploy stack. ```bash npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts" ``` +## Advanced + +Cloudfront edge functions can be used for URL rewrite, among other use-cases. + +> [!IMPORTANT] +> Check out the resources from [aws-samples/amazon-cloudfront-functions](https://github.com/aws-samples/amazon-cloudfront-functions) for examples. + + ## Useful commands * `npx cdk deploy` deploy this stack to your default AWS account/region * `npx cdk diff` compare deployed stack with current state diff --git a/cdk-spa-init.mjs b/cdk-spa-init.mjs new file mode 100644 index 0000000..d2eb94f --- /dev/null +++ b/cdk-spa-init.mjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node +import shell from "shelljs"; +import path from "path"; +import fs from "fs"; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const logPrefix = 'CDK SPA Init'; + +shell.echo(`${logPrefix}: Initializing CDK stack index file for a dynamic SPA...`); + +if (!fs.existsSync('stack')) { + shell.mkdir('-p', 'stack'); + shell.cp(path.join(__dirname, 'templates/spa.ts'), 'stack/index.ts'); + shell.echo(`${logPrefix}: CDK stack index file created. Please adapt the file at 'stack/index.ts' to the project's needs.`); +} else { + shell.echo(`${logPrefix}: CDK stack folder already exists.`); +} \ No newline at end of file diff --git a/examples/astro/stack/index.ts b/examples/astro/stack/index.ts index d03fb0a..4772876 100644 --- a/examples/astro/stack/index.ts +++ b/examples/astro/stack/index.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "../../../"; -// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "../../../"; +// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: '665186350589', region: 'us-east-1' @@ -49,4 +49,4 @@ const appStackProps: StaticSiteProps = { }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); diff --git a/examples/gatsby/stack/index.ts b/examples/gatsby/stack/index.ts index b6f1dcc..c5cc458 100644 --- a/examples/gatsby/stack/index.ts +++ b/examples/gatsby/stack/index.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "../../../"; -// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "../../../"; +// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: '665186350589', region: 'us-east-1' @@ -39,4 +39,4 @@ const appStackProps: StaticSiteProps = { }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); diff --git a/examples/vitepress/stack.ts b/examples/vitepress/stack.ts index 554b0fe..030eee9 100644 --- a/examples/vitepress/stack.ts +++ b/examples/vitepress/stack.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "../../"; -// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "../../"; +// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: '665186350589', region: 'us-east-1' @@ -36,4 +36,4 @@ const appStackProps: StaticSiteProps = { }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); diff --git a/examples/vuejs/stack.ts b/examples/vuejs/stack.ts index 10ba1d8..fd4ef19 100644 --- a/examples/vuejs/stack.ts +++ b/examples/vuejs/stack.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "../../"; -// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "../../"; +// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: '665186350589', region: 'us-east-1' @@ -35,4 +35,4 @@ const appStackProps: StaticSiteProps = { } }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.environment}-${appStackProps.service}-stack`, appStackProps); +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.environment}-${appStackProps.service}-stack`, appStackProps); diff --git a/index.ts b/index.ts index 7ee5e4d..cfcc67a 100644 --- a/index.ts +++ b/index.ts @@ -1,2 +1,2 @@ -export { StaticSiteStack } from './stack/StaticSiteStack'; -export type { StaticSiteProps } from './stack/StaticSiteProps'; \ No newline at end of file +export { SPAStack as SPAStack } from './stack/SPAStack'; +export type { SPAProps as SPAProps } from './stack/SPAProps'; \ No newline at end of file diff --git a/package.json b/package.json index ea6e0a9..8b8440c 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "@thunderso/cdk-spa", "version": "0.5.0", "description": "CDK Library for provisioning infrastructure for Single Page Applications.", + "tags": ["aws", "cdk", "spa", "typescript"], "type": "module", "main": "index.ts", "repository": { @@ -20,6 +21,9 @@ "clean": "tsc -b --clean", "watch": "tsc -b -w" }, + "bin": { + "cdk-spa-init": "cdk-spa-init.mjs" + }, "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "22.4.0", diff --git a/stack/StaticSiteProps.ts b/stack/SPAProps.ts similarity index 98% rename from stack/StaticSiteProps.ts rename to stack/SPAProps.ts index ae2a4b2..c46c1ab 100644 --- a/stack/StaticSiteProps.ts +++ b/stack/SPAProps.ts @@ -1,7 +1,7 @@ import {type StackProps} from "aws-cdk-lib"; import { BuildEnvironmentVariableType } from "aws-cdk-lib/aws-codebuild"; -export interface StaticSiteProps extends StackProps { +export interface SPAProps extends StackProps { /** * The AWS environment (account/region) where this stack will be deployed. */ @@ -97,6 +97,5 @@ export interface StaticSiteProps extends StackProps { */ readonly enableAssetCleanup?: boolean; - } \ No newline at end of file diff --git a/stack/StaticSiteStack.ts b/stack/SPAStack.ts similarity index 95% rename from stack/StaticSiteStack.ts rename to stack/SPAStack.ts index 73c2df8..de24815 100644 --- a/stack/StaticSiteStack.ts +++ b/stack/SPAStack.ts @@ -1,13 +1,13 @@ import { Aws, ArnFormat, Stack } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { ApplicationConstruct, EnvironmentConstruct, HostingConstruct, PipelineConstruct } from '../lib'; -import type { StaticSiteProps } from './StaticSiteProps'; +import type { SPAProps } from './SPAProps'; import { BuildEnvironmentVariableType } from 'aws-cdk-lib/aws-codebuild'; import { CfnDistribution, CfnOriginAccessControl } from "aws-cdk-lib/aws-cloudfront"; import { CfnBucketPolicy } from "aws-cdk-lib/aws-s3"; -export class StaticSiteStack extends Stack { - constructor(scope: Construct, id: string, props?: StaticSiteProps) { +export class SPAStack extends Stack { + constructor(scope: Construct, id: string, props?: SPAProps) { super(scope, id, props); // Check mandatory properties diff --git a/templates/spa.ts b/templates/spa.ts index 1c5feb0..b80c2bf 100644 --- a/templates/spa.ts +++ b/templates/spa.ts @@ -1,8 +1,8 @@ #!/usr/bin/env node import { App } from "aws-cdk-lib"; -import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa"; +import { SPAStack, type SPAProps } from "@thunderso/cdk-spa"; -const appStackProps: StaticSiteProps = { +const appStackProps: SPAProps = { env: { account: 'your-account-id', region: 'us-east-1' @@ -52,4 +52,4 @@ const appStackProps: StaticSiteProps = { // }, }; -new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); \ No newline at end of file +new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps); \ No newline at end of file