├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── npm-publish.yml │ ├── on-docs-update.yml │ ├── on-issue-opened.yml │ ├── on-push.yml │ ├── on-workflow-update.yml │ └── run-security-checks.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── bin ├── cli │ ├── actions │ │ ├── delete.ts │ │ ├── deploy.ts │ │ ├── init.ts │ │ ├── show.ts │ │ └── status.ts │ ├── index.ts │ ├── shared │ │ ├── constants.ts │ │ └── types.ts │ └── utils │ │ ├── awsSDKUtil.ts │ │ ├── helper.ts │ │ └── prompt_questions.ts └── cloudfront-hosting-toolkit.ts ├── cdk.json ├── cdk.json.built ├── cdk.json.dev ├── docs ├── .gitignore ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public │ └── img │ │ ├── architecture.jpg │ │ ├── deploy.gif │ │ ├── flow.png │ │ └── init.gif ├── src │ ├── assets │ │ └── fonts │ │ │ ├── JetBrainsMonoNerdFont-Bold.ttf │ │ │ ├── JetBrainsMonoNerdFont-BoldItalic.ttf │ │ │ ├── JetBrainsMonoNerdFont-Italic.ttf │ │ │ └── JetBrainsMonoNerdFont-Regular.ttf │ ├── components │ │ └── code.astro │ ├── content │ │ ├── config.ts │ │ └── docs │ │ │ ├── advanced │ │ │ ├── bring-your-own-framework.md │ │ │ └── configuration.md │ │ │ ├── architecture │ │ │ ├── github-workflow.md │ │ │ ├── overview.md │ │ │ └── s3-workflow.md │ │ │ ├── features │ │ │ ├── custom-domains.md │ │ │ ├── github-integration.md │ │ │ ├── instant-deployment.md │ │ │ ├── optimized-caching.md │ │ │ ├── overview.md │ │ │ ├── security-headers.md │ │ │ └── setup-wizard.md │ │ │ ├── getting-started │ │ │ ├── how-it-works.md │ │ │ ├── introduction.md │ │ │ └── quickstart.md │ │ │ ├── index.mdx │ │ │ ├── project │ │ │ └── faq.md │ │ │ ├── troubleshooting │ │ │ └── guide.md │ │ │ └── user-guide │ │ │ ├── cdk-configuration.md │ │ │ ├── cdk-construct.md │ │ │ ├── cdk-guide.md │ │ │ ├── cdk-source-code.md │ │ │ └── cli-guide.md │ └── styles │ │ ├── custom.css │ │ ├── font.css │ │ ├── landing.css │ │ └── terminal.css └── tsconfig.json ├── img ├── architecture.jpg ├── deploy.gif └── init.gif ├── jest.config.js ├── lambda ├── delete_old_deployments │ └── index.js ├── layers │ └── aws_sdk │ │ └── nodejs │ │ ├── package-lock.json │ │ └── package.json ├── new_build │ └── index.js └── update_kvs │ └── index.js ├── lib ├── cfn_nag │ └── cfn_nag_utils.ts ├── deploy_type.ts ├── deployment_workflow_sf.ts ├── hosting.ts ├── hosting_infrastructure.ts ├── hosting_stack.ts ├── index.ts ├── pipeline_infrastructure.ts ├── repository_connection.ts ├── repository_stack.ts └── utility.ts ├── package-lock.json ├── package.json ├── resources ├── build_config_templates │ ├── hosting_angularjs.yml │ ├── hosting_basic.yml │ ├── hosting_nextjs.yml │ ├── hosting_reactjs.yml │ ├── hosting_vuejs.yml │ └── s3_build_config.yml ├── cff_templates │ ├── index_angularjs.js │ ├── index_basic.js │ ├── index_nextjs.js │ ├── index_reactjs.js │ └── index_vuejs.js ├── initial_repository │ └── index.html ├── initial_s3 │ └── index.html └── s3_trigger │ └── dummy.txt ├── roadmap.md ├── scripts └── createDummyZip.js ├── solution.context.json.template.github ├── solution.context.json.template.s3 ├── test ├── cloudfront-hosting-toolkit │ ├── angularjs │ │ └── package.json │ ├── nextjs │ │ └── package.json │ ├── no_framework │ │ └── index.html │ ├── reactjs │ │ └── package.json │ └── vuejs │ │ └── package.json ├── deploy.test.ts ├── detect_framework.test.ts ├── hosting_stack.test.ts ├── init.test.ts ├── repository_stack.test.ts └── uri_rewrite.test.ts └── tsconfig.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a reproducible bug to help us improve 3 | title: "Bug: TITLE" 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for submitting a bug report. Please add as much information as possible to help us reproduce, and remove any potential sensitive data. 10 | 11 | - type: textarea 12 | id: expected_behaviour 13 | attributes: 14 | label: Expected Behaviour 15 | description: Please share details on the behaviour you expected 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: current_behaviour 20 | attributes: 21 | label: Current Behaviour 22 | description: Please share details on the current issue 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: code_snippet 27 | attributes: 28 | label: Code snippet 29 | description: Please share a code snippet to help us reproduce the issue 30 | render: python 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: solution 35 | attributes: 36 | label: Possible Solution 37 | description: If known, please suggest a potential resolution 38 | validations: 39 | required: false 40 | - type: textarea 41 | id: steps 42 | attributes: 43 | label: Steps to Reproduce 44 | description: Please share how we might be able to reproduce this issue 45 | validations: 46 | required: true 47 | - type: markdown 48 | attributes: 49 | value: | 50 | --- 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for CloudFront Hosting Toolkit 3 | title: "Feature request: TITLE" 4 | labels: ["feature-request", "triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for taking the time to suggest an idea to the CloudFront Hosting Toolkit project. 10 | 11 | *Future readers*: Please react with 👍 and your use case to help us understand customer demand. 12 | - type: textarea 13 | id: problem 14 | attributes: 15 | label: Use case 16 | description: Please help us understand your use case or problem you're facing 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: suggestion 21 | attributes: 22 | label: Solution/User Experience 23 | description: Please share what a good solution would look like to this use case 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: alternatives 28 | attributes: 29 | label: Alternative solutions 30 | description: Please describe what alternative solutions to this use case, if any 31 | render: markdown 32 | validations: 33 | required: false 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | **Issue number:** 3 | 4 | ## Summary 5 | 6 | ### Changes 7 | 8 | > Please provide a summary of what's being changed 9 | 10 | ### User experience 11 | 12 | > Please share what the user experience looks like before and after this change 13 | 14 | ## Checklist 15 | 16 | If your change doesn't seem to apply, please leave them unchecked. 17 | 18 | * [ ] I have performed a self-review of this change 19 | * [ ] Changes have been tested 20 | * [ ] Changes are documented 21 | 22 |
23 | Is this a breaking change? 24 | 25 | **RFC issue number**: 26 | 27 | Checklist: 28 | 29 | * [ ] Migration process documented 30 | * [ ] Implement warnings (if it can live side by side) 31 | 32 |
33 | 34 | ## Acknowledgment 35 | 36 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 37 | 38 | **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. 39 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Publish to NPM 5 | 6 | on: 7 | workflow_dispatch 8 | 9 | jobs: 10 | build-and-publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@6ccd57f4c5d15bdc2fef309bd9fb6cc9db2ef1c6 14 | - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 15 | with: 16 | node-version: 20 17 | registry-url: https://registry.npmjs.org/ 18 | - run: npm install 19 | - run: npm run build 20 | - run: cp cdk.json.built cdk.json 21 | - run: npm pack 22 | - run: npm publish --access=public 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 25 | -------------------------------------------------------------------------------- /.github/workflows/on-docs-update.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docs/**' 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | jobs: 17 | # Build the documentation. 18 | build: 19 | concurrency: ci-${{ github.ref }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 24 | - name: Install, build, and upload documentation 25 | uses: withastro/action@9a7959a16949e620a22e74f81c10cb7ce3b76924 26 | with: 27 | path: ./docs 28 | - name: Upload artifact 29 | uses: actions/upload-pages-artifact@027b0ddc3de8dbe7e5a699814c4508ca8ecefc5f 30 | with: 31 | path: ./docs/dist 32 | 33 | # Deploy the documentation to GitHub Pages. 34 | deploy: 35 | needs: build 36 | runs-on: ubuntu-latest 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | steps: 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@7a9bd943aa5e5175aeb8502edcc6c1c02d398e10 44 | -------------------------------------------------------------------------------- /.github/workflows/on-issue-opened.yml: -------------------------------------------------------------------------------- 1 | name: Label issues 2 | 3 | on: 4 | issues: 5 | types: 6 | - reopened 7 | - opened 8 | 9 | permissions: 10 | issues: write 11 | 12 | jobs: 13 | label_issues: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/github-script@1f16022c7518aad314c43abcd029895291be0f52 17 | with: 18 | script: | 19 | github.rest.issues.addLabels({ 20 | issue_number: context.issue.number, 21 | owner: context.repo.owner, 22 | repo: context.repo.repo, 23 | labels: ["triage"] 24 | }) 25 | -------------------------------------------------------------------------------- /.github/workflows/on-push.yml: -------------------------------------------------------------------------------- 1 | name: Push Workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: 9 | - opened 10 | - edited 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | security-checks: 17 | uses: ./.github/workflows/run-security-checks.yml 18 | secrets: inherit -------------------------------------------------------------------------------- /.github/workflows/on-workflow-update.yml: -------------------------------------------------------------------------------- 1 | name: Lockdown untrusted workflows 2 | 3 | on: 4 | push: 5 | paths: 6 | - ".github/workflows/**" 7 | pull_request: 8 | paths: 9 | - ".github/workflows/**" 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | enforce_pinned_workflows: 17 | name: Harden Security 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 22 | - name: Ensure 3rd party workflows have SHA pinned 23 | uses: zgosalvez/github-actions-ensure-sha-pinned-actions@f32435541e24cd6a4700a7f52bb2ec59e80603b1 24 | -------------------------------------------------------------------------------- /.github/workflows/run-security-checks.yml: -------------------------------------------------------------------------------- 1 | name: Run security checks on the project 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | scan: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Checkout and setup. 15 | - name: Checkout repository 16 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 17 | - name: Install dependencies 18 | run: npm install 19 | 20 | # NPM audit. 21 | - name: Run audit 22 | run: npm audit 23 | 24 | # GitLeaks. 25 | - name: Run Gitleaks 26 | uses: gitleaks/gitleaks-action@4df650038e2eb9f7329218df929c2780866e61a3 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !jest.config.js 2 | *.d.ts 3 | node_modules 4 | dist 5 | 6 | 7 | bin/*.js 8 | 9 | .cdk.staging 10 | cdk.out 11 | solution.context.json 12 | resources/s3_trigger/dummy.zip 13 | resources/build_config/*.* 14 | *.tgz 15 | cloudfront-hosting-toolkit/*.* 16 | cloudfront-hosting-toolkit 17 | coverage 18 | 19 | .DS_Store 20 | resources/.DS_Store 21 | resources/s3_trigger/.DS_Store 22 | 23 | lib/**/*.d.ts 24 | lib/**/*.js 25 | 26 | bin/**/*.d.ts 27 | bin/**/*.js 28 | 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | * 4 | 5 | !lib/**/*.d.ts 6 | !lib/**/*.js 7 | 8 | !bin/**/*.d.ts 9 | !bin/**/*.js 10 | 11 | !lambda/**/*.js 12 | !resources/**/*/*.* 13 | 14 | !scripts/*.js 15 | 16 | !package.json 17 | !package-lock.json 18 | 19 | !cdk.json 20 | 21 | docs/ 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.0] - 2023-09-31 8 | ### Added 9 | - Initial version 10 | 11 | ## [1.1.0] - 2024-01-10 12 | ### Added 13 | 14 | - Use CloudFront KeyValueStore to store commitId 15 | 16 | ### Changed 17 | ### Fixed 18 | 19 | ## [1.1.1] - 2024-01-10 20 | ### Added 21 | 22 | ### Changed 23 | 24 | - remove age and date header 25 | 26 | ### Fixed 27 | 28 | ## [1.1.2] - 2024-01-10 29 | ### Added 30 | 31 | ### Changed 32 | 33 | - build AWS Layer manually 34 | 35 | ### Fixed 36 | 37 | ## [1.1.3] - 2024-01-25 38 | ### Added 39 | 40 | - max name for OAC & remove old comments 41 | 42 | ### Changed 43 | ### Fixed 44 | 45 | ## [1.1.4] - 2024-01-25 46 | ### Added 47 | 48 | ### Changed 49 | 50 | - use custom name for KVS 51 | 52 | ### Fixed 53 | 54 | ## [1.1.5] - 2024-01-25 55 | ### Added 56 | 57 | - Use Lambda Power Tools for logging and tracing 58 | 59 | ### Changed 60 | ### Fixed 61 | 62 | 63 | ## [1.1.6] - 2024-05-24 64 | ### Added 65 | 66 | 67 | ### Changed 68 | 69 | - Use one CloudFront Function template per framework 70 | - Generate the necessary dummy zip file required by the Pipeline during installation, instead of using the CLI deploy command 71 | 72 | ### Fixed 73 | 74 | ## [1.1.7] - 2024-06-04 75 | ### Added 76 | 77 | 78 | ### Changed 79 | 80 | 81 | ### Fixed 82 | 83 | - Address the behavior when a user wants to utilize their own framework that is not included in the existing list of supported frameworks 84 | 85 | ## [1.1.8] - 2024-06-05 86 | ### Added 87 | 88 | 89 | ### Changed 90 | 91 | 92 | ### Fixed 93 | 94 | - Lambda layer path 95 | - Update Readme CDK Usage 96 | 97 | ## [1.1.9] - 2024-06-05 98 | ### Added 99 | 100 | 101 | ### Changed 102 | 103 | 104 | ### Fixed 105 | 106 | - Fix A record 107 | 108 | 109 | ## [1.1.10] - 2024-06-07 110 | ### Added 111 | 112 | - Add option to submit a feature request 113 | 114 | 115 | ### Changed 116 | 117 | 118 | ### Fixed 119 | 120 | 121 | ## [1.1.11] - 2024-06-07 122 | ### Added 123 | 124 | 125 | ### Changed 126 | 127 | 128 | ### Fixed 129 | 130 | - fix wording for A record 131 | - update a few prompts to be more explicit 132 | 133 | ## [1.1.12] - 2024-06-07 134 | ### Added 135 | 136 | 137 | ### Changed 138 | 139 | 140 | ### Fixed 141 | 142 | - Properly bundles package before publishing to npm 143 | - Ensure connection stack name follows naming pattern 144 | 145 | ## [1.1.15] - 2024-06-07 146 | ### Added 147 | 148 | 149 | ### Changed 150 | 151 | 152 | ### Fixed 153 | 154 | - Removal special characters Action name in CodePipeline 155 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudFront Hosting Toolkit 2 | 3 | CloudFront Hosting Toolkit is an open-source command-line tool designed to simplify the deployment and management of fast, secure frontend applications on AWS. It offers the convenience of a managed frontend hosting service while giving developers full control over their hosting and deployment infrastructure. 4 | 5 |

6 | GitHub Repo 7 | npm 8 | Documentation 9 |

10 | 11 | 12 | 13 | ## What is CloudFront Hosting Toolkit? 14 | 15 | CloudFront Hosting Toolkit is a comprehensive solution that automates the process of setting up and managing a robust, scalable frontend hosting infrastructure on AWS. It leverages several AWS services, including CloudFront, S3, CodePipeline, and Lambda, to create a powerful hosting environment tailored for modern web applications. 16 | 17 | Key features include: 18 | - 🚀 Automated setup of AWS resources for frontend hosting 19 | - 🔄 Continuous deployment pipeline for GitHub and S3-based workflows 20 | - 🌐 Optimized content delivery through CloudFront 21 | - 🔒 Built-in security features including HTTPS and security headers 22 | - 🔗 Custom domain support with automatic SSL/TLS certificate management 23 | - 🛠️ Flexible configuration options for various frontend frameworks 24 | 25 | ## How It Works 26 | 27 | CloudFront Hosting Toolkit streamlines the deployment process through a simple CLI interface. It automatically provisions and configures necessary AWS resources, handles the deployment pipeline, and manages content delivery through CloudFront. 28 | 29 | For a detailed explanation of the architecture and workflow, please refer to our [Architecture documentation](https://awslabs.github.io/cloudfront-hosting-toolkit/architecture/overview). 30 | 31 | ## Why Use CloudFront Hosting Toolkit? 32 | 33 | - **Simplicity**: Deploy complex frontend hosting setups with just a few commands. 34 | - **Speed**: Leverage CloudFront's global CDN for fast content delivery. 35 | - **Security**: Automatic HTTPS configuration and security headers. 36 | - **Flexibility**: Support for various frontend frameworks and deployment sources. 37 | - **Cost-Effective**: Utilize AWS services efficiently without unnecessary overhead. 38 | - **Full Control**: Retain the ability to customize and extend your infrastructure. 39 | 40 | ## Re:Invent 2024 lightning talk 41 | 42 |

43 | 44 | AWS re:Invent 2024 - CloudFront Hosting Toolkit lightning talk 45 | 46 |

47 | 48 | Learn how to leverage CloudFront Hosting Toolkit for deploying secure and fast frontends using Git-based workflows while maintaining full control over your AWS resources. 49 | 50 | 51 | 52 | ## Getting Started 53 | 54 | Check out our [documentation](https://awslabs.github.io/cloudfront-hosting-toolkit/) for comprehensive guides on setting up and using the Cloudfront Hosting Toolkit! 55 | 56 | 57 | 58 | ### Requirements 59 | 60 | - Node.js 18+ 61 | - AWS CLI 2+ configured with your AWS account 62 | - (Optional) A GitHub account for GitHub-based deployments 63 | 64 | ### Installation 65 | 66 | ```bash 67 | npm install -g @aws/cloudfront-hosting-toolkit 68 | ``` 69 | 70 | ### Quick Start 71 | 72 | 1. Initialize your project: 73 | ```bash 74 | cloudfront-hosting-toolkit init 75 | ``` 76 | The animated GIF below demonstrates the initialization process 77 | ![sample](img/init.gif "CloudFront Hosting Toolkit Init") 78 | 79 | 2. Deploy your website: 80 | ```bash 81 | cloudfront-hosting-toolkit deploy 82 | ``` 83 | The animated GIF below demonstrates the deployment process 84 | ![sample](img/deploy.gif "CloudFront Hosting Toolkit Deploy") 85 | 86 | 87 | For more detailed instructions and advanced usage, please refer to our [CLI Guide](https://awslabs.github.io/cloudfront-hosting-toolkit/user-guide/cli-guide). 88 | 89 | ## Example Commands 90 | 91 | ```bash 92 | # Show domain name 93 | cloudfront-hosting-toolkit show 94 | 95 | # Check deployment status 96 | cloudfront-hosting-toolkit status 97 | 98 | # Remove hosting infrastructure 99 | cloudfront-hosting-toolkit delete 100 | ``` 101 | 102 | ## Architecture 103 | 104 | ![Technical diagram](img/architecture.jpg) 105 | 106 | CloudFront Hosting Toolkit sets up a comprehensive AWS architecture for your frontend hosting: 107 | 108 | - **Source Control**: GitHub repository or S3 bucket 109 | - **CI/CD**: AWS CodePipeline for automated builds and deployments 110 | - **Build Process**: AWS CodeBuild for compiling and creating deployment artifacts 111 | - **Storage**: S3 buckets for hosting website files 112 | - **Content Delivery**: CloudFront for global content distribution 113 | - **Routing**: CloudFront Functions for request handling and routing 114 | - **Orchestration**: Step Functions for managing deployment processes 115 | - **State Management**: KVS for storing deployment state information 116 | 117 | This architecture ensures a scalable, performant, and maintainable hosting solution for your frontend applications. 118 | 119 | ## Advanced Usage 120 | 121 | CloudFront Hosting Toolkit offers flexibility in how it can be used: 122 | 123 | - **CLI**: Use the Command-Line Interface for a straightforward, step-by-step deployment process. 124 | - **CDK Construct**: Leverage the CloudFront Hosting Toolkit as a ready-made L3 CDK construct for seamless integration into your AWS CDK projects. 125 | - **CDK Source Code**: Customize the CDK source code to tailor the infrastructure to your specific requirements. 126 | 127 | For more information on how to use CloudFront Hosting Toolkit, including advanced usage scenarios and in-depth customization options, please refer to our extensive documentation in the [Advanced section](https://awslabs.github.io/cloudfront-hosting-toolkit/advanced/configuration/). 128 | 129 | ## Documentation 130 | 131 | - [How it works](https://awslabs.github.io/cloudfront-hosting-toolkit/getting-started/how-it-works) 132 | - [CLI Guide](https://awslabs.github.io/cloudfront-hosting-toolkit/user-guide/cli-guide) 133 | - [CDK Integration](https://awslabs.github.io/cloudfront-hosting-toolkit/user-guide/cdk-guide) 134 | - [Troubleshooting Guide](https://awslabs.github.io/cloudfront-hosting-toolkit/troubleshooting/guide) 135 | - [FAQ](https://awslabs.github.io/cloudfront-hosting-toolkit/project/faq) 136 | - [Contributing Guidelines](CONTRIBUTING.md) 137 | 138 | ## Roadmap 139 | 140 | For information about upcoming features and improvements, please see our [Roadmap](roadmap.md). 141 | 142 | ## 🤝 Contributing 143 | 144 | We welcome contributions! Please see our [Contributing Guide](https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/main/CONTRIBUTING.md) for more details. 145 | 146 | # Author 147 | 148 | - [Corneliu Croitoru](https://www.linkedin.com/in/corneliucroitoru/) 149 | 150 | # Contributors 151 | [![contributors](https://contrib.rocks/image?repo=awslabs/cloudfront-hosting-toolkit&max=2000)](https://github.com/awslabs/cloudfront-hosting-toolkit/graphs/contributors) 152 | 153 | 154 | 155 | 156 | 157 | ## License 158 | 159 | This library is licensed under the Apache-2.0 License. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Reporting a vulnerability 4 | 5 | If you discover a potential security issue in this project, we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to . 6 | 7 | Please do **not** create a public GitHub issue. 8 | -------------------------------------------------------------------------------- /bin/cli/actions/delete.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import { 19 | startPrompt, 20 | executeCommands, 21 | getToolFolder, 22 | getCLIInstallationFolder, 23 | loadHostingConfiguration, 24 | } from "../utils/helper"; 25 | import { hostingInfrastructureDeletionConfirmation } from "../utils/prompt_questions"; 26 | import checkAWSConnection, { 27 | checkCertificateExists, 28 | checkCFARecordExists, 29 | deleteACMCertificate, 30 | deleteCFCNAME, 31 | getSSMParameter, 32 | } from "../utils/awsSDKUtil"; 33 | import { SSM_DOMAIN_STR } from "../shared/constants"; 34 | 35 | export async function handleDeleteCommand() { 36 | await checkAWSConnection(); 37 | 38 | var counter = 1; 39 | const confirm = await startPrompt(hostingInfrastructureDeletionConfirmation); 40 | if (confirm.value == "exit") { 41 | process.exit(0); 42 | } 43 | 44 | var certificateArnCmdParam = ""; 45 | var certificateArn; 46 | const hostingConfiguration = await loadHostingConfiguration(); 47 | 48 | 49 | if (hostingConfiguration.domainName) { 50 | const certificateInfo = await checkCertificateExists(hostingConfiguration.domainName); 51 | certificateArn = certificateInfo.certificateArn; 52 | if (certificateArn) { 53 | certificateArnCmdParam = `--context certificate-arn=${certificateArn}`; 54 | } 55 | } 56 | 57 | console.log(`\n --> ${counter++}. Deleting the hosting infrastructure \n`); 58 | 59 | console.log( 60 | "Please wait while deleting the infrastructure, it may take a few minutes." 61 | ); 62 | const deleteCmd= `npx cdk destroy --all --force --context config-path=${getToolFolder()} ${certificateArnCmdParam}`; 63 | 64 | await executeCommands(deleteCmd, getCLIInstallationFolder()); 65 | 66 | if (hostingConfiguration.domainName && hostingConfiguration.hostedZoneId) { 67 | const domainName = await getSSMParameter(SSM_DOMAIN_STR); 68 | 69 | if(domainName){ 70 | const cFCNAMEExists = await checkCFARecordExists( 71 | hostingConfiguration.domainName, 72 | domainName, 73 | hostingConfiguration.hostedZoneId 74 | ); 75 | if (cFCNAMEExists) { 76 | console.log( 77 | `\n --> ${counter++}. Removing entries from the Route53 Hosted Zone \n` 78 | ); 79 | 80 | await deleteCFCNAME( 81 | hostingConfiguration.domainName, 82 | domainName, 83 | hostingConfiguration.hostedZoneId 84 | ); 85 | } 86 | } 87 | 88 | } 89 | 90 | if (certificateArn) { 91 | console.log( 92 | `\n --> ${counter++}. Deleting the SSL/TLS certificate from AWS Certificate Manager (ACM)\n` 93 | ); 94 | 95 | await deleteACMCertificate(certificateArn); 96 | } 97 | 98 | console.log( 99 | "\nThe hosting infrastructure has been completely removed from your AWS account." 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /bin/cli/actions/show.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import checkAWSConnection, { 19 | checkCFARecordExists, 20 | checkCertificateExists, 21 | getSSMParameter, 22 | } from "../utils/awsSDKUtil"; 23 | import { IHosting } from "../shared/types"; 24 | import { SSM_DOMAIN_STR } from "../shared/constants"; 25 | import { isRepoConfig, loadHostingConfiguration } from "../utils/helper"; 26 | 27 | /** 28 | * Display the Source and Domain Name of the currently deployed infrastructure. 29 | * It first checks the AWS connection, then retrieves the details of the hosting configuration. 30 | * If a hosting configuration exists, it prints the Source and its associated Domain Name. 31 | * If no hosting configuration is found, it informs the user that there is no domain name available 32 | * as no infrastructure has been deployed yet. 33 | */ 34 | export async function handleShowCommand() { 35 | await checkAWSConnection(); 36 | const hostingConfiguration = await loadHostingConfiguration(); 37 | const domainName = await getSSMParameter(SSM_DOMAIN_STR); 38 | if (domainName) { 39 | console.log( 40 | "\nThe Origin paired with its associated CloudFront domain name:\n" 41 | ); 42 | 43 | if (isRepoConfig(hostingConfiguration)) { 44 | 45 | console.log( 46 | `Code Repository: ${hostingConfiguration.repoUrl}/${hostingConfiguration.branchName} --> Hosting: https://${domainName}\n` 47 | ); 48 | } else { 49 | console.log( 50 | `S3 Source Code: ${hostingConfiguration.s3bucket}/${hostingConfiguration.s3path} --> Hosting: https://${domainName}\n` 51 | ); 52 | } 53 | 54 | if (hostingConfiguration.domainName) { 55 | let { certificateArn, status } = await checkCertificateExists( 56 | hostingConfiguration.domainName 57 | ); 58 | if (certificateArn && status == "ISSUED") { 59 | if (hostingConfiguration.hostedZoneId) { 60 | const cFCNAMEExists = await checkCFARecordExists( 61 | hostingConfiguration.domainName, 62 | domainName, 63 | hostingConfiguration.hostedZoneId 64 | ); 65 | if (cFCNAMEExists) { 66 | console.log( 67 | `${hostingConfiguration.domainName} --> Hosting: ${domainName} \n` 68 | ); 69 | } 70 | } else { 71 | console.log( 72 | `${hostingConfiguration.domainName} --> Hosting: ${domainName} \n` 73 | ); 74 | } 75 | } 76 | } 77 | } else { 78 | console.log( 79 | "\nAt the moment, there is no hosting infrastructure deployed.\n" 80 | ); 81 | process.exit(0); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /bin/cli/actions/status.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import checkAWSConnection from "../utils/awsSDKUtil"; 19 | import { checkPipelineStatus } from "../utils/helper"; 20 | 21 | /** 22 | * Retrieves the status of an AWS CodePipeline and displays the status of its stages. 23 | * 24 | * @async 25 | * @function getPipelineStatus 26 | * @throws {Error} Throws an error if there is a problem retrieving the pipeline state. 27 | */ 28 | 29 | export async function handleStatusCommand() { 30 | await checkAWSConnection(); 31 | await checkPipelineStatus(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bin/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"). 6 | * You may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import { handleDeleteCommand } from "./actions/delete"; 19 | import handleDeployCommand from "./actions/deploy"; 20 | import { handleShowCommand } from "./actions/show"; 21 | import handleInitCommand from "./actions/init"; 22 | import { ERROR_PREFIX } from "./shared/constants"; 23 | import { handleStatusCommand } from "./actions/status"; 24 | 25 | const yargs = require("yargs"); 26 | 27 | async function main() { 28 | const args = yargs 29 | .usage("Usage: $0 [options]") 30 | .command( 31 | "init", 32 | "Step by step guide for configuring a GitHub source code repository and generate a configuration file", 33 | (yargs: any) => { 34 | yargs 35 | .option("s3", { 36 | describe: 37 | "Step by step guide for configuring an S3 source code repository and generate a configuration file", 38 | type: "boolean", 39 | }) 40 | } 41 | ) 42 | .command( 43 | "deploy", 44 | 'Initiate a deployment of the infrastructure, utilizing the configuration file generated during the execution of the "init" command' 45 | ) 46 | .command( 47 | "show", 48 | "Show the domain name connected to the deployed source code repository for a website that has been deployed" 49 | ) 50 | .command( 51 | "delete", 52 | "Completely remove the hosting infrastructure from your AWS account" 53 | ) 54 | .command("status", "Display the current status of the pipeline deployment") 55 | .help() 56 | .parse(); 57 | 58 | if (args._.length > 1) { 59 | console.error(`${ERROR_PREFIX} Only one command at a time`); 60 | process.exit(1); 61 | } 62 | 63 | await handleCommand(args); 64 | } 65 | 66 | async function handleCommand({ 67 | _: [command], 68 | s3, 69 | }: { 70 | _: string[]; 71 | s3?: boolean; 72 | }) { 73 | switch (command) { 74 | case "deploy": 75 | await handleDeployCommand(); 76 | break; 77 | 78 | case "show": 79 | await handleShowCommand(); 80 | break; 81 | case "init": 82 | await handleInitCommand(s3 || false); 83 | break; 84 | case "delete": 85 | await handleDeleteCommand(); 86 | break; 87 | case "status": 88 | await handleStatusCommand(); 89 | break; 90 | default: 91 | yargs.showHelp() 92 | } 93 | } 94 | 95 | if (require.main === module) { 96 | main().catch((err) => { 97 | console.error(err); 98 | process.exit(1); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /bin/cli/shared/constants.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import { Dictionary } from "./types"; 19 | import * as Joi from "joi"; 20 | 21 | export const TOOL_NAME = "cloudfront-hosting-toolkit"; 22 | export const CONFIG_FILE_NAME = TOOL_NAME + "-config.json"; 23 | export const BUILD_FILE_NAME = TOOL_NAME + "-build.yml"; 24 | export const CFF_FILE_NAME = TOOL_NAME + "-cff.js"; 25 | 26 | 27 | /* 28 | export const SOURCE_STR = "HostingHostingInfrastructureDeployTypeSource"; 29 | export const DOMAIN_STR = "HostingHostingInfrastructureDomainName"; 30 | export const PIPELINENAME_STR = "HostingPipelineInfrastructurePipelineName"; 31 | 32 | export const CONNECTION_ARN_STR = "RepositoryConnectionConnectionArn"; 33 | export const CONNECTION_NAME_STR = "RepositoryConnectionConnectionName"; 34 | export const CONNECTION_REGION_STR = "RepositoryConnectionHostingRegion"; 35 | */ 36 | 37 | export const SSM_SOURCE_STR = "HostingHostingInfrastructureDeployTypeSource"; 38 | 39 | export const SSM_DOMAIN_STR = "DomainName"; 40 | export const SSM_PIPELINENAME_STR = "PipelineName"; 41 | export const SSM_CONNECTION_ARN_STR = "ConnectionArn"; 42 | export const SSM_CONNECTION_NAME_STR = "ConnectionName"; 43 | export const SSM_CONNECTION_REGION_STR = "ConnectionRegion"; 44 | 45 | 46 | export const CONNECTION_STACK_NAME = "hosting-connection"; 47 | export const MAIN_STACK_NAME = "hosting-main"; 48 | 49 | export const CLOUDFRONT_HOSTEDZONE_ID = "Z2FDTNDATAQYW2"; 50 | 51 | export const GITHUB_REGEX = 52 | /^((https:\/\/github\.com\/([^/]+)\/([^/]+))|(git@github\.com:([^/]+)\/([^/]+)))\.git$/; 53 | export const DOMAIN_NAME_REGEX = 54 | /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i; 55 | 56 | 57 | export const ERROR_PREFIX = "\n\n[ERROR]"; 58 | 59 | export const FRAMEWORKS: Dictionary = { 60 | reactjs: "React Framework", 61 | nextjs: "Next.js Framework", 62 | angularjs: "AngularJS Framework", 63 | vuejs: "Vue.js Framework", 64 | astro: "Astro Framework", 65 | basic: "No FrontEnd framework used; Basic implementation (no build required)", 66 | }; 67 | 68 | export const SCHEMA = Joi.object() 69 | .keys({ 70 | repoUrl: Joi.string().optional(), 71 | branchName: Joi.string().optional(), 72 | framework: Joi.string().optional(), 73 | s3bucket: Joi.string().optional(), 74 | s3path: Joi.string().allow("").optional(), 75 | domainNameRegex: Joi.string().allow("").optional(), 76 | hostedZoneId: Joi.string().allow("").optional(), 77 | }) 78 | .unknown(); 79 | -------------------------------------------------------------------------------- /bin/cli/shared/types.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | export interface CDKCommand { 19 | label: string; 20 | cmd: any; 21 | } 22 | 23 | 24 | export type CommonAttributes = { 25 | domainName?: string; 26 | hostedZoneId?: string; 27 | }; 28 | 29 | export type HostingConfiguration = ( 30 | { 31 | repoUrl: string; 32 | branchName: string; 33 | framework: string; 34 | } & CommonAttributes 35 | ) | ( 36 | { 37 | s3bucket: string; 38 | s3path: string; 39 | } & CommonAttributes 40 | ); 41 | 42 | 43 | export interface IChoice { 44 | title: string; 45 | value: string; 46 | } 47 | 48 | export interface IConnection { 49 | arn: string; 50 | name: string; 51 | region: string; 52 | } 53 | 54 | export interface IHosting { 55 | domain: string; 56 | source: string; 57 | type: string; 58 | pipeline: string; 59 | } 60 | 61 | export interface Dictionary { 62 | [key: string]: T; 63 | } 64 | 65 | export interface PackageJson { 66 | dependencies?: Record; 67 | devDependencies?: Record; 68 | scripts?: Record; 69 | } 70 | 71 | export const FrontendFramework = { 72 | REACT: "reactjs", 73 | VUE: "vuejs", 74 | ANGULAR: "angularjs", 75 | NEXT: "nextjs", 76 | BASIC: "basic", 77 | }; 78 | 79 | export interface CNAMES { 80 | key: string; 81 | value: any; 82 | } 83 | 84 | export interface PromptItems { 85 | title: string; 86 | value: string; 87 | } 88 | -------------------------------------------------------------------------------- /bin/cli/utils/prompt_questions.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import { frameworkList, isValidBucketName, isValidDomainName, isValidGithubUrl, validateNoLeadingTrailingSlashes } from "./helper"; 19 | 20 | //export const getFrameworkSelectionQuestions = (initialValue?: string) => { 21 | 22 | export const getGithubRepositoryQuestions = ( 23 | initialUrl?: string, 24 | initialBranch?: string 25 | ) => { 26 | const question = [ 27 | { 28 | type: "text", 29 | name: "repoUrl", 30 | message: "Please provide your GitHub repository URL", 31 | ...(initialUrl !== undefined && { initial: initialUrl }), // Conditionally include 'initial' 32 | validate: (value: string) => 33 | isValidGithubUrl(value) 34 | ? true 35 | : "GitHub repository format must be https://github.com/USERNAME/REPOSITORY.git or git@github.com:USERNAME/REPOSITORY.git and can be added only once", 36 | }, 37 | { 38 | type: "text", 39 | name: "branchName", 40 | message: 41 | "What is the name of the branch you would like to use? Hit Enter to confirm or change the selection.", 42 | initial: initialBranch || "main", 43 | validate: (value: string) => 44 | value.length > 0 ? true : "Branch name is mandatory", 45 | }, 46 | ]; 47 | 48 | return question; 49 | }; 50 | 51 | export const getS3BucketConfigurationQuestions = ( 52 | defaultBucket: string, 53 | defaultPath: string = "" 54 | ) => [ 55 | { 56 | type: "text", 57 | name: "s3bucket", 58 | ...(defaultBucket !== undefined && { initial: defaultBucket }), 59 | message: "Please enter the name of the bucket you would like to use", 60 | validate: (value: string) => 61 | isValidBucketName(value) ? true : "The bucket name must not be empty, and the corresponding bucket must exist", 62 | }, 63 | { 64 | type: "text", 65 | name: "s3path", 66 | ...(defaultPath !== undefined && { initial: defaultPath }), 67 | message: 68 | "If you would like to specify a prefix, please enter it below. Otherwise, leave it blank", 69 | validate: (value:string) => validateNoLeadingTrailingSlashes(value) ? true : "The prefix should not have leading or trailing slashes", 70 | 71 | }, 72 | ]; 73 | 74 | function findIndexByValue( 75 | objects: { title: string; value: string }[], 76 | value?: string 77 | ) { 78 | if (!value) return 0; 79 | for (let i = 0; i < objects.length; i++) { 80 | if (objects[i].value === value) { 81 | return i; 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | export const getFrameworkSelectionQuestions = (initialValue?: string) => { 88 | const index = findIndexByValue(frameworkList(), initialValue); 89 | const question = { 90 | type: "select", 91 | name: "framework", 92 | message: 93 | "Which framework did you use for website construction? Press Enter to confirm or change the selection.", 94 | choices: frameworkList(), 95 | initial: index, 96 | }; 97 | return [question]; 98 | }; 99 | 100 | export const getDomainNameQuestion = (defaultDomainName?: string) => [ 101 | { 102 | type: "select", 103 | name: "value", 104 | message: "Do you own a domain name that you would like to use?", 105 | choices: [ 106 | { title: "Yes", value: "yes" }, 107 | { title: "No", value: "no" }, 108 | ], 109 | initial: defaultDomainName ? 0 : 1, 110 | }, 111 | ]; 112 | 113 | export const continueConfirmationQuestion = { 114 | type: "text", 115 | name: "value", 116 | message: "Please complete the operation and type 'ok' to continue ", 117 | validate: (value: string) => 118 | value.toLowerCase() == "ok" ? true : "You have to type OK to continue", 119 | }; 120 | 121 | export const domainNameDetailsQuestions = (defaultDomainName?: string) => [ 122 | { 123 | type: "text", 124 | name: "domainName", 125 | ...(defaultDomainName !== undefined && { initial: defaultDomainName }), 126 | 127 | message: 128 | "Please provide your domain name in the following formats: www.mydomainname.com or mydomainname.com ?", 129 | validate: (value: string) => 130 | isValidDomainName(value) 131 | ? true 132 | : "Domain name format must be www.mydomainname.com or mydomainname.com", 133 | }, 134 | { 135 | type: "select", 136 | name: "registrar", 137 | message: "Where is the authoritative DNS server of this domain?", 138 | choices: [ 139 | { title: "Elsewhere", value: "another" }, 140 | { title: "Route 53 in this AWS Account", value: "current" }, 141 | ], 142 | initial: 1, 143 | }, 144 | ]; 145 | 146 | export const hostedZoneIdQuestion = (defaultHostedZoneId?: string) => [ 147 | { 148 | type: "text", 149 | name: "hostedZoneId", 150 | ...(defaultHostedZoneId !== undefined && { initial: defaultHostedZoneId }), 151 | message: "Please type the hosted zone ID", 152 | validate: (value: string) => 153 | value.length > 0 ? true : "The hosted zone must not be empty", 154 | }, 155 | ]; 156 | 157 | export const cloudFrontAssociationQuestion = { 158 | type: "select", 159 | name: "value", 160 | message: 161 | "Would you like to associate the domain name to the CloudFront distribution automatically now, or would you prefer to do it later?", 162 | choices: [ 163 | { title: "Associate automatically now.", value: "yes" }, 164 | { title: "Do it later.", value: "no" }, 165 | ], 166 | initial: 0, 167 | }; 168 | 169 | 170 | 171 | export const manualGitHubConfigConfirmationQuestion = { 172 | type: "select", 173 | name: "value", 174 | message: 175 | "Would you like to manually enter your GitHub repository information?", 176 | choices: [ 177 | { title: "Yes", value: "manual" }, 178 | { title: "No, exit", value: "exit" }, 179 | ], 180 | initial: 0, 181 | }; 182 | 183 | export const hostingInfrastructureDeletionConfirmation = { 184 | type: "select", 185 | name: "value", 186 | message: 187 | "Are you sure you want to completely remove the hosting infrastructure from your AWS account?", 188 | choices: [ 189 | { title: "Yes", value: "save" }, 190 | { title: "No", value: "exit" }, 191 | ], 192 | initial: 1, 193 | }; 194 | 195 | -------------------------------------------------------------------------------- /bin/cloudfront-hosting-toolkit.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import "source-map-support/register"; 18 | import { App } from "aws-cdk-lib"; 19 | import { HostingStack } from "../lib/hosting_stack"; 20 | import { RepositoryStack } from "../lib/repository_stack"; 21 | 22 | import * as path from "path"; 23 | import { 24 | BUILD_FILE_NAME, 25 | CONFIG_FILE_NAME, 26 | CFF_FILE_NAME, 27 | TOOL_NAME, 28 | } from "./cli/shared/constants"; 29 | import { AwsSolutionsChecks } from "cdk-nag"; 30 | import { Aspects } from "aws-cdk-lib"; 31 | import { calculateConnectionStackName, calculateMainStackName, isRepoConfig, loadHostingConfiguration } from "./cli/utils/helper"; 32 | 33 | const app = new App(); 34 | 35 | //Aspects.of(app).add(new AwsSolutionsChecks()); 36 | 37 | (async () => { 38 | var configFilePath, configFile, certificateArn; 39 | 40 | if (app.node.tryGetContext("config-path")) { 41 | configFilePath = app.node.tryGetContext("config-path"); 42 | } 43 | else { 44 | configFilePath = path.join(__dirname, "..", TOOL_NAME); 45 | } 46 | 47 | 48 | if (app.node.tryGetContext("certificate-arn")) { 49 | certificateArn = app.node.tryGetContext("certificate-arn"); 50 | } 51 | 52 | configFile = configFilePath + "/" + CONFIG_FILE_NAME; 53 | 54 | const hostingConfiguration = await loadHostingConfiguration(configFile); 55 | 56 | const buildFilePath = configFilePath + "/" + BUILD_FILE_NAME; 57 | 58 | const cffSourceFilePath = configFilePath + "/" + CFF_FILE_NAME; 59 | 60 | var connectionStack; 61 | 62 | const mainStackName = calculateMainStackName(hostingConfiguration); 63 | 64 | if (isRepoConfig(hostingConfiguration)) { 65 | 66 | const connectionStackName = calculateConnectionStackName(hostingConfiguration.repoUrl, hostingConfiguration.branchName!); 67 | 68 | connectionStack = new RepositoryStack( 69 | app, 70 | connectionStackName, 71 | hostingConfiguration, 72 | { 73 | description: 'Cloudfront Hosting Toolkit Repository Stack', 74 | env: { 75 | region: process.env.CDK_DEFAULT_REGION, 76 | account: process.env.CDK_DEFAULT_ACCOUNT, 77 | }, 78 | } 79 | ); 80 | } 81 | 82 | new HostingStack( 83 | app, 84 | mainStackName, 85 | { 86 | connectionArn: connectionStack?.repositoryConnection.connectionArn, 87 | hostingConfiguration: hostingConfiguration, 88 | buildFilePath: buildFilePath, 89 | cffSourceFilePath: cffSourceFilePath, 90 | certificateArn: certificateArn, 91 | }, 92 | { 93 | description: 'Cloudfront Hosting Toolkit Hosting Stack (uksb-1tupboc37)', 94 | env: { 95 | region: process.env.CDK_DEFAULT_REGION, 96 | account: process.env.CDK_DEFAULT_ACCOUNT, 97 | }, 98 | crossRegionReferences: true, 99 | } 100 | ); 101 | })(); 102 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cloudfront-hosting-toolkit.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/core:target-partitions": [ 33 | "aws", 34 | "aws-cn" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cdk.json.built: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cloudfront-hosting-toolkit.js", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/core:target-partitions": [ 33 | "aws", 34 | "aws-cn" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cdk.json.dev: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cloudfront-hosting-toolkit.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/core:target-partitions": [ 33 | "aws", 34 | "aws-cn" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 |

5 | 6 |

7 |

8 |
9 | 10 | ## 🚀 Run 11 | 12 | To run the documentation locally, clone the repository and run: 13 | 14 | 15 | ```bash 16 | npm run dev 17 | ``` 18 | 19 | ## 🧞 Commands 20 | 21 | All commands are run from the root of the project, from a terminal: 22 | 23 | | Command | Action | 24 | | :------------------------ | :----------------------------------------------- | 25 | | `npm install` | Installs dependencies | 26 | | `npm run dev` | Starts local dev server at `localhost:4321` | 27 | | `npm run build` | Build your production site to `./dist/` | 28 | | `npm run preview` | Preview your build locally, before deploying | 29 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 30 | | `npm run astro -- --help` | Get help using the Astro CLI | 31 | 32 | ## 👀 Want to learn more? 33 | 34 | Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). 35 | -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import starlight from '@astrojs/starlight'; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | site: process.env.ASTRO_SITE, 7 | base: '/cloudfront-hosting-toolkit', 8 | markdown: { 9 | gfm: true 10 | }, 11 | integrations: [ 12 | starlight({ 13 | title: 'Cloudfront Hosting Toolkit', 14 | description: 'open source command line tool to help developers deploy fast and secure frontends in the cloud 🤖🚀', 15 | defaultLocale: 'en', 16 | favicon: '/src/assets/favicon.ico', 17 | customCss: [ 18 | './src/styles/landing.css', 19 | './src/styles/font.css', 20 | './src/styles/custom.css', 21 | './src/styles/terminal.css' 22 | ], 23 | social: { 24 | github: 'https://github.com/awslabs/cloudfront-hosting-toolkit' 25 | }, 26 | "sidebar": [ 27 | { 28 | "label": "Getting Started", 29 | "items": [ 30 | { "label": "Introduction", "link": "/getting-started/introduction" }, 31 | { "label": "Quickstart", "link": "/getting-started/quickstart" }, 32 | { "label": "How It Works", "link": "/getting-started/how-it-works" } 33 | ] 34 | }, 35 | { 36 | "label": "Architecture", 37 | "items": [ 38 | { "label": "Overview", "link": "/architecture/overview" }, 39 | { "label": "GitHub Workflow", "link": "/architecture/github-workflow" }, 40 | { "label": "S3 Workflow", "link": "/architecture/s3-workflow" } 41 | ] 42 | }, 43 | { 44 | "label": "User Guide", 45 | "items": [ 46 | { "label": "CLI Guide", "link": "/user-guide/cli-guide" }, 47 | { 48 | label: 'CDK Guide', 49 | items: [ 50 | { label: 'Overview', link: '/user-guide/cdk-guide' }, 51 | { label: 'CDK Construct', link: '/user-guide/cdk-construct' }, 52 | { label: 'CDK Source code', link: '/user-guide/cdk-source-code' }, 53 | { label: 'Configuration guide', link: '/user-guide/cdk-configuration' }, 54 | ] 55 | }, 56 | ] 57 | }, 58 | { 59 | "label": "Features", 60 | "items": [ 61 | { "label": "Overview", "link": "/features/overview" }, 62 | { "label": "Self-paced wizard", "link": "/features/setup-wizard" }, 63 | { "label": "Instant deployment", "link": "/features/instant-deployment" }, 64 | { "label": "GitHub integration", "link": "/features/github-integration" }, 65 | { "label": "Optimized caching", "link": "/features/optimized-caching" }, 66 | { "label": "Security headers", "link": "/features/security-headers" }, 67 | { "label": "Custom domain support", "link": "/features/custom-domains" } 68 | ] 69 | }, 70 | { 71 | "label": "Advanced Usage", 72 | "items": [ 73 | { "label": "Advanced configuration", "link": "/advanced/configuration" }, 74 | { "label": "Bring your own framework", "link": "/advanced/bring-your-own-framework" } 75 | ] 76 | }, 77 | { 78 | "label": "Troubleshooting", 79 | "items": [ 80 | { "label": "Troubleshooting guide", "link": "/troubleshooting/guide" } 81 | ] 82 | }, 83 | { 84 | "label": "Project Info", 85 | "items": [ 86 | { "label": "FAQ", "link": "/project/faq" }, 87 | ] 88 | } 89 | ] 90 | 91 | }) 92 | ] 93 | }); 94 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudfront-hosting-toolkit/docs", 3 | "description": "The official documentation for Multi Agent Orchestration", 4 | "type": "module", 5 | "version": "0.7.0", 6 | "private": true, 7 | "scripts": { 8 | "dev": "npx astro dev", 9 | "start": "npx astro dev", 10 | "build": "npx astro build", 11 | "preview": "npx astro preview", 12 | "astro": "npx astro", 13 | "audit": "npm audit", 14 | "clean": "npx rimraf .astro/ node_modules/ dist/" 15 | }, 16 | "author": { 17 | "name": "Amazon Web Services", 18 | "url": "https://aws.amazon.com" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/awslabs/cloudfront-hosting-toolkit" 23 | }, 24 | "license": "Apache-2.0", 25 | "dependencies": { 26 | "@astrojs/starlight": "^0.29.2", 27 | "astro": "^4.16.10", 28 | "sharp": "^0.33.4", 29 | "shiki": "^1.10.3" 30 | }, 31 | "devDependencies": { 32 | "rimraf": "^5.0.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/public/img/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/public/img/architecture.jpg -------------------------------------------------------------------------------- /docs/public/img/deploy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/public/img/deploy.gif -------------------------------------------------------------------------------- /docs/public/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/public/img/flow.png -------------------------------------------------------------------------------- /docs/public/img/init.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/public/img/init.gif -------------------------------------------------------------------------------- /docs/src/assets/fonts/JetBrainsMonoNerdFont-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/src/assets/fonts/JetBrainsMonoNerdFont-Bold.ttf -------------------------------------------------------------------------------- /docs/src/assets/fonts/JetBrainsMonoNerdFont-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/src/assets/fonts/JetBrainsMonoNerdFont-BoldItalic.ttf -------------------------------------------------------------------------------- /docs/src/assets/fonts/JetBrainsMonoNerdFont-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/src/assets/fonts/JetBrainsMonoNerdFont-Italic.ttf -------------------------------------------------------------------------------- /docs/src/assets/fonts/JetBrainsMonoNerdFont-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/docs/src/assets/fonts/JetBrainsMonoNerdFont-Regular.ttf -------------------------------------------------------------------------------- /docs/src/components/code.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { ExpressiveCode, ExpressiveCodeConfig } from 'expressive-code'; 3 | import { toHtml } from 'hast-util-to-html'; 4 | import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'; 5 | 6 | import fs from 'node:fs/promises'; 7 | 8 | interface Props { 9 | file: string; 10 | language?: string; 11 | meta?: string; 12 | } 13 | 14 | const { file, language, meta } = Astro.props; 15 | const fileNamePath = '../' + file; 16 | const fileEtension = file.split('.').pop() ?? 'js'; 17 | const code = await fs.readFile(fileNamePath, 'utf-8'); 18 | const ec = new ExpressiveCode({ 19 | plugins: [pluginCollapsibleSections()], 20 | }); 21 | 22 | // Get base styles that should be included on the page 23 | // (they are independent of the rendered code blocks) 24 | const baseStyles = await ec.getBaseStyles(); 25 | 26 | // Render some example code to AST 27 | const { renderedGroupAst, styles } = await ec.render({ 28 | code: code, 29 | language: language ?? fileEtension, 30 | meta: `title="${file}"` + (meta ? ` ${meta}` : ''), 31 | }); 32 | 33 | // Convert the rendered AST to HTML 34 | let htmlContent = toHtml(renderedGroupAst); 35 | 36 | // Collect styles and add them before the HTML content 37 | const stylesToPrepend: string[] = []; 38 | stylesToPrepend.push(baseStyles); 39 | stylesToPrepend.push(...styles); 40 | if (stylesToPrepend.length) { 41 | htmlContent = `${htmlContent}`; 42 | } 43 | --- 44 | 45 |
-------------------------------------------------------------------------------- /docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Amazon.com, Inc. or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { defineCollection } from 'astro:content'; 18 | import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'; 19 | 20 | export const collections = { 21 | docs: defineCollection({ schema: docsSchema() }), 22 | i18n: defineCollection({ type: 'data', schema: i18nSchema() }), 23 | }; 24 | -------------------------------------------------------------------------------- /docs/src/content/docs/advanced/bring-your-own-framework.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bring your own framework 3 | --- 4 | 5 | While CloudFront Hosting Toolkit supports many popular frameworks out-of-the-box, you can configure it for custom frameworks. This allows you to create a reusable configuration for your specific framework that can be used across multiple projects. 6 | 7 | ## Steps to Integrate Your Custom Framework 8 | 9 | 1. Locate the CLI installation folder of CloudFront Hosting Toolkit. 10 | 11 | 2. Create two new files and place them in the following subfolders of the CLI installation folder: 12 | - A custom build configuration file named `hosting_YOUR_FRAMEWORK_NAME.yml` in the `resources/build_config_templates` folder. 13 | - A custom CloudFront Function file named `index_YOUR_FRAMEWORK_NAME.js` in the `resources/cff_templates` folder. 14 | 15 | 3. After creating and placing these files, run `cloudfront-hosting-toolkit init` in your project directory and select your custom framework when prompted. 16 | 17 | ## File Naming Convention and Locations 18 | 19 | It's crucial to follow the exact naming format for both files and place them in the correct folders: 20 | 21 | - The build configuration file must be named: `hosting_YOUR_FRAMEWORK_NAME.yml` 22 | Place this file in: `/resources/build_config_templates/` 23 | 24 | - The CloudFront Function file must be named: `index_YOUR_FRAMEWORK_NAME.js` 25 | Place this file in: `/resources/cff_templates/` 26 | 27 | Replace YOUR_FRAMEWORK_NAME with the name of your framework. 28 | 29 | For example: 30 | - For a framework called "MySSG": 31 | - `resources/build_config_templates/hosting_MySSG.yml` 32 | - `resources/cff_templates/index_MySSG.js` 33 | - For a framework called "Custom React": 34 | - `resources/build_config_templates/hosting_Custom_React.yml` 35 | - `resources/cff_templates/index_Custom_React.js` 36 | 37 | Note: The framework name displayed in the init wizard will be taken from the YOUR_FRAMEWORK_NAME part of the filename. For example, if your files are named `hosting_Custom_React.yml` and `index_Custom_React.js`, the framework name displayed will be "Custom React". 38 | 39 | ## Build Configuration File Structure 40 | 41 | When creating your custom build configuration file (`hosting_YOUR_FRAMEWORK_NAME.yml`), keep in mind the following structure and requirements: 42 | 43 | ```yaml 44 | version: 0.2 45 | phases: 46 | build: 47 | commands: 48 | # Your custom build commands go here 49 | - command1 50 | - command2 51 | # ... 52 | # The following command is mandatory and must be the last command 53 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 54 | ``` 55 | 56 | Important notes: 57 | 1. You can include any number of custom build commands to suit your framework's needs. 58 | 2. The last command (the `aws s3 cp` command) is mandatory and must be included exactly as shown above. 59 | 3. `$DEST_BUCKET_NAME` and `$CODEBUILD_RESOLVED_SOURCE_VERSION` are environment variables automatically available in CodeBuild. Do not change these variable names. 60 | 61 | ## Example: Custom Static Site Generator 62 | 63 | Let's create a configuration for a custom static site generator called "MySSG": 64 | 65 | 1. Create `hosting_MySSG.yml` in the `/resources/build_config_templates/` folder: 66 | 67 | ```yaml 68 | version: 0.2 69 | phases: 70 | build: 71 | commands: 72 | - npm install 73 | - npm run generate 74 | - cd dist # Assuming 'dist' is your output directory 75 | # The following command is mandatory 76 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 77 | ``` 78 | 79 | 2. Create `index_MySSG.js` in the `/resources/cff_templates/` folder: 80 | 81 | ```javascript 82 | function handler(event) { 83 | var request = event.request; 84 | var uri = request.uri; 85 | 86 | // Custom URL rewriting logic for MySSG 87 | if (uri.endsWith('/')) { 88 | request.uri += 'index.html'; 89 | } else if (!uri.includes('.')) { 90 | request.uri += '.html'; 91 | } 92 | 93 | return request; 94 | } 95 | ``` 96 | 97 | 3. Run `cloudfront-hosting-toolkit init` in your project directory and select "MySSG" when prompted for the framework. 98 | 99 | 100 | By following these steps and ensuring your build configuration file includes the mandatory S3 copy command, you can create and use custom framework configurations across multiple projects, streamlining your deployment process for unique or in-house frameworks. -------------------------------------------------------------------------------- /docs/src/content/docs/advanced/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced Configuration 3 | --- 4 | 5 | CloudFront Hosting Toolkit offers various advanced configuration options to tailor the deployment process to your specific needs. After running the `init` command, you have the flexibility to customize your deployment before running the `deploy` command. 6 | 7 | ## Configuration Files 8 | 9 | After running `cloudfront-hosting-toolkit init`, three configuration files are created in your project's `cloudfront-hosting-toolkit` folder: 10 | 11 | 1. `cloudfront-hosting-toolkit-config.yml` 12 | 2. `cloudfront-hosting-toolkit-cff.js` 13 | 3. `cloudfront-hosting-toolkit-config.json` 14 | 15 | You can edit these files to customize your deployment before running the `deploy` command. 16 | 17 | ## Customization Process 18 | 19 | 1. **Initial Setup**: Run `cloudfront-hosting-toolkit init` to generate the default configuration files. 20 | 2. **Customization**: Edit the configuration files as needed (optional). 21 | 3. **Deployment**: Run `cloudfront-hosting-toolkit deploy` to deploy your infrastructure using either the default or customized configuration. 22 | 23 | Note: You can deploy using the default configuration and later customize as needed. After making changes to any configuration file, simply run the `deploy` command again to update your infrastructure. 24 | 25 | ## Custom Build Configurations 26 | 27 | Modify the `cloudfront-hosting-toolkit-config.yml` file to customize the build process. This file contains instructions for CodeBuild on how to build your website. 28 | 29 | Example for a React app: 30 | 31 | ```yaml 32 | version: 0.2 33 | phases: 34 | install: 35 | runtime-versions: 36 | nodejs: 18 37 | pre_build: 38 | commands: 39 | - npm install 40 | build: 41 | commands: 42 | - npm run build 43 | artifacts: 44 | base-directory: build 45 | files: 46 | - '**/*' 47 | ``` 48 | 49 | ## CloudFront Function Customization 50 | 51 | The `cloudfront-hosting-toolkit-cff.js` file contains the CloudFront Function code for URL rewriting. Modify this file to implement custom routing logic for your application. 52 | 53 | Example of a basic URL rewriting function: 54 | 55 | ```javascript 56 | function handler(event) { 57 | var request = event.request; 58 | var uri = request.uri; 59 | 60 | // Add custom routing logic here 61 | if (uri.endsWith('/')) { 62 | request.uri += 'index.html'; 63 | } else if (!uri.includes('.')) { 64 | request.uri += '.html'; 65 | } 66 | 67 | return request; 68 | } 69 | ``` 70 | 71 | ## Project Configuration 72 | 73 | The `cloudfront-hosting-toolkit-config.json` file contains your project's configuration settings. You can modify this file to change settings such as the repository URL, branch name, or framework. 74 | 75 | Example: 76 | 77 | ```json 78 | { 79 | "repoUrl": "https://github.com/USERNAME/REPOSITORY.git", 80 | "branchName": "main", 81 | "framework": "react" 82 | } 83 | ``` 84 | 85 | ## Potential Modifications 86 | 87 | Here are some examples of modifications you might make to each configuration file: 88 | 89 | ### 1. cloudfront-hosting-toolkit-config.yml 90 | 91 | This file controls your build process. Some potential modifications include: 92 | 93 | - Changing the Node.js version: 94 | ```yaml 95 | runtime-versions: 96 | nodejs: 16 # Change to a different version if needed 97 | ``` 98 | 99 | - Adding a testing step: 100 | ```yaml 101 | phases: 102 | pre_build: 103 | commands: 104 | - npm install 105 | - npm run test # Add this line to run tests before building 106 | ``` 107 | 108 | - Customizing the artifact output: 109 | ```yaml 110 | artifacts: 111 | base-directory: dist # Change if your build output is in a different folder 112 | files: 113 | - '**/*' 114 | - '!**/*.map' # Exclude source map files 115 | ``` 116 | 117 | ### 2. cloudfront-hosting-toolkit-cff.js 118 | 119 | This file contains your CloudFront Function for URL rewriting. You might modify it to: 120 | 121 | - Handle single-page application (SPA) routing: 122 | ```javascript 123 | function handler(event) { 124 | var request = event.request; 125 | var uri = request.uri; 126 | 127 | // Route all requests to index.html for SPA 128 | if (!uri.includes('.')) { 129 | request.uri = '/index.html'; 130 | } 131 | 132 | return request; 133 | } 134 | ``` 135 | 136 | - Implement custom error page routing: 137 | ```javascript 138 | function handler(event) { 139 | var request = event.request; 140 | var uri = request.uri; 141 | 142 | if (uri === '/404') { 143 | request.uri = '/custom-404.html'; 144 | } else if (uri === '/500') { 145 | request.uri = '/custom-500.html'; 146 | } 147 | 148 | return request; 149 | } 150 | ``` 151 | 152 | ### 3. cloudfront-hosting-toolkit-config.json 153 | 154 | This file contains your project configuration. Possible modifications include: 155 | 156 | - Changing the deployment branch: 157 | ```json 158 | { 159 | "repoUrl": "https://github.com/USERNAME/REPOSITORY.git", 160 | "branchName": "develop", // Change to deploy from a different branch 161 | "framework": "react" 162 | } 163 | ``` 164 | 165 | - Adding a custom domain: 166 | ```json 167 | { 168 | "repoUrl": "https://github.com/USERNAME/REPOSITORY.git", 169 | "branchName": "main", 170 | "framework": "react", 171 | "domainName": "www.example.com", // Add your custom domain 172 | "hostedZoneId": "Z1234567890ABC" // Add your Route 53 hosted zone ID 173 | } 174 | ``` 175 | 176 | Remember, you can always revert to the default configuration by re-running the `init` command, but this will overwrite any existing customizations. Always backup your custom configurations before reinitializing. -------------------------------------------------------------------------------- /docs/src/content/docs/architecture/github-workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub Workflow 3 | --- 4 | 5 | The GitHub workflow in CloudFront Hosting Toolkit provides a seamless integration between your GitHub repository and AWS deployment infrastructure. 6 | 7 | ## Process Overview 8 | 1. **Source Stage**: Code changes pushed to the GitHub repository trigger AWS CodePipeline. 9 | 2. **Build Stage**: CodeBuild compiles the code and creates deployment artifacts using the buildspec YAML. 10 | 3. **Deploy Stage**: 11 | - Artifacts are uploaded to the hosting S3 bucket in a new folder (identified by the commit ID). 12 | - A Step Function updates the DynamoDB Key-Value Store with the new folder information. 13 | - The CloudFront Function is updated to route traffic to the new folder. 14 | 15 | ## Benefits 16 | - Automated deployments triggered by code pushes 17 | - Version control integration 18 | - Consistent build and deployment process 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/src/content/docs/architecture/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture Overview 3 | --- 4 | 5 | CloudFront Hosting Toolkit leverages several AWS services to create a robust, scalable, and efficient hosting infrastructure for your web applications. The architecture is designed to support both GitHub-based and S3-based source code repositories, ensuring flexibility in your deployment workflow. 6 | 7 | CloudFront Hosting Toolkit Architecture Diagram 8 | 9 | ## Key Components 10 | 11 | 1. **Source Code Management**: 12 | - **GitHub Repository**: For Git-based workflows. 13 | - **Amazon S3 (Source Code Repository)**: For S3-based workflows, allowing ZIP file uploads. 14 | 15 | 2. **Deployment Pipeline**: 16 | - **AWS CodePipeline**: Manages the overall deployment process, orchestrating the flow from source to production. 17 | - **AWS CodeBuild**: Handles the build process for your web application, compiling code and creating artifacts. 18 | - **Amazon S3 (Artifacts)**: Stores deployment artifacts during the pipeline process. 19 | 20 | 3. **Hosting Infrastructure**: 21 | - **Amazon S3 (Static files)**: Stores your website files for serving. 22 | - **Amazon CloudFront**: Serves as the content delivery network (CDN) for your website, ensuring fast global access. 23 | - **CloudFront Function**: Handles request routing to serve the latest version of your site. 24 | - **Amazon Route 53**: Manages DNS routing for your custom domain (if configured). 25 | 26 | 4. **Security and SSL**: 27 | - **AWS Certificate Manager (ACM)**: Provisions and manages SSL/TLS certificates for secure HTTPS connections. 28 | 29 | 5. **Deployment Orchestration**: 30 | - **AWS Step Functions**: Orchestrates the deployment process, ensuring proper sequencing of tasks. 31 | - **AWS Lambda**: Triggers the pipeline for S3-based deployments and potentially other serverless operations. 32 | 33 | 6. **State Management**: 34 | - **Key Value Store (DynamoDB)**: Stores routing information to direct traffic to the latest deployed version. 35 | 36 | ## Workflow Overview 37 | 38 | 1. **Code Push/Upload**: 39 | - Developers push code to GitHub or upload a ZIP file to the S3 source code repository. 40 | 41 | 2. **Pipeline Triggering**: 42 | - GitHub pushes or S3 uploads trigger the AWS CodePipeline, either directly or via AWS Lambda. 43 | 44 | 3. **Build and Artifact Creation**: 45 | - AWS CodeBuild compiles the code and creates deployment artifacts. 46 | - Artifacts are stored in an S3 bucket. 47 | 48 | 4. **Deployment**: 49 | - AWS Step Functions orchestrate the deployment process. 50 | - Static files are copied to the hosting S3 bucket. 51 | - The Key Value Store is updated with the latest deployment information. 52 | 53 | 5. **Content Delivery**: 54 | - CloudFront serves the website content globally. 55 | - CloudFront Functions use the Key Value Store to route requests to the latest version. 56 | - Amazon Route 53 handles DNS routing for custom domains. 57 | 58 | 6. **Security**: 59 | - AWS Certificate Manager provides SSL/TLS certificates for secure connections. 60 | 61 | This architecture ensures rapid, consistent deployments, optimal performance, and high availability for your web applications. It leverages AWS's global infrastructure to deliver content quickly to users worldwide while maintaining the flexibility to deploy from various source repositories. -------------------------------------------------------------------------------- /docs/src/content/docs/architecture/s3-workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: S3 Workflow 3 | --- 4 | The S3 workflow in CloudFront Hosting Toolkit allows for deployments from an S3 bucket, useful for pre-built artifacts or when GitHub integration is not needed. 5 | 6 | ## Process Overview 7 | 1. **Source Stage**: Uploading a ZIP file to a specified S3 bucket triggers AWS CodePipeline. 8 | 2. **Build Stage**: The ZIP file is copied from the source S3 bucket, unzipped, and files are copied to the hosting S3 bucket. 9 | 3. **Deploy Stage**: Similar to the GitHub process, updating the Key-Value Store and CloudFront Function. 10 | 11 | ## Benefits 12 | - Suitable for pre-built or static websites 13 | - Flexibility in deployment source 14 | - Consistent deployment process with GitHub workflow -------------------------------------------------------------------------------- /docs/src/content/docs/features/custom-domains.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom domain support 3 | --- 4 | 5 | The CloudFront Hosting Toolkit supports custom domain configuration for both Amazon Route 53 users and those using other DNS providers. This flexibility ensures that regardless of your DNS management solution, you can easily set up a custom domain for your CloudFront-hosted website. 6 | 7 | ## Route 53 Users vs. Non-Route 53 Users 8 | 9 | 1. **Route 53 Users** 10 | - Benefit from a more automated process 11 | - The CLI can directly interact with your Route 53 hosted zones 12 | - Automatic creation of necessary DNS records 13 | 14 | 2. **Non-Route 53 Users** 15 | - Receive guided instructions for manual DNS configuration 16 | - Need to create DNS records with their respective DNS providers 17 | - Still benefit from automated certificate management and CloudFront configuration 18 | 19 | ## The Process 20 | 21 | Regardless of your DNS provider, the CLI handles the following steps: 22 | 23 | 1. **SSL/TLS Certificate Management** 24 | - Checks for an existing certificate in AWS Certificate Manager (ACM) 25 | - Creates a new certificate if one doesn't exist 26 | - Waits for the certificate to be issued and validated 27 | 28 | 2. **CloudFront Distribution Configuration** 29 | - Integrates your custom domain with the CloudFront distribution 30 | 31 | 3. **DNS Configuration** 32 | - This step differs based on your DNS provider: 33 | 34 | ### For Route 53 Users: 35 | 36 | 1. The CLI detects that you're using Route 53 based on the provided hosted zone ID 37 | 2. After deploying the CloudFront distribution, it checks if a CNAME record already exists 38 | 3. If no record exists, it prompts you to confirm the creation of a new CNAME record 39 | 4. Upon confirmation, it automatically creates the CNAME record in your Route 53 hosted zone 40 | 41 | ### For Non-Route 53 Users: 42 | 43 | 1. The CLI recognizes that you're not using Route 53 (no hosted zone ID provided) 44 | 2. After deploying the CloudFront distribution, it provides detailed instructions for manual DNS configuration 45 | 3. You receive step-by-step guidance on how to create a CNAME record with your DNS provider, including: 46 | - The record type (CNAME) 47 | - The host name (your custom domain) 48 | - The target (the CloudFront distribution domain) 49 | 50 | ## User Interaction 51 | 52 | - **Route 53 Users**: You'll be prompted to confirm the creation of the DNS record. The process is largely automated after your confirmation. 53 | - **Non-Route 53 Users**: You'll need to manually create the DNS record with your provider using the instructions provided by the CLI. 54 | 55 | In both cases, the CLI handles the complex tasks of certificate management and CloudFront configuration, simplifying the process of setting up a custom domain for your static website. 56 | 57 | ## Benefits 58 | 59 | - Supports users of all DNS providers 60 | - Automates certificate management and CloudFront configuration 61 | - Provides a guided experience for non-Route 53 users 62 | - Offers a near-fully automated process for Route 53 users 63 | 64 | By accommodating both Route 53 and non-Route 53 users, the CloudFront Hosting Toolkit ensures a smooth custom domain setup process, regardless of your DNS management solution. -------------------------------------------------------------------------------- /docs/src/content/docs/features/github-integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub Integration 3 | --- 4 | 5 | The GitHub Integration feature of CloudFront Hosting Toolkit provides seamless connectivity between your GitHub repository and your AWS hosting infrastructure, enabling efficient version control and collaborative development. 6 | 7 | ## Key Features 8 | 9 | - **Automatic deployments**: Triggers deployments automatically when code is pushed to the specified branch. 10 | - **Branch-based deployments**: Supports deployments from different branches for staging and production environments. 11 | - **Simplified collaboration**: Enables team members to contribute to the project using familiar GitHub workflows. 12 | - **Secure connection**: Utilizes AWS CodeStar connections for secure, OAuth-based authentication with GitHub. 13 | 14 | ## How It Works 15 | 16 | 1. **Repository connection**: During setup, you provide your GitHub repository details. 17 | 2. **AWS CodeStar Connection**: The toolkit creates an AWS CodeStar connection to your GitHub repository. 18 | - This connection uses OAuth to securely authenticate with GitHub. 19 | - You'll need to authorize the AWS Connector for GitHub app during the setup process. 20 | 3. **Repository access**: You can select specific repositories to make accessible to CodePipeline. 21 | 4. **Automated pipeline**: When code is pushed, it triggers the AWS CodePipeline to start the deployment process. 22 | 23 | ## Connection Setup Process 24 | 25 | 1. **Initiate connection**: The toolkit initiates the creation of a CodeStar connection to GitHub. 26 | 2. **Authorization**: You'll be prompted to authorize the AWS Connector for GitHub app. 27 | 3. **App installation**: If not already installed, you'll need to install the AWS Connector for GitHub app for your account or organization. 28 | 4. **Repository selection**: Choose the specific repositories you want to make accessible to AWS. 29 | 5. **Connection completion**: Once authorized and configured, the connection becomes active, enabling CodePipeline to access your GitHub repository. 30 | 31 | ## Benefits 32 | 33 | - **Streamlined workflow**: Integrates directly with your existing Git-based development process. 34 | - **Security**: OAuth-based authentication ensures secure access without the need for personal access tokens. 35 | 36 | > **Important** 37 | > 38 | > - To create the connection, you must be the GitHub organization owner. For repositories not under an organization, you must be the repository owner. 39 | > - The GitHub Integration feature is not available in certain AWS regions. Check the AWS documentation for the latest information on regional availability. 40 | > - If your CodePipeline service role was created before December 18, 2019, you might need to update its permissions to use `codestar-connections:UseConnection` for AWS CodeStar connections. 41 | 42 | GitHub Integration bridges the gap between your development workflow and your hosting infrastructure, providing a cohesive and efficient deployment process that aligns with modern development practices while maintaining security and ease of use. -------------------------------------------------------------------------------- /docs/src/content/docs/features/instant-deployment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Instant deployment 3 | --- 4 | 5 | The Instant Deployment feature of CloudFront Hosting Toolkit enables rapid and efficient updates to your live website, significantly reducing deployment times and accelerating your development cycle. 6 | 7 | ## Key Features 8 | 9 | - **Automated Deployment Pipeline**: Leverages AWS CodePipeline for a streamlined deployment process. 10 | - **Quick Updates**: Changes are reflected on your live site within minutes of pushing code. 11 | - **Versioned Deployments**: Each deployment creates a new version in a separate S3 folder. 12 | - **Atomic Updates**: Ensures all users see the new version, never a mix of old and new version. 13 | - **No Cache Invalidation Required**: CloudFront Function redirects requests to the new folder in S3. 14 | 15 | ## How It Works 16 | 17 | 1. **Code Push**: You push your changes to the configured Git repository or upload a new ZIP file to S3. 18 | 2. **Pipeline Trigger**: This action automatically triggers the AWS CodePipeline. 19 | 3. **Build Process**: AWS CodeBuild compiles your code and creates deployment artifacts. 20 | 4. **Artifact Upload**: The built artifacts are uploaded to a new folder in the hosting S3 bucket. 21 | 5. **Key-Value Store Update**: The commit ID of the new deployment is stored in a Key-Value Store (KVS). 22 | 6. **Traffic Routing**: A CloudFront Function checks the KVS for the latest commit ID and routes traffic to the corresponding S3 folder. 23 | 24 | ## Benefits 25 | 26 | - **Instant availability**: New versions of your website are available immediately after deployment. 27 | - **Consistent user experience**: All users see the same version of the site at any given time. 28 | - **No mixed content**: Eliminates the risk of serving a mixture of old and new version files to users. 29 | - **Efficient resource usage**: No need for cache invalidation. 30 | 31 | 32 | Instant Deployment streamlines your development workflow, allowing you to focus on building features rather than managing complex deployment processes. By leveraging CloudFront Functions and Key-Value Store, it ensures that your users always see a consistent, up-to-date version of your website without the need for cache invalidation or concerns about mixed content during deployments. -------------------------------------------------------------------------------- /docs/src/content/docs/features/optimized-caching.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Optimized caching 3 | --- 4 | 5 | The Optimized Caching feature of CloudFront Hosting Toolkit leverages Amazon CloudFront's powerful caching capabilities to enhance your website's performance, ensuring faster response times and reduced server load. 6 | 7 | ## Key Features 8 | 9 | - **Specialized Cache Policies**: Implements distinct caching strategies for different types of content. 10 | - **Long-Term Caching**: Utilizes extended cache durations to maximize performance benefits. 11 | - **Compression Support**: Enables Gzip and Brotli compression for compatible content. 12 | 13 | ## How It Works 14 | 15 | ### Cache Policies 16 | 17 | 1. **Default Cache Policy**: 18 | - Applied to most content types 19 | - Cache duration: 365 days (1 year) 20 | - Ignores cookies and query strings 21 | - Enables Gzip and Brotli compression 22 | 23 | 2. **Images Cache Policy**: 24 | - Specifically for image files (jpg, jpeg, png, gif, bmp, tiff, ico) 25 | - Cache duration: 365 days (1 year) 26 | - Ignores cookies, headers, and query strings 27 | 28 | 3. **Static Assets Cache Policy**: 29 | - Applied to js, css, and html files 30 | - Cache duration: 365 days (1 year) 31 | - Ignores cookies, headers, and query strings 32 | - Enables compression 33 | 34 | ## Benefits 35 | 36 | - **Improved Load Times**: Long-term caching ensures faster content delivery for returning visitors. 37 | - **Reduced Origin Load**: Extended cache durations minimize requests to the origin S3 bucket. 38 | - **Optimized for Different Content Types**: Specialized policies for images and static assets ensure appropriate handling. 39 | - **Reduced Bandwidth Usage**: Compression and efficient caching reduce data transfer. 40 | 41 | The Optimized Caching feature ensures that your website delivers exceptional performance globally, enhancing user experience and reducing infrastructure load. -------------------------------------------------------------------------------- /docs/src/content/docs/features/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | --- 4 | 5 | CloudFront Hosting Toolkit offers a comprehensive set of features designed to simplify and enhance your frontend deployment process. Each feature is crafted to address specific aspects of modern web application hosting and deployment. 6 | 7 | Our key features include: 8 | 9 | 1. [Self-paced Setup Wizard](/features/setup-wizard) 10 | 2. [Instant Deployment](/features/instant-deployment) 11 | 3. [GitHub Integration](/features/github-integration) 12 | 4. [Optimized Caching](/features/optimized-caching) 13 | 5. [Enhanced Security Headers](/features/security-headers) 14 | 6. [Custom Domain Support](/features/custom-domains) 15 | 7. [SSL/TLS Management](/features/ssl-tls-management) 16 | 17 | Each of these features is designed to work seamlessly together, providing a robust and efficient hosting solution for your frontend applications. Click on any feature to learn more about its capabilities and how it can benefit your development workflow. -------------------------------------------------------------------------------- /docs/src/content/docs/features/security-headers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Security headers 3 | --- 4 | 5 | The CloudFront Hosting Toolkit implements a comprehensive set of security headers to enhance the protection of your website against common web vulnerabilities. 6 | 7 | ## Key Features 8 | 9 | - **Custom Response Headers Policy**: Applies a set of security headers to all responses from CloudFront. 10 | - **Comprehensive Protection**: Addresses multiple security concerns with a single configuration. 11 | 12 | ## Implemented Security Headers 13 | 14 | The following security headers are implemented through a custom Response Headers Policy: 15 | 16 | 1. **Content-Type Options**: 17 | - Prevents MIME type sniffing. 18 | - Helps protect against MIME confusion attacks. 19 | 20 | 2. **Frame Options**: 21 | - Set to DENY. 22 | - Prevents your content from being embedded in iframes on other domains. 23 | - Protects against clickjacking attacks. 24 | 25 | 3. **Strict Transport Security (HSTS)**: 26 | - Enforces HTTPS connections. 27 | - Includes subdomains. 28 | - Set for a duration of one year (31,536,000 seconds). 29 | - Enhances protection against protocol downgrade attacks and cookie hijacking. 30 | 31 | 4. **XSS Protection**: 32 | - Enables the browser's built-in XSS protection. 33 | - Set to block mode. 34 | - Provides an additional layer of protection against Cross-Site Scripting (XSS) attacks. 35 | 36 | 5. **Referrer Policy**: 37 | - Set to STRICT_ORIGIN_WHEN_CROSS_ORIGIN. 38 | - Controls the Referer header sent by the browser. 39 | - Balances security and functionality by sending the origin, path, and query string when performing a same-origin request, and only the origin when performing a cross-origin request. 40 | 41 | ## Benefits 42 | 43 | - **Enhanced Security Posture**: Protects against various common web vulnerabilities. 44 | - **Browser Compatibility**: Implemented headers are widely supported by modern browsers. 45 | - **Centralized Configuration**: Applied at the CloudFront level, ensuring consistent security across all resources. 46 | - **Compliance Support**: Helps meet security requirements for various compliance standards. 47 | 48 | These security headers work in conjunction with other security features of the CloudFront Hosting Toolkit to provide a robust defense for your web application. -------------------------------------------------------------------------------- /docs/src/content/docs/features/setup-wizard.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Self-paced Setup Wizard 3 | --- 4 | 5 | The Self-paced setup wizard is a feature of CloudFront Hosting Toolkit that allows you to quickly deploy your website without needing in-depth knowledge of individual AWS services. It guides you through the process, handling the complexities of AWS infrastructure setup behind the scenes. 6 | 7 | 8 | ## Key Benefits 9 | 10 | - **Intuitive command-line interface**: Easy-to-follow prompts guide you through each step of the setup process. 11 | - **Automatic project detection**: The wizard automatically detects your project settings, including framework and repository details. 12 | - **Customizable configuration**: While providing smart defaults, the wizard allows you to customize every aspect of your setup. 13 | - **AWS resource setup**: Guides you through the process of setting up necessary AWS resources, ensuring proper configuration. 14 | 15 | ## How It Works 16 | 17 | 1. **Initiation**: Run `cloudfront-hosting-toolkit init` to start the wizard. 18 | 2. **Project analysis**: The wizard scans your project directory to detect the framework, repository, and other relevant details. 19 | 3. **Guided configuration**: You're prompted to confirm or modify detected settings and provide additional information as needed. 20 | 5. **Configuration save**: All settings are saved to a configuration file. 21 | 22 | ## Best Practices 23 | 24 | - Run the wizard in your project's root directory for the most accurate automatic detection. 25 | - Review each step carefully to ensure all settings align with your project requirements. 26 | 27 | The Self-paced setup wizard simplifies the often complex process of configuring a cloud hosting environment, allowing you to get your project up and running quickly and efficiently. -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/how-it-works.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How it works 3 | --- 4 | 5 | CloudFront Hosting Toolkit simplifies the process of deploying and managing frontend applications on AWS. The toolkit operates in three main steps: 6 | 7 | 1. **Initialization**: Set up your project and configure the toolkit. 8 | 2. **Deployment**: Deploy your frontend application infrastructure to AWS. 9 | 3. **Updates**: Automatic updates and content delivery after deployment. 10 | 11 | ## Re:Invent 2024 lightning talk 12 | 13 |

14 | 15 | AWS re:Invent 2024 - CloudFront Hosting Toolkit lightning talk 16 | 17 |

18 | 19 | Learn how to leverage CloudFront Hosting Toolkit for deploying secure and fast frontends using Git-based workflows while maintaining full control over your AWS resources. 20 | 21 | 22 | ## Initialization 23 | 24 | When you run `cloudfront-hosting-toolkit init`: 25 | 26 | 1. The CLI detects your project's configuration, including GitHub repository details (if applicable). 27 | 2. It guides you through a setup process, allowing you to confirm or override detected information. 28 | 3. The command creates configuration files in a `cloudfront-hosting-toolkit` folder. 29 | 30 | ## Deployment 31 | 32 | When you run `cloudfront-hosting-toolkit deploy`: 33 | 34 | 1. The toolkit deploys the hosting infrastructure on AWS, including: 35 | - Amazon S3 bucket for storing your website files 36 | - Amazon CloudFront distribution for content delivery 37 | - AWS CodePipeline for continuous deployment 38 | - AWS CodeBuild project for building your application 39 | - CloudFront Functions for request handling and routing 40 | - Key-Value Store (KVS) for storing the latest deployment information 41 | 42 | 2. Once the infrastructure is set up, the toolkit triggers the initial deployment of your website content. 43 | 44 | ## Updates 45 | 46 | After the initial deployment, the update process and content delivery work as follows: 47 | 48 | CloudFront Hosting Toolkit Flow 49 | 50 | 51 | 1. A developer pushes code changes to the GitHub repository, triggering the AWS CodePipeline (steps 4-5). 52 | 53 | 2. CodePipeline fetches the new code and builds the new website version (step 5). 54 | 55 | 3. The built artifacts are uploaded to a new folder in the S3 bucket, identified by the commit ID (step 6). 56 | 57 | 4. The Key-Value Store is updated with the new commit ID as the current served website version (step 7). 58 | 59 | 5. When an end-user requests the website (step 1): 60 | - The request hits Amazon CloudFront. 61 | - CloudFront executes a function that fetches the latest build ID from the Key-Value Store (step 2). 62 | - The function rewrites the URL to include the latest build ID and adds it to the cache key. 63 | - CloudFront then requests the content from the correct S3 folder using the rewritten URL (step 3). 64 | - The content is served to the user with a 200 OK status. 65 | 66 | 6. For subsequent requests (step 8), the process repeats, ensuring that users always see the latest version of the website. 67 | 68 | This approach enables atomic deployments, providing several key benefits: 69 | 70 | - All requests are immediately directed to the new version once it's deployed. 71 | - There's no risk of users seeing a mix of old and new content. 72 | - The transition is seamless and instantaneous for all users globally. 73 | - It eliminates the "partial update" problem, ensuring consistency across your entire user base. 74 | - It prevents potential issues that could arise from inconsistent state between old and new versions. 75 | 76 | By leveraging CloudFront's global content delivery network and this intelligent routing mechanism, visitors always see the most recent version of your website, regardless of their location or when they accessed the site. This ensures a consistent, up-to-date experience for all users while benefiting from the performance advantages of a CDN. -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | CloudFront Hosting Toolkit is an open-source command-line tool designed to simplify the deployment and management of frontend applications on AWS. It provides a seamless way to set up and maintain a robust, scalable, and secure hosting infrastructure for your web applications. 6 | 7 | Key benefits: 8 | - Simplified AWS resource management 9 | - Automated deployment pipelines 10 | - Optimized content delivery through CloudFront 11 | - Built-in security features 12 | - Flexible configuration options 13 | 14 | Whether you're a solo developer or part of a large team, CloudFront Hosting Toolkit can streamline your frontend deployment process and help you focus on what matters most - building great web applications. 15 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quickstart 3 | --- 4 | 5 | Get up and running with CloudFront Hosting Toolkit in just a few minutes: 6 | 7 | ## Prerequisites 8 | - Node.js 18+ 9 | - AWS CLI 2+ installed and configured 10 | - A GitHub account (if deploying from GitHub) 11 | 12 | ## Installation 13 | Install CloudFront Hosting Toolkit globally: 14 | 15 | ```bash 16 | npm install -g @aws/cloudfront-hosting-toolkit 17 | ``` 18 | 19 | ## Deployment Steps 20 | 1. Navigate to your project directory: 21 | ```bash 22 | cd /path/to/your/project 23 | ``` 24 | 25 | 2. Initialize your deployment configuration: 26 | ```bash 27 | cloudfront-hosting-toolkit init 28 | ``` 29 | Follow the prompts to configure your deployment. 30 | 31 | 3. Deploy your website: 32 | ```bash 33 | cloudfront-hosting-toolkit deploy 34 | ``` 35 | 36 | 4. Once deployment is complete, you'll receive a CloudFront domain name. Use this to access your deployed website. 37 | -------------------------------------------------------------------------------- /docs/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CloudFront Hosting Toolkit 3 | description: Self-managed Frontend Hosting on AWS 4 | template: splash 5 | hero: 6 | tagline: Effortlessly deploy and manage fast, secure frontends in the cloud 🚀☁️ 7 | actions: 8 | - text: How it works 9 | link: /cloudfront-hosting-toolkit/getting-started/how-it-works 10 | icon: right-arrow 11 | variant: primary 12 | - text: GitHub Repository 13 | link: https://github.com/awslabs/cloudfront-hosting-toolkit 14 | icon: external 15 | - text: NPM Package 16 | link: https://www.npmjs.com/package/@aws/cloudfront-hosting-toolkit 17 | icon: external 18 | --- 19 | 20 | import { Card, CardGrid } from '@astrojs/starlight/components'; 21 | 22 | ## Key Features 23 | 24 | 25 | - **Self-paced wizard for easy setup**: Our self-paced setup wizard guides you through the installation process step by step, making it effortless to get started with our project. 26 | - **Instant deployment for quick results**: Experience rapid deployment of your project changes, reducing waiting times and accelerating your development cycle. The solution automatically clears the cache of the previous version. 27 | - **Seamless GitHub integration for version control**: Integrate your project seamlessly with GitHub, enabling efficient version control and collaboration with your team. 28 | - **Optimized caching for improved performance**: We've implemented advanced caching mechanisms to enhance your project's performance, ensuring faster response times and reduced server load. 29 | - **Enhanced security headers for protection**: Your project benefits from enhanced security headers to safeguard against potential security vulnerabilities, helping protect your users and data. 30 | - **Custom domain name support with TLS certificate**: Easily configure custom domain names for your project and secure them with TLS certificates, ensuring a professional and secure online presence. 31 | 32 | 33 | 34 | 35 | 36 | Get your website deployed in minutes: 37 | See our [Quick Start Guide](/cloudfront-hosting-toolkit/getting-started/quickstart) for step-by-step instructions. 38 | 39 | 40 | Works with popular frontend frameworks: 41 | - React 42 | - Vue.js 43 | - Angular 44 | - Next.js 45 | - [Bring your own framework](/cloudfront-hosting-toolkit/advanced/bring-your-own-framework) 46 | 47 | 48 | Ensure consistent user experience: 49 | - Instant updates 50 | - No mixed content during deployments 51 | 52 | Learn more about our [deployment process](/cloudfront-hosting-toolkit/features/instant-deployment). 53 | 54 | 55 | Seamlessly integrate with your infrastructure as code: 56 | - Use as a CDK construct 57 | - Customize and extend functionality 58 | 59 | See how to [integrate with CDK](/cloudfront-hosting-toolkit/user-guide/cdk-guide). 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/src/content/docs/project/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FAQ 3 | --- 4 | 5 | 6 | ##### I've successfully deployed my website using the tool, and now I'd like to configure a custom domain name. What are the steps I should follow? 7 | 8 | To use a domain name for your deployed website, follow these steps: 9 | 10 | Run the init command again to initiate the setup process. 11 | 12 | During the setup process, select the option that allows you to specify your desired domain name. 13 | 14 | Once you've made the desired domain name selection, proceed with the deployment by running the deploy command. 15 |
16 | 17 | --- 18 | 19 | ##### Is it possible to deploy a website that was built without utilizing any frontend framework using this tool? 20 | 21 | Yes, you can. The tool is designed to be versatile and adaptable to your specific needs. If you've built your website without utilizing any frontend framework, our tool will automatically detect that no framework is in use. As a result, there won't be any build step required. 22 | 23 |
24 | 25 | --- 26 | 27 | ##### What is CloudFront Hosting Toolkit? 28 | 29 | CloudFront Hosting Toolkit is an open-source command-line tool designed for deploying and managing frontend applications on AWS. It simplifies the process of setting up and maintaining a robust, scalable hosting infrastructure using services like CloudFront, S3, and CodePipeline. 30 | 31 |
32 | 33 | --- 34 | 35 | ##### Who is CloudFront Hosting Toolkit for? 36 | 37 | CloudFront Hosting Toolkit is primarily designed for developers and teams working on frontend projects who want to leverage AWS services for hosting. It's particularly useful for those who need a streamlined deployment process, want to take advantage of CloudFront's global content delivery network, and require features like custom domains and SSL/TLS management. 38 | 39 |
40 | 41 | --- 42 | 43 | ##### What types of projects are supported? 44 | 45 | The toolkit supports a wide range of frontend projects, including: 46 | - Single-page applications (SPAs) 47 | - Static websites 48 | - Projects built with popular frameworks like React, Angular, and Vue.js 49 | - Custom or less common frameworks (via the [Bring your own framework](/cloudfront-hosting-toolkit/advanced/bring-your-own-framework) feature) 50 | 51 | Additionally, you can easily customize the build and deployment process to suit your specific project requirements. 52 | 53 |
54 | 55 | --- 56 | 57 | ##### Is custom domain support available? 58 | 59 | Yes, CloudFront Hosting Toolkit provides built-in support for custom domains. During the initialization process, you can specify your custom domain, and the toolkit will: 60 | 61 | 1. Configure your CloudFront distribution to use the custom domain. 62 | 2. Automatically provision and associate an SSL/TLS certificate using AWS Certificate Manager. 63 | 3. Provide guidance on setting up the necessary DNS records. 64 | 65 | This feature allows you to use your own branded domain while still benefiting from CloudFront's global content delivery network. 66 | 67 |
68 | 69 | --- 70 | 71 | ##### Is it necessary to acquire a domain name in advance or before deploying my website? 72 | 73 | Yes, it's advisable to acquire your desired domain name before configuring it for your website if you want to associate a custom domain name with your website. You have the flexibility to purchase the domain name either through your AWS account or from any other DNS provider of your choice. This ensures that you have ownership and control over the domain name as you proceed with the website configuration. 74 |
75 | 76 | --- 77 | 78 | ##### Can I use the toolkit with existing AWS resources? 79 | 80 | While CloudFront Hosting Toolkit is designed to set up a complete hosting infrastructure, it also offers flexibility for integration with existing AWS resources: 81 | 82 | - You can use an existing S3 bucket for deployments by specifying it during the initialization process. 83 | - For more advanced scenarios, you can use the toolkit's [CDK constructs](/cloudfront-hosting-toolkit/user-guide/cdk-guide) to integrate with your existing AWS CDK stacks. 84 | 85 |
86 | 87 | --- 88 | 89 | ##### Is continuous deployment supported? 90 | 91 | Yes, CloudFront Hosting Toolkit supports continuous deployment when using [GitHub](/cloudfront-hosting-toolkit/architecture/github-workflow) as your source repository. Each push to your configured branch will automatically trigger a new deployment through the AWS CodePipeline set up by the toolkit. 92 | 93 | For [S3-based deployments](/cloudfront-hosting-toolkit/architecture/s3-workflow), you can achieve continuous deployment by integrating the toolkit's commands into your existing CI/CD processes. 94 | 95 |
96 | 97 | --- 98 | 99 | ##### How can I contribute to the CloudFront Hosting Toolkit project? 100 | 101 | Contributions to CloudFront Hosting Toolkit are welcome! You can contribute in several ways: 102 | 103 | 1. Fork the [CloudFront Hosting Toolkit repository](https://github.com/awslabs/cloudfront-hosting-toolkit) on GitHub and submit pull requests for new features or bug fixes. 104 | 2. Report issues or suggest features using the GitHub issue tracker. 105 | 3. Improve the documentation by submitting updates or clarifications. 106 | 4. Share your experiences and help other users in the project's discussion forums. 107 | 108 | Before contributing, please review the project's contribution guidelines and code of conduct in the repository. 109 | 110 |
111 | 112 | --- 113 | 114 | ##### Can I use the toolkit for backend deployments? 115 | 116 | CloudFront Hosting Toolkit is primarily designed for frontend deployments. However, the toolkit's [CDK constructs](/cloudfront-hosting-toolkit/user-guide/cdk-construct) can be integrated into a broader CDK stack that includes both frontend and backend resources 117 | -------------------------------------------------------------------------------- /docs/src/content/docs/troubleshooting/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Troubleshooting guide 3 | --- 4 | 5 | If you encounter issues while using CloudFront Hosting Toolkit, follow this guide to diagnose and resolve common problems. 6 | 7 | ## 1. General debugging steps 8 | 9 | 1. Use `cloudfront-hosting-toolkit status` to check the current state of your deployment. 10 | 2. Review the logs in the `cloudfront-hosting-toolkit` folder (named with the format `YYYY-MM-DD_HH-MM-SS.log`) for detailed operation logs. 11 | 3. Use AWS Management Console to inspect the state of individual resources (S3, CloudFront, CodePipeline, CodeBuild). 12 | 13 | ## 2. Deployment fails 14 | 15 | - **Check AWS Credentials**: Ensure your AWS CLI is correctly configured with the right permissions. 16 | - **Verify Build Configuration**: Ensure your `cloudfront-hosting-toolkit-config.yml` is correctly formatted and contains all necessary commands. 17 | - **Review CodeBuild Logs**: Check the CodeBuild project in AWS Console for specific build failure reasons. 18 | - **GitHub Connection Issues**: For GitHub-based deployments, ensure the AWS CodeStar connection is properly set up and authorized. 19 | - **Project Structure and Framework Compatibility**: Verify that your project structure is correct and compatible with the chosen framework. If the framework specified in the JSON file is incorrect, you can: 20 | - Manually change it in the `cloudfront-hosting-toolkit-config.json` file to one of the supported values. 21 | - Customize the build configuration in `cloudfront-hosting-toolkit-config.yml` to use the correct commands for building your website. 22 | 23 | ## 3. Website not updating after deployment 24 | 25 | - **Review CodeBuild Logs**: Often, the issue lies in the build configuration. Inspect the CodeBuild logs in the AWS Console to understand if the build process completed successfully and if all expected files were generated. 26 | - **Verify Build Configuration**: Ensure your `cloudfront-hosting-toolkit-config.yml` is correctly configured for your framework and project structure. Pay special attention to build commands and output directories. 27 | - **Review CloudFront Function**: Ensure the function is correctly routing to the new folder. Check the function logs in CloudFront console. 28 | - **Check Key-Value Store (KVS)**: Verify that the KVS was updated with the new deployment information (commit ID). You can check this in the CloudFront console under the Functions section. 29 | 30 | ## 4. Custom domain issues 31 | 32 | - **Certificate Validation**: If using a custom domain, ensure the SSL/TLS certificate is properly validated in AWS Certificate Manager. 33 | - **DNS Configuration**: Verify that your domain's DNS settings are correctly pointing to the CloudFront distribution. 34 | 35 | 36 | ## Getting Help 37 | 38 | - **Documentation**: Refer to our comprehensive documentation for detailed information on each feature and process. 39 | - **GitHub Issues**: Check existing issues or create a new one on our [GitHub repository](https://github.com/awslabs/cloudfront-hosting-toolkit). 40 | 41 | -------------------------------------------------------------------------------- /docs/src/content/docs/user-guide/cdk-configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CDK Configuration guide 3 | --- 4 | 5 | Both the CDK Construct and CDK Source Code methods require some common configuration steps. Follow this guide to set up the necessary files. 6 | 7 | ## Configuration Files 8 | 9 | Create a `cloudfront-hosting-toolkit` folder at the root of your CDK project and add the following files: 10 | 11 | 1. `buildConfigurationFile.yml`: Configures the build process for your specific framework. 12 | 2. A JavaScript file for the CloudFront function (e.g., `url-rewriting.js`): Handles URL rewriting for your application. 13 | 3. `cloudfront-hosting-toolkit.json`: Contains your project configuration. 14 | 15 | ### 1. Build Configuration File 16 | 17 | Example for Vue.js: 18 | 19 | ```yaml 20 | version: 0.2 21 | phases: 22 | build: 23 | commands: 24 | - npx npm install 25 | - npx npm run build 26 | - cd dist 27 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 28 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 29 | ``` 30 | 31 | [More build configuration templates](https://github.com/awslabs/cloudfront-hosting-toolkit/tree/main/resources/build_config_templates) 32 | 33 | ### 2. CloudFront Function File 34 | 35 | Example for Vue.js: 36 | 37 | ```javascript 38 | import cf from 'cloudfront'; 39 | 40 | const kvsId = '__KVS_ID__'; 41 | 42 | // This fails if the key value store is not associated with the function 43 | const kvsHandle = cf.kvs(kvsId); 44 | 45 | function pointsToFile(uri) { 46 | return /\/[^/]+\.[^/]+$/.test(uri); 47 | } 48 | var rulePatterns = { 49 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 50 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 51 | "!file/": "/index.html",// When URI has a trailing slash and doesn't point to a specific file, append 'index.html' 52 | }; 53 | 54 | // Function to determine rule and update the URI 55 | async function updateURI(uri) { 56 | 57 | let pathToAdd = ""; 58 | 59 | try { 60 | pathToAdd = await kvsHandle.get("path"); 61 | } catch (err) { 62 | console.log(`No key 'path' present : ${err}`); 63 | return uri; 64 | } 65 | 66 | // Check for trailing slash and apply rule. 67 | if (uri.endsWith("/") && rulePatterns["/$"]) { 68 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"]; 69 | } 70 | 71 | // Check if URI doesn't point to a specific file. 72 | if (!pointsToFile(uri)) { 73 | // If URI doesn't have a trailing slash, apply rule. 74 | if (!uri.endsWith("/") && rulePatterns["!file"]) { 75 | return "/" + pathToAdd + uri + rulePatterns["!file"]; 76 | } 77 | 78 | // If URI has a trailing slash, apply rule. 79 | if (uri.endsWith("/") && rulePatterns["!file/"]) { 80 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"]; 81 | } 82 | } 83 | 84 | return "/" + pathToAdd + uri; 85 | } 86 | 87 | // Main CloudFront handler 88 | async function handler(event) { 89 | var request = event.request; 90 | var uri = request.uri; 91 | 92 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 93 | request.uri = await updateURI(uri); 94 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 95 | 96 | 97 | 98 | return request; 99 | } 100 | 101 | ``` 102 | 103 | [More CloudFront function templates](https://github.com/awslabs/cloudfront-hosting-toolkit/tree/main/resources/cff_templates) 104 | 105 | ### 3. Project Configuration File 106 | 107 | For GitHub-based workflow: 108 | 109 | ```json 110 | { 111 | "repoUrl": "https://github.com/USERNAME/REPOSITORY.git", 112 | "branchName": "main", 113 | "framework": "nextjs", 114 | "domainName": "example.com", 115 | "hostedZoneId": "Z1234567890ABCDEF" 116 | } 117 | ``` 118 | 119 | For S3-based workflow: 120 | 121 | ```json 122 | { 123 | "s3bucket": "my-source-bucket", 124 | "s3path": "path/to/source", 125 | "domainName": "example.com", 126 | "hostedZoneId": "Z1234567890ABCDEF" 127 | } 128 | ``` 129 | 130 | [More configuration examples](https://github.com/your-repo-link/configuration-examples) 131 | 132 | ## Field Descriptions 133 | 134 | ### Mandatory Fields 135 | 136 | - For GitHub workflow: 137 | - `repoUrl`: The URL of your GitHub repository 138 | - `branchName`: The branch to deploy 139 | - `framework`: The framework used in your project (e.g., "nextjs", "react", "vue") 140 | 141 | - For S3 workflow: 142 | - `s3bucket`: The name of your S3 bucket containing the source code 143 | - `s3path`: The path within the bucket where your source code is located 144 | 145 | ### Optional Fields 146 | 147 | - `domainName`: Your custom domain name (if you want to use one) 148 | - `hostedZoneId`: The Route 53 hosted zone ID for your domain (required if using a custom domain) 149 | 150 | Note: If you include `domainName`, you must also include `hostedZoneId`. These fields are used together to set up a custom domain for your website. -------------------------------------------------------------------------------- /docs/src/content/docs/user-guide/cdk-construct.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CDK Construct Guide 3 | --- 4 | 5 | The CloudFront Hosting Toolkit can be easily integrated into your existing CDK projects as a construct. 6 | 7 | ## Installation 8 | 9 | Install the CloudFront Hosting Toolkit in your CDK project: 10 | 11 | ```bash 12 | npm install @aws/cloudfront-hosting-toolkit 13 | ``` 14 | 15 | ## Configuration 16 | 17 | Before using the construct, you need to set up some configuration files. See our [CDK Configuration Guide](/cloudfront-hosting-toolkit/user-guide/cdk-configuration) for detailed instructions. 18 | 19 | ## Usage 20 | 21 | After setting up the required configuration, you can use the CloudFront Hosting Toolkit construct in your CDK code: 22 | 23 | ### For GitHub Repository Deployment 24 | 25 | ```typescript 26 | import { RepositoryConnection, Hosting } from '@aws/cloudfront-hosting-toolkit'; 27 | 28 | const config = { 29 | repoUrl: "https://github.com/USERNAME/REPOSITORY.git", 30 | branchName: "main", 31 | framework: "nextjs" 32 | }; 33 | 34 | const repositoryConnection = new RepositoryConnection(this, "MyRepositoryConnection", config); 35 | 36 | new Hosting(this, "MyHosting", { 37 | hostingConfiguration: config, 38 | buildFilePath: "cloudfront-hosting-toolkit/buildConfigurationFile.yml", 39 | connectionArn: repositoryConnection.connectionArn, 40 | cffSourceFilePath: "cloudfront-hosting-toolkit/url-rewriting.js" 41 | }); 42 | ``` 43 | 44 | ### For S3 Repository Deployment 45 | 46 | ```typescript 47 | import { Hosting } from '@aws/cloudfront-hosting-toolkit'; 48 | 49 | const config = { 50 | s3bucket: "frontend-hosting-source", 51 | s3path: "" 52 | }; 53 | 54 | new Hosting(this, "MyHosting", { 55 | hostingConfiguration: config, 56 | buildFilePath: "cloudfront-hosting-toolkit/buildConfigurationFile.yml", 57 | cffSourceFilePath: "cloudfront-hosting-toolkit/url-rewriting.js" 58 | }); 59 | ``` 60 | 61 | ## Deployment 62 | 63 | Deploy your CDK project as usual. The CloudFront Hosting Toolkit infrastructure will be deployed as part of your stack. 64 | 65 | -------------------------------------------------------------------------------- /docs/src/content/docs/user-guide/cdk-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CDK Overview 3 | --- 4 | 5 | CloudFront Hosting Toolkit offers two primary methods for integration with AWS CDK (Cloud Development Kit): 6 | 7 | 1. **CDK Construct**: Use the CloudFront Hosting Toolkit as a pre-built construct in your existing CDK project. This method is ideal if you want to quickly integrate the toolkit's functionality without modifying its core logic. 8 | 9 | 2. **CDK Source Code**: Clone the CloudFront Hosting Toolkit repository, modify the source code as needed, and deploy it as a standalone CDK project. This approach offers maximum flexibility and customization. 10 | 11 | Both methods require some common setup steps, which are detailed in our [CDK Configuration Guide](cdk-configuration-guide.md). 12 | 13 | Choose the method that best fits your project's needs and level of customization required: 14 | -------------------------------------------------------------------------------- /docs/src/content/docs/user-guide/cdk-source-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CDK Source Code Guide 3 | --- 4 | 5 | For maximum flexibility, you can deploy the CloudFront Hosting Toolkit by cloning and modifying its source code. 6 | 7 | ## Steps 8 | 9 | 1. Clone the project: 10 | ```bash 11 | git clone https://github.com/awslabs/cloudfront-hosting-toolkit.git 12 | cd cloudfront-hosting-toolkit 13 | ``` 14 | 15 | 2. Modify the source code as needed to suit your requirements. 16 | 17 | 3. Set up the necessary configuration files. See our [CDK Configuration Guide](/cloudfront-hosting-toolkit/user-guide/cdk-configuration) for detailed instructions. 18 | 19 | 4. Deploy the project: 20 | ```bash 21 | npm run build 22 | cdk deploy 23 | ``` 24 | 25 | 5. After deployment, you need to configure the GitHub connection by going to the AWS console. [More information on GitHub connections](https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-github.html). 26 | 27 | ## Note on deployment 28 | 29 | - The first time you deploy, the pipeline will be automatically triggered. 30 | - If you make changes to the infrastructure and need to redeploy, you may need to manually trigger the pipeline if the changes require a new deployment. 31 | -------------------------------------------------------------------------------- /docs/src/content/docs/user-guide/cli-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CLI Guide 3 | --- 4 | 5 | The CloudFront Hosting Toolkit CLI is designed to simplify the process of deploying and managing your frontend applications on AWS. 6 | 7 | ## Usage 8 | 9 | The CLI can be executed in two ways: 10 | 11 | 1. In a folder where your project is cloned from GitHub. This allows the CLI to auto-detect the framework, repository URL, and branch name. 12 | 2. In any other folder, where you'll need to manually input this information. 13 | 14 | ## Available Commands 15 | 16 | ### Init 17 | 18 | Initialize your deployment configuration: 19 | 20 | ```bash 21 | cloudfront-hosting-toolkit init 22 | ``` 23 | 24 | Options: 25 | - `--s3`: Initialize an S3 repository deployment instead of GitHub. 26 | 27 | The following animated GIF demonstrates the initialization process: 28 | 29 | 30 | 31 | 32 | After running `cloudfront-hosting-toolkit init`, CloudFront Hosting Toolkit will create a folder named `cloudfront-hosting-toolkit` in the directory where the tool is executed. 33 | 34 | This `cloudfront-hosting-toolkit` folder contains essential files for managing your deployment process: 35 | 36 | 1. `cloudfront-hosting-toolkit-config.json`: This JSON file stores the configuration settings gathered during the `init` process. You can review and modify these settings as needed. 37 | 2. `cloudfront-hosting-toolkit-config.yml`: This YAML file is necessary for CodeBuild to build your website. It contains build configuration information, ensuring that your website is built correctly. 38 | 3. `cloudfront-hosting-toolkit-cff.js`: This JavaScript file encapsulates the code utilized by the CloudFront Function for URL rewriting. Given the variability in rules based on the framework, this file is accessible for modifications at any time, enabling the addition of additional logic required to execute at the edge. 39 | 4. During deployment, the CLI generates log files to capture important information and log messages. These log files are named using the format `YYYY-MM-DD_HH-MM-SS.log` and are stored in the `cloudfront-hosting-toolkit` folder. You can review these logs to troubleshoot any deployment issues or monitor the deployment process. 40 | 41 | Make sure to keep these files in the `cloudfront-hosting-toolkit` folder for seamless management of your future deployments. 42 | 43 | ### Deploy 44 | 45 | Deploy your website: 46 | 47 | ```bash 48 | cloudfront-hosting-toolkit deploy 49 | ``` 50 | 51 | > **Important** 52 | > 53 | > You typically only need to run the deploy command once after your initial init command. Subsequent code updates will be automatically deployed through the established pipeline. 54 | 55 | 56 | You should only need to run deploy again if: 57 | 58 | - You've run the init command again to change your configuration. 59 | - You've manually deleted or significantly altered your AWS infrastructure. 60 | 61 | 62 | When you run this command, the CLI will: 63 | 64 | 1. For GitHub-based workflows, create an AWS CodeStar connection to your GitHub repository. This process involves: 65 | 66 | - Creating a connection in a PENDING state. 67 | - Prompting you to complete the connection setup in the AWS console. 68 | - Guiding you through the process of installing the AWS Connector for GitHub app and granting necessary permissions. 69 | - You'll need to select the specific GitHub repository you want to deploy. 70 | 71 | This step is crucial for establishing secure access between your AWS account and your GitHub repository. For more detailed information on this process, refer to the AWS documentation on GitHub connections. 72 | 2. Display the progress of the infrastructure deployment in real-time. 73 | 3. Continue to show updates until the entire infrastructure is successfully deployed. 74 | 4. After the infrastructure deployment is complete, automatically trigger the pipeline for your initial website deployment. 75 | 5. Wait for the pipeline to finish, providing status updates throughout the process. 76 | 77 | The deployment is only considered complete when both the infrastructure is set up and the initial pipeline run has finished successfully. 78 | 79 | Upon successful completion of the deployment: 80 | - The domain name under which your website is available will be displayed. 81 | - The pipeline status and name will be shown. 82 | 83 | If the pipeline fails, you'll receive an error message. For troubleshooting information and potential solutions, please refer to the [Troubleshooting Guide](/cloudfront-hosting-toolkit/troubleshooting/guide). 84 | 85 | The following animated GIF demonstrates the deployment process: 86 | 87 | 88 | 89 | 90 | Note: The actual time for deployment can vary based on the complexity of your project and current AWS service response times. 91 | 92 | 93 | ### Show 94 | 95 | Display the domain name linked to your deployed source code repository: 96 | 97 | ```bash 98 | cloudfront-hosting-toolkit show 99 | ``` 100 | 101 | ### Status 102 | 103 | Check the current status of your pipeline deployment: 104 | 105 | ```bash 106 | cloudfront-hosting-toolkit status 107 | ``` 108 | 109 | ### Delete 110 | 111 | Remove the hosting infrastructure from your AWS account: 112 | 113 | ```bash 114 | cloudfront-hosting-toolkit delete 115 | ``` 116 | 117 | ## Best Practices 118 | 119 | 1. Run `init` in your project root directory for accurate auto-detection of project settings when using a GitHub repository. 120 | 2. Review the generated log files in the `cloudfront-hosting-toolkit` folder for troubleshooting and monitoring. 121 | 3. If you need to customize URL rewriting rules, modify the `cloudfront-hosting-toolkit-cff.js` file as needed. -------------------------------------------------------------------------------- /docs/src/styles/custom.css: -------------------------------------------------------------------------------- 1 | /* Dark mode colors. */ 2 | :root { 3 | --sl-color-accent-low: #1a2639; 4 | --sl-color-accent: #3e4a61; 5 | --sl-color-accent-high: #c9d1d9; 6 | --sl-color-white: #ffffff; 7 | --sl-color-gray-1: #f0f6fc; 8 | --sl-color-gray-2: #c9d1d9; 9 | --sl-color-gray-3: #8b949e; 10 | --sl-color-gray-4: #6e7681; 11 | --sl-color-gray-5: #484f58; 12 | --sl-color-gray-6: #30363d; 13 | --sl-color-black: #0d1117; 14 | --sl-label-api-color: #ff7b72; 15 | --sl-label-api-background-color: #ff7b72; 16 | --sl-label-version-color: #7ee787; 17 | --sl-label-version-background-color: #7ee787; 18 | --sl-label-package-color: #ffa657; 19 | --sl-label-package-background-color: #ffa657; 20 | } 21 | 22 | /* Light mode colors. */ 23 | :root[data-theme='light'] { 24 | --sl-color-accent-low: #d0e8ff; 25 | --sl-color-accent: #0969da; 26 | --sl-color-accent-high: #033d8b; 27 | --sl-color-white: #0d1117; 28 | --sl-color-gray-1: #30363d; 29 | --sl-color-gray-2: #484f58; 30 | --sl-color-gray-3: #6e7681; 31 | --sl-color-gray-4: #8b949e; 32 | --sl-color-gray-5: #c9d1d9; 33 | --sl-color-gray-6: #f0f6fc; 34 | --sl-color-gray-7: #f8f9fa; 35 | --sl-color-black: #ffffff; 36 | --sl-label-api-color: #cf222e; 37 | --sl-label-api-background-color: #cf222e; 38 | --sl-label-version-color: #2da44e; 39 | --sl-label-version-background-color: #2da44e; 40 | --sl-label-package-color: #d4a72c; 41 | --sl-label-package-background-color: #d4a72c; 42 | } 43 | 44 | :root { 45 | --purple-hsl: 212, 72%, 59%; 46 | --overlay-blurple: hsla(var(--purple-hsl), 0.4); 47 | } 48 | 49 | [data-has-hero] .page { 50 | background: linear-gradient(215deg, var(--overlay-blurple), transparent 40%), 51 | radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, 52 | radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / 60rem 30rem; 53 | } 54 | 55 | [data-has-hero] header { 56 | border-bottom: 1px solid transparent; 57 | background-color: transparent; 58 | -webkit-backdrop-filter: blur(16px); 59 | backdrop-filter: blur(16px); 60 | } 61 | 62 | [data-has-hero] .hero > img { 63 | filter: drop-shadow(0 0 3rem var(--overlay-blurple)); 64 | } 65 | 66 | [data-page-title] { 67 | font-size: 3rem; 68 | } 69 | 70 | @media (max-width: 768px) { 71 | [data-page-title] { 72 | font-size: 2.5rem; 73 | } 74 | } 75 | 76 | .card-grid > .card { 77 | border-radius: 10px; 78 | } 79 | 80 | .card > .title { 81 | font-size: 1.3rem; 82 | font-weight: 600; 83 | line-height: 1.2; 84 | } 85 | 86 | .Label, .label { 87 | border: 1px solid; 88 | border-radius: 2em; 89 | display: inline-block; 90 | font-size: 0.75rem; 91 | font-weight: 500; 92 | line-height: 18px; 93 | padding: 0 7px; 94 | white-space: nowrap; 95 | } 96 | 97 | .Label > a, .label > a { 98 | color: inherit; 99 | text-decoration: none; 100 | } 101 | 102 | .Label > a:hover, .label > a:hover { 103 | color: inherit; 104 | text-decoration: none; 105 | } 106 | 107 | .Label.Label--api { 108 | color: var(--sl-label-api-color); 109 | border-color: var(--sl-label-api-background-color); 110 | } 111 | 112 | .Label.Label--version { 113 | color: var(--sl-label-version-color); 114 | border-color: var(--sl-label-version-background-color); 115 | } 116 | 117 | .Label.Label--package { 118 | color: var(--sl-label-package-color); 119 | border-color: var(--sl-label-package-background-color); 120 | } 121 | 122 | .text-uppercase { 123 | text-transform: uppercase !important; 124 | } 125 | 126 | .language-icon { 127 | margin-bottom: -8px; 128 | float: right; 129 | } 130 | 131 | @media only screen and (max-width: 1023px) { 132 | .language-icon { 133 | display: none; 134 | float: none; 135 | } 136 | } -------------------------------------------------------------------------------- /docs/src/styles/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "JetBrainsMono NF"; 3 | src: url("../assets/fonts/JetBrainsMonoNerdFont-Regular.ttf") 4 | format("truetype"); 5 | font-weight: 400; 6 | font-style: normal; 7 | font-display: swap; 8 | } 9 | 10 | @font-face { 11 | font-family: "JetBrainsMono NF"; 12 | src: url("../assets/fonts/JetBrainsMonoNerdFont-Italic.ttf") 13 | format("truetype"); 14 | font-weight: 400; 15 | font-style: italic; 16 | font-display: swap; 17 | } 18 | 19 | @font-face { 20 | font-family: "JetBrainsMono NF"; 21 | src: url("../assets/fonts/JetBrainsMonoNerdFont-Bold.ttf") format("truetype"); 22 | font-weight: 700; 23 | font-style: normal; 24 | font-display: swap; 25 | } 26 | 27 | @font-face { 28 | font-family: "JetBrainsMono NF"; 29 | src: url("../assets/fonts/JetBrainsMonoNerdFont-BoldItalic.ttf") 30 | format("truetype"); 31 | font-weight: 700; 32 | font-style: italic; 33 | font-display: swap; 34 | } 35 | 36 | :root { 37 | --sl-font: "JetBrainsMono NF"; 38 | --sl-font-mono: "JetBrainsMono NF"; 39 | } 40 | -------------------------------------------------------------------------------- /docs/src/styles/landing.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sl-hue-accent: 255; 3 | --sl-color-accent-low: hsl(var(--sl-hue-accent), 14%, 20%); 4 | --sl-color-accent: hsl(var(--sl-hue-accent), 60%, 60%); 5 | --sl-color-accent-high: hsl(var(--sl-hue-accent), 60%, 87%); 6 | --overlay-blurple: hsla(var(--sl-hue-accent), 60%, 60%, 0.2); 7 | } 8 | 9 | :root[data-theme='light'] { 10 | --sl-hue-accent: 45; /*Color of top bar text and icons and Getting Started button*/ 11 | --sl-color-accent-high: hsl(var(--sl-hue-accent), 90%, 20%); 12 | --sl-color-accent: hsl(var(--sl-hue-accent), 100%, 50%); 13 | --sl-color-accent-low: hsl(var(--sl-hue-accent), 98%, 80%); 14 | } 15 | 16 | [data-has-hero] .page { 17 | background: linear-gradient(215deg, var(--overlay-blurple), transparent 40%), 18 | radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, 19 | radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / 60rem 30rem; 20 | } 21 | 22 | [data-has-hero] header { 23 | border-bottom: 1px solid transparent; 24 | background-color: transparent; 25 | -webkit-backdrop-filter: blur(16px); 26 | backdrop-filter: blur(16px); 27 | } 28 | 29 | [data-has-hero] .hero > img { 30 | filter: drop-shadow(0 0 3rem var(--overlay-blurple)); 31 | } 32 | 33 | iframe[id='stackblitz-iframe'] { 34 | width: 100%; 35 | min-height: 600px; 36 | } -------------------------------------------------------------------------------- /docs/src/styles/terminal.css: -------------------------------------------------------------------------------- 1 | /* Solarized color palette */ 2 | :root { 3 | --sol-red: #dc322f; 4 | --sol-bright-red: #cb4b16; 5 | --sol-green: #859900; 6 | --sol-yellow: #b58900; 7 | --sol-blue: #268bd2; 8 | --sol-magenta: #d33682; 9 | --sol-bright-magenta: #6c71c4; 10 | --sol-cyan: #2aa198; 11 | 12 | --sol-base03: #002b36; 13 | --sol-base02: #073642; 14 | --sol-base00: #657b83; 15 | --sol-base0: #839496; 16 | --sol-base2: #eee8d5; 17 | --sol-base3: #fdf6e3; 18 | } 19 | 20 | pre.terminal { 21 | --black: var(--sol-base02); 22 | --red: var(--sol-red); 23 | --bright-red: var(--sol-bright-red); 24 | --green: var(--sol-green); 25 | --yellow: var(--sol-yellow); 26 | --blue: var(--sol-blue); 27 | --magenta: var(--sol-magenta); 28 | --bright-magenta: var(--sol-bright-magenta); 29 | --cyan: var(--sol-cyan); 30 | --white: var(--sol-base2); 31 | 32 | background-color: var(--sol-base03); 33 | color: var(--sol-base0); 34 | font-family: var(--__sl-font-mono); 35 | } 36 | 37 | :root[data-theme="light"] pre.terminal { 38 | background-color: var(--sol-base3); 39 | color: var(--sol-base00); 40 | } 41 | 42 | pre.terminal p { 43 | margin: -0.75rem -1rem; 44 | padding: 0.75rem 1rem; 45 | overflow-x: auto; 46 | } 47 | 48 | pre.astro-code + pre.terminal { 49 | margin-top: 0; 50 | border-top-width: 0; 51 | } -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict" 3 | } -------------------------------------------------------------------------------- /img/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/img/architecture.jpg -------------------------------------------------------------------------------- /img/deploy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/img/deploy.gif -------------------------------------------------------------------------------- /img/init.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/img/init.gif -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | }, 8 | coveragePathIgnorePatterns : [ 9 | "/bin/cli/actions/*.d.ts","/bin/cli/actions/*.js" 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /lambda/delete_old_deployments/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const { S3 } = require("@aws-sdk/client-s3"); 17 | const s3 = new S3({customUserAgent: "cht"}); 18 | 19 | const bucketName = process.env.BUCKET_NAME; // Use the environment variable 20 | 21 | exports.handler = async (event, context) => { 22 | console.log("event=" + JSON.stringify(event)); 23 | 24 | try { 25 | const commitId = event.commitId; 26 | 27 | const listObjectsParams = { 28 | Bucket: bucketName, 29 | Delimiter: "/", 30 | }; 31 | 32 | const listResponse = await s3.listObjectsV2(listObjectsParams); 33 | 34 | const folderObjects = listResponse.CommonPrefixes.map((prefix) => ({ 35 | Prefix: prefix.Prefix, 36 | })); 37 | 38 | const objectsToDelete = folderObjects.filter((object) => { 39 | const folderPath = object.Prefix; 40 | const folderName = folderPath.slice(0, -1); // Remove trailing slash 41 | 42 | return ( 43 | !folderName.startsWith(commitId) 44 | ); 45 | }); 46 | 47 | if (objectsToDelete.length === 0) { 48 | console.log("No files or folders to delete."); 49 | } else { 50 | let deletedItemCount = 0; 51 | 52 | for (const object of objectsToDelete) { 53 | const deleteFilesParams = { 54 | Bucket: bucketName, 55 | Prefix: object.Prefix, 56 | }; 57 | 58 | const files = await s3.listObjectsV2(deleteFilesParams); 59 | 60 | for (const file of files.Contents) { 61 | const deleteFileParams = { 62 | Bucket: bucketName, 63 | Key: file.Key, 64 | }; 65 | 66 | await s3.deleteObject(deleteFileParams); 67 | console.log(`Deleted file: ${file.Key}`); 68 | deletedItemCount++; 69 | } 70 | } 71 | 72 | console.log(`Deleted ${deletedItemCount} objects successfully.`); 73 | } 74 | 75 | return "Execution completed successfully"; 76 | } catch (error) { 77 | console.error("Error:", error); 78 | throw new Error(`Error deleting old files and folders: ${error.message}`); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /lambda/layers/aws_sdk/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AWS_SDK_Layer", 3 | "version": "1.0.0", 4 | "description": "Latest AWS SDK libs", 5 | "main": "", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "@aws-sdk/client-cloudfront-keyvaluestore": "^3.633.0", 11 | "@aws-sdk/signature-v4-crt": "^3.511.0", 12 | "@aws-sdk/signature-v4-multi-region": "^3.511.0" 13 | }, 14 | "author": { 15 | "name": "Amazon Web Services", 16 | "url": "https://aws.amazon.com/solutions" 17 | }, 18 | "license": "Apache-2.0" 19 | } 20 | -------------------------------------------------------------------------------- /lambda/new_build/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const { SSM } = require("@aws-sdk/client-ssm"); 18 | const ssm = new SSM(); 19 | const { CodePipeline } = require("@aws-sdk/client-codepipeline"); 20 | const codepipeline = new CodePipeline(); 21 | 22 | exports.handler = async (event, context) => { 23 | console.log('event is 👉', JSON.stringify(event, null, 4)); 24 | const key = event.detail.object.key; 25 | 26 | const parts = key.split("/"); 27 | const file_name_parts = parts[parts.length - 1].split(".").slice(0, -1); 28 | const commitId = file_name_parts.join("."); 29 | console.log("commitId="+commitId); 30 | 31 | const ssmCommitIdName = process.env.SSM_PARAM_COMMITID; 32 | const ssms3KeyName = process.env.SSM_PARAM_S3_KEY; 33 | await setSsmParameter(ssmCommitIdName, commitId); 34 | await setSsmParameter(ssms3KeyName, key); 35 | await startPipeline(process.env.PIPELINE_NAME); 36 | 37 | return { 38 | body: JSON.stringify({message: 'Success!'}), 39 | statusCode: 200, 40 | }; 41 | } 42 | 43 | async function setSsmParameter (paramName, paramValue){ 44 | const params = { 45 | Name: paramName, 46 | Value: paramValue, 47 | Type: "String", 48 | Overwrite: true 49 | }; 50 | 51 | try { 52 | await ssm.putParameter(params); 53 | console.log(`Parameter ${paramName} set to ${paramValue}`); 54 | } catch (err) { 55 | console.log(`Error setting parameter ${paramName}: ${err}`); 56 | throw err; 57 | } 58 | 59 | } 60 | 61 | 62 | async function startPipeline(pipelineName) { 63 | 64 | const params = { 65 | name: pipelineName 66 | }; 67 | 68 | try { 69 | await codepipeline.startPipelineExecution(params); 70 | console.log(`Pipeline ${pipelineName} started`); 71 | } catch (err) { 72 | console.log(`Error starting pipeline ${pipelineName}: ${err}`); 73 | throw err; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /lambda/update_kvs/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const { 18 | CloudFrontKeyValueStoreClient, 19 | DescribeKeyValueStoreCommand, 20 | PutKeyCommand, 21 | } = require("@aws-sdk/client-cloudfront-keyvaluestore"); 22 | 23 | const { 24 | SignatureV4MultiRegion, 25 | } = require("@aws-sdk/signature-v4-multi-region"); 26 | require("@aws-sdk/signature-v4-crt"); 27 | 28 | const client = new CloudFrontKeyValueStoreClient({ 29 | region: "us-east-1", 30 | signerConstructor: SignatureV4MultiRegion, 31 | }); 32 | 33 | const kvsARN = process.env.KVS_ARN; 34 | 35 | async function updateKvs(path) { 36 | console.log("Get ETAG for KVS " + process.env.KVS_ARN); 37 | 38 | const { ETag } = await client.send(new DescribeKeyValueStoreCommand({ KvsARN: kvsARN })); 39 | 40 | console.log("Update KVS using ETAG " + ETag); 41 | 42 | await client.send(new PutKeyCommand({ 43 | Key: "path", 44 | Value: path, 45 | KvsARN: kvsARN, 46 | IfMatch: ETag, 47 | })); 48 | 49 | console.log("KVS updated"); 50 | } 51 | 52 | exports.handler = async (event, context) => { 53 | console.log("event=" + JSON.stringify(event)); 54 | 55 | try { 56 | await updateKvs(event.commitId); 57 | return {}; 58 | 59 | } catch (error) { 60 | console.error("Error updating KVS:", error); 61 | throw error; 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /lib/cfn_nag/cfn_nag_utils.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | import { CfnCondition, CfnResource, Resource } from "aws-cdk-lib"; 14 | 15 | interface CfnNagSuppressRule { 16 | id: string; 17 | reason: string; 18 | } 19 | 20 | /** 21 | * Adds CFN NAG suppress rules to the CDK resource. 22 | * @param resource The CDK resource. 23 | * @param rules The CFN NAG suppress rules. 24 | */ 25 | export function addCfnSuppressRules( 26 | resource: Resource | CfnResource | undefined, 27 | rules: CfnNagSuppressRule[] 28 | ) { 29 | if (typeof resource === "undefined") return; 30 | 31 | if (resource instanceof Resource) { 32 | resource = resource.node.defaultChild as CfnResource; 33 | } 34 | 35 | if (resource.cfnOptions.metadata?.cfn_nag?.rules_to_suppress) { 36 | resource.cfnOptions.metadata.cfn_nag.rules_to_suppress.push(...rules); 37 | } else { 38 | resource.addMetadata("cfn_nag", { rules_to_suppress: rules }); 39 | } 40 | } 41 | 42 | /** 43 | * Adds CDK condition to the CDK resource. 44 | * @param resource The CDK resource. 45 | * @param condition The CDK condition. 46 | */ 47 | export function addCfnCondition( 48 | resource: Resource | CfnResource | undefined, 49 | condition: CfnCondition 50 | ) { 51 | if (typeof resource === "undefined") return; 52 | 53 | if (resource instanceof Resource) { 54 | resource = resource.node.defaultChild as CfnResource; 55 | } 56 | 57 | (resource as CfnResource).cfnOptions.condition = condition; 58 | } 59 | -------------------------------------------------------------------------------- /lib/deploy_type.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Construct } from "constructs"; 18 | import { isRepoConfig, parseRepositoryUrl } from "../bin/cli/utils/helper"; 19 | import { CfnOutput, Stack } from "aws-cdk-lib"; 20 | import { HostingConfiguration } from "../bin/cli/shared/types"; 21 | export class DeployType extends Construct { 22 | public readonly deployIdentifier: string; 23 | /** 24 | * Constructs a deployment stack based on the provided configuration. 25 | * Handles Git repository and S3 bucket deployment scenarios, setting up 26 | * deployment identifiers and CloudFormation outputs accordingly. 27 | * In case of incorrect configuration, logs an error and exits the process. 28 | * 29 | * @param stackConfig - Configuration object containing deployment details. 30 | */ 31 | constructor( 32 | scope: Construct, 33 | id: string, 34 | hostingConfiguration: HostingConfiguration 35 | ) { 36 | //export function geteDeployIdentifier(stackConfig: IConfiguration) { 37 | super(scope, id); 38 | 39 | if (isRepoConfig(hostingConfiguration)) { 40 | const repoUrl = hostingConfiguration.repoUrl; 41 | const parsedUrl = parseRepositoryUrl(repoUrl as string); 42 | this.deployIdentifier = parsedUrl.repoOwner + " - " + parsedUrl.repoName + "-" + Stack.of(this).region; 43 | 44 | new CfnOutput(this, "Source", { 45 | value: hostingConfiguration.repoUrl, 46 | }); 47 | } else if (hostingConfiguration.s3bucket) { 48 | this.deployIdentifier = hostingConfiguration.s3bucket; 49 | 50 | new CfnOutput(this, "Source", { 51 | value: 52 | hostingConfiguration.s3bucket + "/" + hostingConfiguration.s3path, 53 | }); 54 | } else { 55 | console.log("Wrong configuration found. Exiting."); 56 | console.log( 57 | "Configuration found: " + JSON.stringify(hostingConfiguration) 58 | ); 59 | process.exit(1); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/deployment_workflow_sf.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { 18 | Aws, 19 | Duration, 20 | aws_iam as iam, 21 | aws_cloudfront as cloudfront, 22 | aws_stepfunctions as sfn, 23 | aws_lambda as lambda, 24 | aws_stepfunctions_tasks as tasks, 25 | aws_logs as logs, 26 | aws_ssm as ssm, 27 | RemovalPolicy, 28 | Stack, 29 | } from "aws-cdk-lib"; 30 | 31 | import { Construct } from "constructs"; 32 | import * as path from "path"; 33 | import { IChainable, JsonPath, LogLevel } from "aws-cdk-lib/aws-stepfunctions"; 34 | import { IBucket } from "aws-cdk-lib/aws-s3"; 35 | import { addCfnSuppressRules } from "./cfn_nag/cfn_nag_utils"; 36 | import { NagSuppressions } from "cdk-nag"; 37 | import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs"; 38 | import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; 39 | import { LayerVersion, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda'; 40 | 41 | 42 | interface IParamProps { 43 | changeUri: cloudfront.Function; 44 | kvsArn: string, 45 | hostingBucket: IBucket; 46 | ssmCommitIdParam?: ssm.StringParameter; 47 | ssmS3KeyParam?: ssm.StringParameter; 48 | } 49 | 50 | 51 | export class DeploymentWorkflowStepFunction extends Construct { 52 | public readonly stepFunction: sfn.IStateMachine; 53 | 54 | constructor(scope: Construct, id: string, params: IParamProps) { 55 | super(scope, id); 56 | 57 | 58 | const basicLambdaRole = new iam.Role(this, "BasicLambdaRole", { 59 | assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), 60 | }); 61 | 62 | const awsSdkLayer = new lambda.LayerVersion(this, "AwsSdkLayer", { 63 | compatibleRuntimes: [lambda.Runtime.NODEJS_20_X], 64 | code: lambda.Code.fromAsset(path.join(__dirname, "../lambda/layers/aws_sdk")), 65 | description: "AWS SDK lib including client-cloudfront-keyvaluestore", 66 | }); 67 | 68 | const updateKvs = new lambda.Function(this, "UpdateKvsFunction", { 69 | runtime: lambda.Runtime.NODEJS_20_X, 70 | code: lambda.Code.fromAsset(path.join(__dirname, "../lambda/update_kvs")), 71 | handler: "index.handler", 72 | memorySize: 512, 73 | timeout: Duration.minutes(10), 74 | logRetention: logs.RetentionDays.ONE_WEEK, 75 | tracing: lambda.Tracing.ACTIVE, 76 | layers: [awsSdkLayer], 77 | environment: { 78 | KVS_ARN: params.kvsArn, 79 | }, 80 | role: basicLambdaRole, 81 | }); 82 | 83 | 84 | updateKvs.addToRolePolicy( 85 | new iam.PolicyStatement({ 86 | effect: iam.Effect.ALLOW, 87 | actions: [ 88 | "logs:CreateLogGroup", 89 | "logs:CreateLogStream", 90 | "logs:PutLogEvents", 91 | ], 92 | resources: ["arn:aws:logs:*:*:*"], 93 | }) 94 | ); 95 | 96 | updateKvs.addToRolePolicy( 97 | new iam.PolicyStatement({ 98 | effect: iam.Effect.ALLOW, 99 | actions: [ 100 | "cloudfront-keyvaluestore:DescribeKeyValueStore", 101 | "cloudfront-keyvaluestore:PutKey", 102 | ], 103 | resources: [params.kvsArn], 104 | }) 105 | ); 106 | 107 | addCfnSuppressRules(updateKvs, [ 108 | { 109 | id: "W58", 110 | reason: 111 | "Lambda has CloudWatch permissions by using service role AWSLambdaBasicExecutionRole", 112 | }, 113 | ]); 114 | addCfnSuppressRules(updateKvs, [ 115 | { 116 | id: "W89", 117 | reason: 118 | "We don t have any VPC in the stack, we only use serverless services", 119 | }, 120 | ]); 121 | addCfnSuppressRules(updateKvs, [ 122 | { 123 | id: "W92", 124 | reason: 125 | "No need for ReservedConcurrentExecutions, some are used only for the demo website, and others are not used in a concurrent mode.", 126 | }, 127 | ]); 128 | 129 | const deleteOldDeployments = new lambda.Function( 130 | this, 131 | "DeleteOldDeployments", 132 | { 133 | runtime: lambda.Runtime.NODEJS_18_X, 134 | code: lambda.Code.fromAsset( 135 | path.join(__dirname, "../lambda/delete_old_deployments") 136 | ), 137 | timeout: Duration.seconds(300), 138 | handler: "index.handler", 139 | environment: { 140 | BUCKET_NAME: params.hostingBucket.bucketName, 141 | }, 142 | logRetention: logs.RetentionDays.ONE_WEEK, 143 | role: basicLambdaRole, 144 | } 145 | ); 146 | 147 | 148 | deleteOldDeployments.addToRolePolicy( 149 | new iam.PolicyStatement({ 150 | effect: iam.Effect.ALLOW, 151 | actions: [ 152 | "logs:CreateLogGroup", 153 | "logs:CreateLogStream", 154 | "logs:PutLogEvents", 155 | ], 156 | resources: ["arn:aws:logs:*:*:*"], 157 | }) 158 | ); 159 | 160 | addCfnSuppressRules(deleteOldDeployments, [ 161 | { 162 | id: "W58", 163 | reason: 164 | "Lambda has CloudWatch permissions by using service role AWSLambdaBasicExecutionRole", 165 | }, 166 | ]); 167 | addCfnSuppressRules(deleteOldDeployments, [ 168 | { 169 | id: "W89", 170 | reason: 171 | "We don t have any VPC in the stack, we only use serverless services", 172 | }, 173 | ]); 174 | addCfnSuppressRules(deleteOldDeployments, [ 175 | { 176 | id: "W92", 177 | reason: 178 | "No need for ReservedConcurrentExecutions, some are used only for the demo website, and others are not used in a concurrent mode.", 179 | }, 180 | ]); 181 | 182 | params.hostingBucket.grantReadWrite(deleteOldDeployments); 183 | 184 | NagSuppressions.addResourceSuppressions( 185 | basicLambdaRole, 186 | [ 187 | { 188 | id: "AwsSolutions-IAM5", 189 | reason: "Permissions to get objects and delete them is required", 190 | }, 191 | ], 192 | true 193 | ); 194 | 195 | const successState = new sfn.Pass(this, "SuccessState"); 196 | 197 | const updateCloudFrontFunctionJob = new tasks.LambdaInvoke( 198 | this, 199 | "Update KeyValueStore", 200 | { 201 | lambdaFunction: updateKvs, 202 | resultPath: JsonPath.DISCARD, 203 | } 204 | ); 205 | 206 | const deleteOldDeploymentsJob = new tasks.LambdaInvoke( 207 | this, 208 | "Purge previous deployments from S3", 209 | { 210 | lambdaFunction: deleteOldDeployments, 211 | resultPath: JsonPath.DISCARD, 212 | } 213 | ); 214 | 215 | 216 | const communDefinition = updateCloudFrontFunctionJob 217 | .next(deleteOldDeploymentsJob); 218 | 219 | var stepFunctionDefinition: IChainable; 220 | 221 | if (params.ssmCommitIdParam) { 222 | const getCommitId = new tasks.CallAwsService( 223 | this, 224 | "Get CommitID from Parameter Store", 225 | { 226 | service: "ssm", 227 | action: "getParameter", 228 | parameters: { 229 | Name: params.ssmCommitIdParam.parameterName, 230 | }, 231 | iamResources: [params.ssmCommitIdParam.parameterArn], 232 | iamAction: "ssm:getParameter", 233 | resultSelector: { 234 | commitId: sfn.JsonPath.stringAt("$.Parameter.Value"), 235 | }, 236 | } 237 | ); 238 | 239 | const ssmHasValue = new sfn.Choice(this, "SSM Param is SET ?") 240 | .when(sfn.Condition.stringEquals("$.commitId", "init"), successState) 241 | .otherwise(communDefinition); 242 | 243 | stepFunctionDefinition = getCommitId.next(ssmHasValue); 244 | } else { 245 | stepFunctionDefinition = communDefinition; 246 | } 247 | 248 | const sfnLog = new LogGroup(this, "sfnLog", { 249 | logGroupName: 250 | "/aws/vendedlogs/states/" + Stack.of(this).stackName, 251 | removalPolicy: RemovalPolicy.DESTROY, 252 | retention: RetentionDays.ONE_WEEK, 253 | }); 254 | 255 | this.stepFunction = new sfn.StateMachine(this, "StaticHostingSF", { 256 | definitionBody : sfn.DefinitionBody.fromChainable(stepFunctionDefinition), 257 | tracingEnabled: true, 258 | logs: { 259 | destination: sfnLog, 260 | includeExecutionData: true, 261 | level: LogLevel.ALL, 262 | }, 263 | }); 264 | 265 | NagSuppressions.addResourceSuppressions( 266 | this.stepFunction, 267 | [ 268 | { 269 | id: "AwsSolutions-IAM5", 270 | reason: "Required permissions for this Step Function", 271 | }, 272 | ], 273 | true 274 | ); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /lib/hosting.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). 4 | You may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | import { 14 | Aws, 15 | aws_cloudfront as cloudfront, 16 | aws_s3 as s3, 17 | Stack 18 | } from "aws-cdk-lib"; 19 | import * as path from "path"; 20 | import * as fs from "fs"; 21 | import { Construct } from "constructs"; 22 | import { HostingInfrastructure } from "./hosting_infrastructure"; 23 | import { PipelineInfrastructure } from "./pipeline_infrastructure"; 24 | import { HostingConfiguration } from "../bin/cli/shared/types"; 25 | import { truncateString } from "./utility"; 26 | 27 | interface IParamProps { 28 | hostingConfiguration: HostingConfiguration; 29 | buildFilePath: string; 30 | cffSourceFilePath: string; 31 | connectionArn?: string; 32 | certificateArn?: string; 33 | } 34 | 35 | /** 36 | * Custom CDK Construct for hosting resources. 37 | * 38 | * This construct sets up hosting based on the provided configuration, 39 | * build file path, and optional connection and certificate ARNs. 40 | * 41 | * @param scope - The Construct scope in which this construct is defined. 42 | * @param id - The identifier for this construct within the scope. 43 | * @param params - Parameters for configuring hosting resources. 44 | */ 45 | export class Hosting extends Construct { 46 | /** 47 | * The CloudFront Distribution created by this construct. 48 | */ 49 | public readonly distribution: cloudfront.Distribution; 50 | 51 | /** 52 | * The S3 Bucket used for hosting the content. 53 | */ 54 | public readonly hostingBucket: s3.IBucket; 55 | 56 | /** 57 | * The CloudFront Function used for URI manipulation. 58 | */ 59 | public readonly changeUriFunction: cloudfront.Function; 60 | 61 | /** 62 | * The Key-Value Store used by CloudFront. 63 | */ 64 | public readonly uriStore: cloudfront.KeyValueStore; 65 | 66 | /** 67 | * The distribution's domain name. 68 | */ 69 | public readonly distributionDomainName: string; 70 | 71 | /** 72 | * The distribution's URL (https://{domainName}). 73 | */ 74 | public readonly distributionUrl: string; 75 | 76 | /** 77 | * Reference to the hosting infrastructure construct. 78 | */ 79 | public readonly hostingInfrastructure: HostingInfrastructure; 80 | 81 | /** 82 | * Reference to the pipeline infrastructure construct. 83 | */ 84 | public readonly pipelineInfrastructure: PipelineInfrastructure; 85 | 86 | constructor(scope: Construct, id: string, params: IParamProps) { 87 | super(scope, id); 88 | 89 | const stackName = Stack.of(this).stackName; 90 | const region = Stack.of(this).region; 91 | 92 | // Create URI Store 93 | this.uriStore = new cloudfront.KeyValueStore(this, 'UriStore', { 94 | keyValueStoreName: truncateString(stackName + "-" + region, 64) 95 | }); 96 | 97 | // Setup CloudFront Function 98 | let cloudFrontFunctionCode = fs.readFileSync(params.cffSourceFilePath, 'utf-8'); 99 | cloudFrontFunctionCode = cloudFrontFunctionCode.replace(/__KVS_ID__/g, this.uriStore.keyValueStoreId); 100 | 101 | this.changeUriFunction = new cloudfront.Function(this, "ChangeUri", { 102 | code: cloudfront.FunctionCode.fromInline(cloudFrontFunctionCode), 103 | runtime: cloudfront.FunctionRuntime.JS_2_0, 104 | comment: "Change uri", 105 | }); 106 | 107 | (this.changeUriFunction.node.defaultChild as cloudfront.CfnFunction).addPropertyOverride( 108 | "FunctionConfig.KeyValueStoreAssociations", 109 | [{ 110 | "KeyValueStoreARN": this.uriStore.keyValueStoreArn 111 | }] 112 | ); 113 | 114 | // Create Hosting Infrastructure 115 | this.hostingInfrastructure = new HostingInfrastructure(this, "HostingInfrastructure", { 116 | changeUri: this.changeUriFunction, 117 | certificateArn: params.certificateArn, 118 | hostingConfiguration: params.hostingConfiguration, 119 | }); 120 | 121 | // Set properties from hosting infrastructure 122 | this.distribution = this.hostingInfrastructure.distribution; 123 | this.hostingBucket = this.hostingInfrastructure.hostingBucket; 124 | this.distributionDomainName = this.distribution.distributionDomainName; 125 | this.distributionUrl = `https://${this.distributionDomainName}`; 126 | 127 | // Create Pipeline Infrastructure 128 | this.pipelineInfrastructure = new PipelineInfrastructure(this, "PipelineInfrastructure", { 129 | hostingConfiguration: params.hostingConfiguration, 130 | connectionArn: params.connectionArn, 131 | kvsArn: this.uriStore.keyValueStoreArn, 132 | hostingBucket: this.hostingBucket, 133 | changeUri: this.changeUriFunction, 134 | buildFilePath: params.buildFilePath, 135 | }); 136 | } 137 | } -------------------------------------------------------------------------------- /lib/hosting_stack.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Stack, StackProps } from "aws-cdk-lib"; 18 | 19 | import { Construct } from "constructs"; 20 | import { HostingConfiguration } from "../bin/cli/shared/types"; 21 | import { Hosting } from "./hosting"; 22 | 23 | interface IParamProps { 24 | hostingConfiguration: HostingConfiguration; 25 | buildFilePath: string; 26 | cffSourceFilePath: string; 27 | connectionArn?: string; 28 | certificateArn?: string; 29 | } 30 | 31 | export class HostingStack extends Stack { 32 | public readonly hosting: Hosting; 33 | constructor( 34 | scope: Construct, 35 | id: string, 36 | params: IParamProps, 37 | props?: StackProps 38 | ) { 39 | super(scope, id, props); 40 | 41 | this.hosting = new Hosting(this, "Hosting", params); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import { Construct } from "constructs"; 17 | 18 | export * from './hosting'; 19 | export * from './repository_connection'; 20 | -------------------------------------------------------------------------------- /lib/repository_connection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Stack, 3 | StackProps, 4 | aws_cloudfront as cloudfront, 5 | aws_ssm as ssm, 6 | CfnOutput, 7 | Aws, 8 | aws_codestarconnections as codestarconnections, 9 | } from "aws-cdk-lib"; 10 | 11 | import { Construct } from "constructs"; 12 | import { 13 | calculateCodeStarConnectionStackName, 14 | calculateConnectionStackName, 15 | isRepoConfig, 16 | parseRepositoryUrl, 17 | } from "../bin/cli/utils/helper"; 18 | import { 19 | SSM_CONNECTION_ARN_STR, 20 | SSM_CONNECTION_NAME_STR, 21 | SSM_CONNECTION_REGION_STR, 22 | } from "../bin/cli/shared/constants"; 23 | import { HostingConfiguration } from "../bin/cli/shared/types"; 24 | 25 | /** 26 | * Custom CDK Construct for setting up a repository connection. 27 | * 28 | * This construct creates a connection to a hosting repository using the provided configuration. 29 | * 30 | * @param scope - The Construct scope in which this construct is defined. 31 | * @param id - The identifier for this construct within the scope. 32 | * @param params - Parameters for configuring the repository connection. 33 | * - `repoUrl` (optional): The URL of the hosting repository. 34 | * - `branchName` (optional): The name of the branch in the repository. 35 | * - `framework` (optional): The framework used for hosting. 36 | * - `s3bucket` (optional): The name of the Amazon S3 bucket for hosting content. 37 | * - `s3path` (optional): The path within the S3 bucket where content is stored. 38 | * - `domainName` (optional): The domain name associated with the hosting. 39 | * - `hostedZoneId` (optional): The ID of the Route 53 hosted zone associated with the domain. 40 | */ 41 | export class RepositoryConnection extends Construct { 42 | public readonly connectionArn: string; 43 | public readonly repoUrl: string; 44 | 45 | constructor( 46 | scope: Construct, 47 | id: string, 48 | hostingConfiguration: HostingConfiguration 49 | ) { 50 | super(scope, id); 51 | 52 | if (isRepoConfig(hostingConfiguration)) { 53 | const repoUrl = hostingConfiguration.repoUrl; 54 | const parsedUrl = parseRepositoryUrl(repoUrl as string); 55 | 56 | if (parsedUrl) { 57 | const { repoName } = parsedUrl; 58 | const conn = new codestarconnections.CfnConnection( 59 | this, 60 | "MyCfnConnection" + repoName, 61 | { 62 | connectionName: calculateCodeStarConnectionStackName( 63 | hostingConfiguration.repoUrl, 64 | hostingConfiguration.branchName 65 | ), 66 | providerType: "GitHub", 67 | } 68 | ); 69 | 70 | this.connectionArn = conn.attrConnectionArn; 71 | this.repoUrl = hostingConfiguration.repoUrl; 72 | 73 | new CfnOutput(this, "ConnectionArn", { 74 | value: conn.attrConnectionArn, 75 | }); 76 | 77 | new CfnOutput(this, "ConnectionName", { 78 | value: conn.connectionName, 79 | }); 80 | 81 | const stackName = calculateConnectionStackName( 82 | hostingConfiguration.repoUrl, 83 | hostingConfiguration.branchName 84 | ); 85 | 86 | new ssm.StringParameter(this, "SSMConnectionArn", { 87 | parameterName: "/" + stackName + "/" + SSM_CONNECTION_ARN_STR, 88 | stringValue: conn.attrConnectionArn, 89 | }); 90 | new ssm.StringParameter(this, "SSMConnectionName", { 91 | parameterName: "/" + stackName + "/" + SSM_CONNECTION_NAME_STR, 92 | stringValue: conn.connectionName, 93 | }); 94 | 95 | new ssm.StringParameter(this, "SSMConnectionRegion", { 96 | parameterName: "/" + stackName + "/" + SSM_CONNECTION_REGION_STR, 97 | stringValue: Aws.REGION, 98 | }); 99 | } 100 | } else { 101 | // Handle case where the URL did not match the expected format 102 | console.log(`The configuration for repository URL is invalid, exiting.`); 103 | process.exit(0); 104 | } 105 | new CfnOutput(this, "HostingRegion", { 106 | value: Aws.REGION, 107 | }); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/repository_stack.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | You may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { 18 | Stack, 19 | StackProps, 20 | } from "aws-cdk-lib"; 21 | 22 | import { Construct } from "constructs"; 23 | 24 | import { RepositoryConnection } from "./repository_connection"; 25 | import { HostingConfiguration } from "../bin/cli/shared/types"; 26 | 27 | export class RepositoryStack extends Stack { 28 | public readonly repositoryConnection: RepositoryConnection; 29 | 30 | constructor( 31 | scope: Construct, 32 | id: string, 33 | hostingConfiguration: HostingConfiguration, 34 | props?: StackProps 35 | ) { 36 | super(scope, id, props); 37 | 38 | this.repositoryConnection = new RepositoryConnection(this, "RepositoryConnection", hostingConfiguration); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/utility.ts: -------------------------------------------------------------------------------- 1 | export function truncateString(inputString: string, maxLength: number): string { 2 | if (inputString.length <= maxLength) { 3 | // No need to truncate, the string is already within the specified length 4 | return inputString; 5 | } else { 6 | // Truncate the string 7 | return inputString.substring(0, maxLength); 8 | } 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aws/cloudfront-hosting-toolkit", 3 | "version": "1.1.22", 4 | "description": "CloudFront Hosting Toolkit offers the convenience of a managed frontend hosting service while retaining full control over the hosting and deployment infrastructure to make it your own.", 5 | "license": "Apache-2.0", 6 | "bin": { 7 | "cloudfront-hosting-toolkit": "./bin/cli/index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/awslabs/cloudfront-hosting-toolkit" 12 | }, 13 | "main": "lib/index.js", 14 | "types": "lib/index.d.ts", 15 | "homepage": "https://github.com/awslabs/cloudfront-hosting-toolkit", 16 | "keywords": [ 17 | "AWS", 18 | "NextJs", 19 | "ReactJs", 20 | "AngularJs", 21 | "VueJs", 22 | "hosting", 23 | "static website", 24 | "infrastructure", 25 | "deployment", 26 | "automation", 27 | "awscdk" 28 | ], 29 | "author": "Corneliu Croitoru", 30 | "scripts": { 31 | "build": "tsc", 32 | "watch": "tsc -w", 33 | "clean": "rm -rf node_modules/ dist/ coverage/ package-lock.json", 34 | "audit": "npm audit && cdk synth | cfn_nag", 35 | "cloudfront-hosting-toolkit": "node bin/cli/index.js", 36 | "test": "jest --coverage", 37 | "zipfile": "node scripts/createDummyZip.js", 38 | "postinstall": "cd lambda/layers/aws_sdk/nodejs && npm install && cd ../../../.. && npm run zipfile", 39 | "postversion": "git push && git push --tags" 40 | }, 41 | "devDependencies": { 42 | "@types/archiver": "5.3.2", 43 | "@types/aws-lambda": "^8.10.133", 44 | "@types/glob": "8.1.0", 45 | "@types/jest": "^29.5.5", 46 | "@types/node": "20.5.9", 47 | "@types/prompts": "2.4.4", 48 | "aws-cdk": "2.163.1", 49 | "aws-sdk-client-mock": "^3.0.0", 50 | "esbuild": "^0.20.0", 51 | "jest": "^29.7.0", 52 | "ts-jest": "^29.1.1", 53 | "ts-node": "10.9.1", 54 | "typescript": "^5.2.2" 55 | }, 56 | "dependencies": { 57 | "@aws-lambda-powertools/logger": "^1.18.0", 58 | "@aws-lambda-powertools/tracer": "^1.18.0", 59 | "@aws-sdk/client-acm": "^3.484.0", 60 | "@aws-sdk/client-cloudfront": "^3.484.0", 61 | "@aws-sdk/client-cloudfront-keyvaluestore": "^3.484.0", 62 | "@aws-sdk/client-codepipeline": "^3.484.0", 63 | "@aws-sdk/client-codestar-connections": "^3.484.0", 64 | "@aws-sdk/client-route-53": "^3.484.0", 65 | "@aws-sdk/client-route-53-domains": "^3.484.0", 66 | "@aws-sdk/client-s3": "^3.484.0", 67 | "@aws-sdk/client-ssm": "^3.484.0", 68 | "@aws-sdk/client-sts": "^3.484.0", 69 | "@aws-sdk/config-resolver": "^3.374.0", 70 | "@aws-sdk/node-config-provider": "^3.374.0", 71 | "@aws-sdk/types": "^3.484.0", 72 | "adm-zip": "^0.5.10", 73 | "aws-cdk": "^2.163.1", 74 | "aws-cdk-lib": "^2.163.1", 75 | "aws-crt": "^1.20.1", 76 | "cdk-nag": "^2.27.129", 77 | "cli-progress": "^3.12.0", 78 | "constructs": "^10.2.70", 79 | "esbuild": "^0.20.0", 80 | "glob": "^10.3.4", 81 | "joi": "^17.10.1", 82 | "prompts": "^2.4.2", 83 | "source-map-support": "^0.5.21", 84 | "ts-node": "^10.9.1", 85 | "yaml": "^2.3.2", 86 | "yargs": "^17.7.2" 87 | }, 88 | "preferGlobal": true 89 | } 90 | -------------------------------------------------------------------------------- /resources/build_config_templates/hosting_angularjs.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - aws --version 7 | - node -v 8 | - ls 9 | - npm install 10 | - npm install -g @angular/cli 11 | - ng build 12 | - cd dist/static-frontend-angularjs 13 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 14 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/build_config_templates/hosting_basic.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 7 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/build_config_templates/hosting_nextjs.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - n install 18.18.0 7 | - npx npm install 8 | - npx next build 9 | - cd out 10 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 11 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 12 | 13 | -------------------------------------------------------------------------------- /resources/build_config_templates/hosting_reactjs.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - npx npm install 7 | - npx npm run build 8 | - cd build 9 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 10 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/build_config_templates/hosting_vuejs.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - npx npm install 7 | - npx npm run build 8 | - cd dist 9 | - echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 10 | - aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /resources/build_config_templates/s3_build_config.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | env: 3 | parameter-store: 4 | COMMITID: $SSM_PARAM_COMMITID 5 | S3KEY: $SSM_PARAM_S3_KEY 6 | phases: 7 | build: 8 | commands: 9 | - | 10 | if [ "$COMMITID" = "init" ] && [ "$S3KEY" = "init" ]; then 11 | echo "No zip, this must be the first execution of the pipeline after infrastructure deployment" 12 | else 13 | mkdir temp 14 | aws s3 cp s3://$SRC_BUCKET_NAME/$S3KEY ./temp 15 | cd temp && ls 16 | unzip *.zip 17 | rm *.zip 18 | echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$COMMITID/ --recursive #don't change this line 19 | aws s3 cp ./ s3://$DEST_BUCKET_NAME/$COMMITID/ --recursive #don't change this line 20 | fi 21 | -------------------------------------------------------------------------------- /resources/cff_templates/index_angularjs.js: -------------------------------------------------------------------------------- 1 | import cf from 'cloudfront'; 2 | 3 | const kvsId = '__KVS_ID__'; 4 | 5 | // This fails if the key value store is not associated with the function 6 | const kvsHandle = cf.kvs(kvsId); 7 | 8 | // Function to update the URI to point to index.html 9 | async function updateURI(uri) { 10 | let pathToAdd = ""; 11 | 12 | try { 13 | pathToAdd = await kvsHandle.get("path"); 14 | } catch (err) { 15 | console.log(`No key 'path' present: ${err}`); 16 | return uri; 17 | } 18 | 19 | // Always return index.html 20 | return `/${pathToAdd}/index.html`; 21 | } 22 | 23 | 24 | // Main CloudFront handler 25 | async function handler(event) { 26 | var request = event.request; 27 | var uri = request.uri; 28 | 29 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 30 | request.uri = await updateURI(uri); 31 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 32 | 33 | return request; 34 | } 35 | -------------------------------------------------------------------------------- /resources/cff_templates/index_basic.js: -------------------------------------------------------------------------------- 1 | import cf from 'cloudfront'; 2 | 3 | const kvsId = '__KVS_ID__'; 4 | 5 | // This fails if the key value store is not associated with the function 6 | const kvsHandle = cf.kvs(kvsId); 7 | 8 | function pointsToFile(uri) { 9 | return /\/[^/]+\.[^/]+$/.test(uri); 10 | } 11 | var rulePatterns = { 12 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 13 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 14 | "!file/": "/index.html",// When URI has a trailing slash and doesn't point to a specific file, append 'index.html' 15 | }; 16 | 17 | // Function to determine rule and update the URI 18 | async function updateURI(uri) { 19 | 20 | let pathToAdd = ""; 21 | 22 | try { 23 | pathToAdd = await kvsHandle.get("path"); 24 | } catch (err) { 25 | console.log(`No key 'path' present : ${err}`); 26 | return uri; 27 | } 28 | 29 | // Check for trailing slash and apply rule. 30 | if (uri.endsWith("/") && rulePatterns["/$"]) { 31 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"]; 32 | } 33 | 34 | // Check if URI doesn't point to a specific file. 35 | if (!pointsToFile(uri)) { 36 | // If URI doesn't have a trailing slash, apply rule. 37 | if (!uri.endsWith("/") && rulePatterns["!file"]) { 38 | return "/" + pathToAdd + uri + rulePatterns["!file"]; 39 | } 40 | 41 | // If URI has a trailing slash, apply rule. 42 | if (uri.endsWith("/") && rulePatterns["!file/"]) { 43 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"]; 44 | } 45 | } 46 | 47 | return "/" + pathToAdd + uri; 48 | } 49 | 50 | // Main CloudFront handler 51 | async function handler(event) { 52 | var request = event.request; 53 | var uri = request.uri; 54 | 55 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 56 | request.uri = await updateURI(uri); 57 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 58 | 59 | 60 | 61 | return request; 62 | } 63 | -------------------------------------------------------------------------------- /resources/cff_templates/index_nextjs.js: -------------------------------------------------------------------------------- 1 | import cf from 'cloudfront'; 2 | 3 | const kvsId = '__KVS_ID__'; 4 | 5 | // This fails if the key value store is not associated with the function 6 | const kvsHandle = cf.kvs(kvsId); 7 | 8 | function pointsToFile(uri) { 9 | return /\/[^/]+\.[^/]+$/.test(uri); 10 | } 11 | var rulePatterns = { 12 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 13 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 14 | "!file/": "/index.html",// When URI has a trailing slash and doesn't point to a specific file, append 'index.html' 15 | }; 16 | 17 | // Function to determine rule and update the URI 18 | async function updateURI(uri) { 19 | 20 | let pathToAdd = ""; 21 | 22 | try { 23 | pathToAdd = await kvsHandle.get("path"); 24 | } catch (err) { 25 | console.log(`No key 'path' present : ${err}`); 26 | return uri; 27 | } 28 | 29 | // Check for trailing slash and apply rule. 30 | if (uri.endsWith("/") && rulePatterns["/$"]) { 31 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"]; 32 | } 33 | 34 | // Check if URI doesn't point to a specific file. 35 | if (!pointsToFile(uri)) { 36 | // If URI doesn't have a trailing slash, apply rule. 37 | if (!uri.endsWith("/") && rulePatterns["!file"]) { 38 | return "/" + pathToAdd + uri + rulePatterns["!file"]; 39 | } 40 | 41 | // If URI has a trailing slash, apply rule. 42 | if (uri.endsWith("/") && rulePatterns["!file/"]) { 43 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"]; 44 | } 45 | } 46 | 47 | return "/" + pathToAdd + uri; 48 | } 49 | 50 | // Main CloudFront handler 51 | async function handler(event) { 52 | var request = event.request; 53 | var uri = request.uri; 54 | 55 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 56 | request.uri = await updateURI(uri); 57 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 58 | 59 | 60 | 61 | return request; 62 | } 63 | -------------------------------------------------------------------------------- /resources/cff_templates/index_reactjs.js: -------------------------------------------------------------------------------- 1 | import cf from 'cloudfront'; 2 | 3 | const kvsId = '__KVS_ID__'; 4 | 5 | // This fails if the key value store is not associated with the function 6 | const kvsHandle = cf.kvs(kvsId); 7 | 8 | function pointsToFile(uri) { 9 | return /\/[^/]+\.[^/]+$/.test(uri); 10 | } 11 | 12 | // Function to update the URI to point to index.html 13 | async function updateURI(uri) { 14 | try { 15 | const pathToAdd = await kvsHandle.get("path"); 16 | 17 | if (!pointsToFile(uri)) { 18 | return `/${pathToAdd}/index.html`; 19 | } 20 | 21 | return `/${pathToAdd}${uri}`; 22 | 23 | } catch (err) { 24 | console.log(`No key 'path' present: ${err}`); 25 | return uri; 26 | } 27 | } 28 | 29 | 30 | 31 | // Main CloudFront handler 32 | async function handler(event) { 33 | var request = event.request; 34 | var uri = request.uri; 35 | 36 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 37 | request.uri = await updateURI(uri); 38 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 39 | 40 | 41 | 42 | return request; 43 | } 44 | -------------------------------------------------------------------------------- /resources/cff_templates/index_vuejs.js: -------------------------------------------------------------------------------- 1 | import cf from 'cloudfront'; 2 | 3 | const kvsId = '__KVS_ID__'; 4 | 5 | // This fails if the key value store is not associated with the function 6 | const kvsHandle = cf.kvs(kvsId); 7 | 8 | function pointsToFile(uri) { 9 | return /\/[^/]+\.[^/]+$/.test(uri); 10 | } 11 | var rulePatterns = { 12 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 13 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 14 | "!file/": "/index.html",// When URI has a trailing slash and doesn't point to a specific file, append 'index.html' 15 | }; 16 | 17 | // Function to determine rule and update the URI 18 | async function updateURI(uri) { 19 | 20 | let pathToAdd = ""; 21 | 22 | try { 23 | pathToAdd = await kvsHandle.get("path"); 24 | } catch (err) { 25 | console.log(`No key 'path' present : ${err}`); 26 | return uri; 27 | } 28 | 29 | // Check for trailing slash and apply rule. 30 | if (uri.endsWith("/") && rulePatterns["/$"]) { 31 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"]; 32 | } 33 | 34 | // Check if URI doesn't point to a specific file. 35 | if (!pointsToFile(uri)) { 36 | // If URI doesn't have a trailing slash, apply rule. 37 | if (!uri.endsWith("/") && rulePatterns["!file"]) { 38 | return "/" + pathToAdd + uri + rulePatterns["!file"]; 39 | } 40 | 41 | // If URI has a trailing slash, apply rule. 42 | if (uri.endsWith("/") && rulePatterns["!file/"]) { 43 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"]; 44 | } 45 | } 46 | 47 | return "/" + pathToAdd + uri; 48 | } 49 | 50 | // Main CloudFront handler 51 | async function handler(event) { 52 | var request = event.request; 53 | var uri = request.uri; 54 | 55 | //console.log("URI BEFORE: " + request.uri); // Uncomment if needed 56 | request.uri = await updateURI(uri); 57 | //console.log("URI AFTER: " + request.uri); // Uncomment if needed 58 | 59 | 60 | 61 | return request; 62 | } 63 | -------------------------------------------------------------------------------- /resources/initial_repository/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Your website is currently being deployed 8 | 16 | 17 | 18 |
19 |

Deployment status

20 |
21 |

Please note that your are currently seeing this screen because this is the first deployment of the website. So, just take it easy and unwind, the page will automatically refresh on its own.

22 |
23 |
24 | -------------------------------------------------------------------------------- /resources/initial_s3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Your website is currently being deployed 8 | 16 | 17 | 18 |
19 |

Deployment status

20 |
21 |

Please note that you are currently seeing this screen either because no zip file has been uploaded to your S3 bucket yet, or because the deployment is currently in progress. Please be patient and the page will automatically refresh once the deployment is complete.

22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/s3_trigger/dummy.txt: -------------------------------------------------------------------------------- 1 | This is a dummy file. -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | ## Roadmap 2 | 3 | 4 | This roadmap outlines the planned features and enhancements for the CloudFront Hosting Toolkit project. 5 | 6 | ## Image Optimization 7 | 8 | **Explanation**: Optimization module dedicated to enhancing website performance. This module's function is to reduce image sizes to enhance website performance, ensuring faster page load times and an overall enhanced user experience. 9 | 10 | 11 | ## PR Preview 12 | 13 | **Explanation**: PR Preview (Pull Request Preview) functionality allows developers to preview a branch before merging it into the main codebase. 14 | 15 | 16 | ## Authorization 17 | 18 | **Explanation**: Enhancing authorization for precise user access control and permissions. 19 | 20 | ## A/B Testing 21 | 22 | **Explanation**: A/B testing enables you to compare different versions of your website to determine which performs better. 23 | 24 | ## Instant Rollback 25 | 26 | **Explanation**: Instant rollback capability enables rapid reversion to a prior application version in case of unexpected issues during new deployments. 27 | 28 | ## Server-Side Rendering (SSR) 29 | 30 | **Explanation**: Server-Side Rendering (SSR) enhances website performance and SEO by rendering pages on the server before sending them to the client. 31 | 32 | ## Routes 33 | 34 | **Explanation**: Routing features offer navigation to backend resources. 35 | 36 | 37 | This roadmap outlines key features, but we are open to incorporating new ideas based on your feedback. We remain dedicated to the ongoing improvement of CloudFront Hosting Toolkit, with the goal of empowering you to create and host exceptional web applications. We are excited about implementing these enhancements to provide an even better hosting experience for our users -------------------------------------------------------------------------------- /scripts/createDummyZip.js: -------------------------------------------------------------------------------- 1 | // scripts/createZipArchive.js 2 | 3 | const fs = require("fs"); 4 | const AdmZip = require("adm-zip"); 5 | 6 | console.log("createZipArchive"); 7 | const outputFile = "resources/s3_trigger/dummy.zip"; // Adjust the path as needed 8 | 9 | // Check if the zip file already exists 10 | if (fs.existsSync(outputFile)) { 11 | console.log("Zip file already exists."); 12 | return; 13 | } 14 | 15 | console.log("Creating new zip file..."); 16 | 17 | const zip = new AdmZip(); 18 | 19 | try { 20 | zip.addLocalFile("resources/s3_trigger/dummy.txt"); // Adjust the path as needed 21 | zip.writeZip(outputFile); 22 | console.log("Zip file written successfully."); 23 | } catch (error) { 24 | console.error("Error writing zip file:", error); 25 | process.exit(1); 26 | } 27 | -------------------------------------------------------------------------------- /solution.context.json.template.github: -------------------------------------------------------------------------------- 1 | { 2 | "repoUrl": "https://github.com/USERNAME/REPOSITORY.git", 3 | "branchName": "main", 4 | "framework": "nextjs" 5 | } 6 | -------------------------------------------------------------------------------- /solution.context.json.template.s3: -------------------------------------------------------------------------------- 1 | { 2 | "s3bucket": "test-bucket-source-build", 3 | "s3path": "temp/" 4 | } -------------------------------------------------------------------------------- /test/cloudfront-hosting-toolkit/angularjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-frontend-angularjs", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^15.1.0", 14 | "@angular/common": "^15.1.0", 15 | "@angular/compiler": "^15.1.0", 16 | "@angular/core": "^15.1.0", 17 | "@angular/forms": "^15.1.0", 18 | "@angular/platform-browser": "^15.1.0", 19 | "@angular/platform-browser-dynamic": "^15.1.0", 20 | "@angular/router": "^15.1.0", 21 | "rxjs": "~7.8.0", 22 | "tslib": "^2.3.0", 23 | "zone.js": "~0.12.0" 24 | }, 25 | "devDependencies": { 26 | "@angular-devkit/build-angular": "^15.1.4", 27 | "@angular/cli": "~15.1.4", 28 | "@angular/compiler-cli": "^15.1.0", 29 | "@types/jasmine": "~4.3.0", 30 | "jasmine-core": "~4.5.0", 31 | "karma": "~6.4.0", 32 | "karma-chrome-launcher": "~3.1.0", 33 | "karma-coverage": "~2.2.0", 34 | "karma-jasmine": "~5.1.0", 35 | "karma-jasmine-html-reporter": "~2.0.0", 36 | "typescript": "~4.9.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/cloudfront-hosting-toolkit/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build && next export", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "cloudfront-pages": "file:../aws-solution-for-hosting-frontend/cloudfront-pages-0.0.1.tgz", 13 | "gray-matter": "^4.0.3", 14 | "markdown-it": "^12.3.2", 15 | "next": "^12.2.4", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2" 18 | }, 19 | "devDependencies": { 20 | "@tailwindcss/typography": "^0.5.0", 21 | "autoprefixer": "^10.4.2", 22 | "eslint": "8.7.0", 23 | "eslint-config-next": "12.0.8", 24 | "postcss": "^8.4.5", 25 | "tailwindcss": "^3.0.15" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/cloudfront-hosting-toolkit/no_framework/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/cloudfront-hosting-toolkit/199ce3142ba9f9c28f2849b78df387c8e59a26ec/test/cloudfront-hosting-toolkit/no_framework/index.html -------------------------------------------------------------------------------- /test/cloudfront-hosting-toolkit/reactjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-frontend-reactjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/cloudfront-hosting-toolkit/vuejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-frontend-vuejs", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Corneliu CROITORU ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "vue": "^2.5.2", 15 | "vue-router": "^3.0.1" 16 | }, 17 | "devDependencies": { 18 | "autoprefixer": "^7.1.2", 19 | "babel-core": "^6.22.1", 20 | "babel-eslint": "^8.2.1", 21 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 22 | "babel-loader": "^7.1.1", 23 | "babel-plugin-syntax-jsx": "^6.18.0", 24 | "babel-plugin-transform-runtime": "^6.22.0", 25 | "babel-plugin-transform-vue-jsx": "^3.5.0", 26 | "babel-preset-env": "^1.3.2", 27 | "babel-preset-stage-2": "^6.22.0", 28 | "chalk": "^2.0.1", 29 | "copy-webpack-plugin": "^4.0.1", 30 | "css-loader": "^0.28.0", 31 | "eslint": "^4.15.0", 32 | "eslint-config-standard": "^10.2.1", 33 | "eslint-friendly-formatter": "^3.0.0", 34 | "eslint-loader": "^1.7.1", 35 | "eslint-plugin-import": "^2.7.0", 36 | "eslint-plugin-node": "^5.2.0", 37 | "eslint-plugin-promise": "^3.4.0", 38 | "eslint-plugin-standard": "^3.0.1", 39 | "eslint-plugin-vue": "^4.0.0", 40 | "extract-text-webpack-plugin": "^3.0.0", 41 | "file-loader": "^1.1.4", 42 | "friendly-errors-webpack-plugin": "^1.6.1", 43 | "html-webpack-plugin": "^2.30.1", 44 | "node-notifier": "^5.1.2", 45 | "optimize-css-assets-webpack-plugin": "^3.2.0", 46 | "ora": "^1.2.0", 47 | "portfinder": "^1.0.13", 48 | "postcss-import": "^11.0.0", 49 | "postcss-loader": "^2.0.8", 50 | "postcss-url": "^7.2.1", 51 | "rimraf": "^2.6.0", 52 | "semver": "^5.3.0", 53 | "shelljs": "^0.7.6", 54 | "uglifyjs-webpack-plugin": "^1.1.1", 55 | "url-loader": "^0.5.8", 56 | "vue-loader": "^13.3.0", 57 | "vue-style-loader": "^3.0.1", 58 | "vue-template-compiler": "^2.5.2", 59 | "webpack": "^3.6.0", 60 | "webpack-bundle-analyzer": "^2.9.0", 61 | "webpack-dev-server": "^2.9.1", 62 | "webpack-merge": "^4.1.0" 63 | }, 64 | "engines": { 65 | "node": ">= 6.0.0", 66 | "npm": ">= 3.0.0" 67 | }, 68 | "browserslist": [ 69 | "> 1%", 70 | "last 2 versions", 71 | "not ie <= 8" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /test/detect_framework.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | } from "../bin/cli/actions/init"; 3 | import * as path from "path"; 4 | import { detectFrontendFramework } from "../bin/cli/utils/helper"; 5 | 6 | 7 | describe("detect framework", () => { 8 | 9 | 10 | test("Detect NextJs framework", async () => { 11 | 12 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/nextjs"); 13 | const framework = await detectFrontendFramework(nextJsFolderPath); 14 | console.log(framework); 15 | expect(framework).toEqual("nextjs") 16 | }); 17 | 18 | test("Detect VueJs framework", async () => { 19 | 20 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/vuejs"); 21 | const framework = await detectFrontendFramework(nextJsFolderPath); 22 | console.log(framework); 23 | expect(framework).toEqual("vuejs") 24 | }); 25 | 26 | test("Detect ReactJS legacy framework", async () => { 27 | 28 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/reactjs"); 29 | const framework = await detectFrontendFramework(nextJsFolderPath); 30 | console.log(framework); 31 | expect(framework).toEqual("reactjs") 32 | }); 33 | 34 | test("Detect AngularJS framework", async () => { 35 | 36 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/angularjs"); 37 | const framework = await detectFrontendFramework(nextJsFolderPath); 38 | console.log(framework); 39 | expect(framework).toEqual("angularjs") 40 | }); 41 | 42 | test("Detect No framework", async () => { 43 | 44 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/no_framework"); 45 | const framework = await detectFrontendFramework(nextJsFolderPath); 46 | console.log(framework); 47 | expect(framework).toEqual("basic") 48 | }); 49 | 50 | test("Empty folder", async () => { 51 | 52 | const nextJsFolderPath = path.join(__dirname, "./cloudfront-hosting-toolkit/empty_folder"); 53 | const framework = await detectFrontendFramework(nextJsFolderPath); 54 | console.log(framework); 55 | expect(framework).toEqual("") 56 | }); 57 | 58 | 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /test/hosting_stack.test.ts: -------------------------------------------------------------------------------- 1 | import { Template } from "aws-cdk-lib/assertions"; 2 | import { App } from "aws-cdk-lib"; 3 | import { HostingStack } from "../lib/hosting_stack"; 4 | import * as path from "path"; 5 | 6 | describe("hosting stack", () => { 7 | test("No domain name", () => { 8 | const app = new App(); 9 | 10 | const hostingConfiguration = { 11 | repoUrl: "https://github.com/cornelcroi/static-frontend-vuejs.git", 12 | branchName: "dev", 13 | framework: "vuejs", 14 | }; 15 | 16 | const hostingStack = new HostingStack(app, "HostingStack", { 17 | connectionArn: 18 | "arn:aws:codestar-connections:eu-west-1:123456:connection/abc", 19 | hostingConfiguration: hostingConfiguration, 20 | buildFilePath: path.join(__dirname, "./cloudfront-hosting-toolkit/cloudfront-hosting-toolkit-build.yml"), 21 | cffSourceFilePath: path.join(__dirname, "./cloudfront-hosting-toolkit/cloudfront-hosting-toolkit-cff.js"), 22 | certificateArn: "", 23 | }); 24 | 25 | const autoTemplate = Template.fromStack(hostingStack); 26 | 27 | autoTemplate.resourceCountIs("AWS::CodePipeline::Pipeline", 1); 28 | autoTemplate.resourceCountIs("AWS::StepFunctions::StateMachine", 1); 29 | autoTemplate.resourceCountIs("AWS::Lambda::Function", 5); 30 | 31 | autoTemplate.resourceCountIs("AWS::CloudFront::Function", 1); 32 | autoTemplate.resourceCountIs("AWS::S3::Bucket", 2); 33 | autoTemplate.resourceCountIs("AWS::CloudFront::OriginAccessControl", 1); 34 | 35 | autoTemplate.resourceCountIs("AWS::CloudFront::Distribution", 1); 36 | }); 37 | 38 | test("with domain name", () => { 39 | const app = new App(); 40 | 41 | const hostingConfiguration = { 42 | repoUrl: "https://github.com/cornelcroi/static-frontend-vuejs.git", 43 | branchName: "dev", 44 | framework: "vuejs", 45 | domainName: "example.com", 46 | }; 47 | 48 | const hostingStack = new HostingStack(app, "HostingStack", { 49 | connectionArn: 50 | "arn:aws:codestar-connections:eu-west-1:123456:connection/abc", 51 | hostingConfiguration: hostingConfiguration, 52 | buildFilePath: path.join(__dirname, "./cloudfront-hosting-toolkit/cloudfront-hosting-toolkit-build.yml"), 53 | cffSourceFilePath: path.join(__dirname, "./cloudfront-hosting-toolkit/cloudfront-hosting-toolkit-cff.js"), 54 | certificateArn: 55 | "arn:aws:acm:us-east-1:123456:certificate/abc", 56 | }); 57 | 58 | const autoTemplate = Template.fromStack(hostingStack); 59 | const distributions = autoTemplate.findResources( 60 | "AWS::CloudFront::Distribution" 61 | ); 62 | console.log(JSON.stringify(distributions, null, 2)); 63 | 64 | autoTemplate.resourceCountIs("AWS::CodePipeline::Pipeline", 1); 65 | autoTemplate.resourceCountIs("AWS::StepFunctions::StateMachine", 1); 66 | autoTemplate.resourceCountIs("AWS::Lambda::Function", 5); 67 | 68 | autoTemplate.resourceCountIs("AWS::CloudFront::Function", 1); 69 | autoTemplate.resourceCountIs("AWS::S3::Bucket", 2); 70 | autoTemplate.resourceCountIs("AWS::CloudFront::OriginAccessControl", 1); 71 | 72 | autoTemplate.resourceCountIs("AWS::CloudFront::Distribution", 1); 73 | 74 | autoTemplate.hasResourceProperties("AWS::CloudFront::Distribution", { 75 | DistributionConfig: { 76 | Aliases: ["example.com", "www.example.com"], 77 | ViewerCertificate: { 78 | AcmCertificateArn: 79 | "arn:aws:acm:us-east-1:123456:certificate/abc", 80 | }, 81 | }, 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/repository_stack.test.ts: -------------------------------------------------------------------------------- 1 | import { Template } from "aws-cdk-lib/assertions"; 2 | import { App } from "aws-cdk-lib"; 3 | import { RepositoryStack } from "../lib/repository_stack"; 4 | import { calculateCodeStarConnectionStackName, calculateConnectionStackName } from "../bin/cli/utils/helper"; 5 | 6 | test("Repository Stack", () => { 7 | // WHEN 8 | const app = new App(); 9 | 10 | const hostingConfiguration = { 11 | repoUrl: "https://github.com/cornelcroi/static-frontend-vuejs.git", 12 | branchName: "dev", 13 | framework: "vuejs" 14 | }; 15 | 16 | const repositoryStack = new RepositoryStack( 17 | app, 18 | "RepositoryStack", 19 | hostingConfiguration 20 | ); 21 | 22 | const autoTemplate = Template.fromStack(repositoryStack); 23 | console.log(JSON.stringify(autoTemplate)) 24 | 25 | const connectionName = calculateCodeStarConnectionStackName(hostingConfiguration.repoUrl, hostingConfiguration.branchName); 26 | 27 | autoTemplate.resourceCountIs("AWS::CodeStarConnections::Connection", 1); 28 | autoTemplate.resourceCountIs("AWS::SSM::Parameter", 3); 29 | autoTemplate.hasResourceProperties('AWS::CodeStarConnections::Connection', { 30 | ProviderType: 'GitHub', 31 | ConnectionName: connectionName 32 | }); 33 | 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /test/uri_rewrite.test.ts: -------------------------------------------------------------------------------- 1 | import {} from "../bin/cli/actions/init"; 2 | 3 | describe("Test URI rewrites", () => { 4 | // URL rewriting rules 5 | function pointsToFile(uri: string) { 6 | return /\/[^/]+\.[^/]+$/.test(uri); 7 | } 8 | var rulePatterns = { 9 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 10 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 11 | "!file/": "/index.html" 12 | }; 13 | 14 | const pathToAdd = "myFolderName"; 15 | 16 | // Function to determine rule and update the URI 17 | function updateURI(uri: string) { 18 | // Check for trailing slash and apply rule. 19 | if (uri.endsWith("/") && rulePatterns["/$"]) { 20 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"]; 21 | } 22 | 23 | // Check if URI doesn't point to a specific file. 24 | if (!pointsToFile(uri)) { 25 | // If URI doesn't have a trailing slash, apply rule. 26 | if (!uri.endsWith("/") && rulePatterns["!file"]) { 27 | return "/" + pathToAdd + uri + rulePatterns["!file"]; 28 | } 29 | 30 | // If URI has a trailing slash, apply rule. 31 | if (uri.endsWith("/") && rulePatterns["!file/"]) { 32 | return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"]; 33 | } 34 | 35 | } 36 | 37 | // Default return 38 | return "/" + pathToAdd + uri; 39 | } 40 | 41 | test("test uri rewrite", async () => { 42 | const testCases = [ 43 | { input: "/", expected: "/myFolderName/index.html" }, 44 | { input: "/test", expected: "/myFolderName/test.html" }, 45 | { input: "/test/", expected: "/myFolderName/test/index.html" }, 46 | { input: "/test.jpg", expected: "/myFolderName/test.jpg" }, 47 | { input: "/folder/", expected: "/myFolderName/folder/index.html" }, 48 | { 49 | input: "/folder/page", 50 | expected: "/myFolderName/folder/page.html", 51 | }, 52 | 53 | // Add more test cases as required 54 | ]; 55 | 56 | testCases.forEach((testCase, index) => { 57 | const result = updateURI(testCase.input); 58 | expect(result).toEqual(testCase.expected); // This will assert if the result matches the expected value. 59 | }); 60 | }); 61 | }); 62 | 63 | 64 | describe("Test URI SPA rewrites", () => { 65 | // URL rewriting rules 66 | function pointsToFile(uri: string) { 67 | return /\/[^/]+\.[^/]+$/.test(uri); 68 | } 69 | var rulePatterns = { 70 | "/$": "/index.html", // When URI ends with a '/', append 'index.html' 71 | "!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html' 72 | "!file/": "/index.html" 73 | }; 74 | 75 | const pathToAdd = "myFolderName"; 76 | 77 | // Function to determine rule and update the URI 78 | function updateURI(uri: string) { 79 | // Check for trailing slash and apply rule. 80 | if (!pointsToFile(uri)) { 81 | return `/${pathToAdd}/index.html`; 82 | } 83 | 84 | return `/${pathToAdd}${uri}`; 85 | } 86 | 87 | test("test uri SPA rewrite", async () => { 88 | const testCases = [ 89 | { input: "/", expected: "/myFolderName/index.html" }, 90 | { input: "/test", expected: "/myFolderName/index.html" }, 91 | { input: "/test/", expected: "/myFolderName/index.html" }, 92 | { input: "/test.jpg", expected: "/myFolderName/test.jpg" }, 93 | { input: "/folder/", expected: "/myFolderName/index.html" }, 94 | { 95 | input: "/folder/page", 96 | expected: "/myFolderName/index.html", 97 | }, 98 | 99 | // Add more test cases as required 100 | ]; 101 | 102 | testCases.forEach((testCase, index) => { 103 | const result = updateURI(testCase.input); 104 | expect(result).toEqual(testCase.expected); // This will assert if the result matches the expected value. 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": [ 4 | "node", "jest" 5 | ], 6 | "target": "ES2018", 7 | "module": "commonjs", 8 | "lib": ["es2018", "dom"], 9 | "declaration": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": false, 19 | "inlineSourceMap": true, 20 | "inlineSources": true, 21 | "experimentalDecorators": true, 22 | "strictPropertyInitialization": false, 23 | "resolveJsonModule": true, 24 | "esModuleInterop": true, 25 | //"typeRoots": ["./node_modules/@types"] 26 | }, 27 | "exclude": ["cdk.out", "node_modules", "dist", "test", "docs"] 28 | } 29 | --------------------------------------------------------------------------------