├── .eslintignore ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .jsii ├── .npmignore ├── LICENSE ├── README.md ├── cdk.context.json ├── cdk.json ├── jest.config.js ├── lib ├── index.ts └── spa-deploy │ └── spa-deploy-construct.ts ├── package-lock.json ├── package.json ├── test └── cdk-spa-deploy.test.ts ├── tsconfig.json └── website └── index.html /.eslintignore: -------------------------------------------------------------------------------- 1 | cdk.out/* 2 | **/*.d.ts 3 | **/*.d.tsx 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "airbnb-base", 5 | "eslint:recommended", 6 | "plugin:jest/recommended" 7 | ], 8 | "plugins": [ 9 | "jest" 10 | ], 11 | "rules": { 12 | "import/no-unresolved": 0, 13 | "import/extensions":0, 14 | "class-methods-use-this": 0, 15 | "no-new": 0, 16 | "max-len": 0, 17 | "jest/no-disabled-tests": 2, 18 | "jest/no-focused-tests": 2, 19 | "jest/no-identical-title": 2, 20 | "jest/prefer-to-have-length": 1, 21 | "jest/valid-expect": 2, 22 | "jest/expect-expect": 0, 23 | "jest/consistent-test-it": [ 24 | 2, 25 | { 26 | "fn": "test", 27 | "withinDescribe": "it" 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: "npm" 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: "/" 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | push: 8 | tags: 9 | - '*' 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | container: 21 | image: jsii/superchain 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | # Run the tests 29 | - name: Run the tests 30 | run: npm i && npm run build && npm run test 31 | 32 | # Runs a single command using the runners shell 33 | - name: Build and Package JSII modules 34 | run: npm i && npm run build && npm run package 35 | 36 | # Release to NPM 37 | - name: Release NPM 38 | run: npx -p jsii-release jsii-release-npm 39 | env: 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | 42 | # Release to Pypi 43 | - name: Release Pypi 44 | run: npx -p jsii-release jsii-release-pypi 45 | env: 46 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 47 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 48 | 49 | # Release to Nuget 50 | - name: Release Nuget 51 | run: npx -p jsii-release jsii-release-nuget 52 | env: 53 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 54 | 55 | # Release to Maven 56 | - name: Release Maven 57 | run: npx -p jsii-release jsii-release-maven 58 | env: 59 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 60 | MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 61 | MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 62 | MAVEN_GPG_PRIVATE_KEY_PASSPHRASE: ${{ secrets.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE }} 63 | MAVEN_STAGING_PROFILE_ID: ${{ secrets.MAVEN_STAGING_PROFILE_ID }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | dist 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | -------------------------------------------------------------------------------- /.jsii: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "hi@cdkpatterns.com", 4 | "roles": [ 5 | "author" 6 | ] 7 | }, 8 | "dependencies": { 9 | "@aws-cdk/aws-certificatemanager": "^1.103.0", 10 | "@aws-cdk/aws-cloudfront": "^1.103.0", 11 | "@aws-cdk/aws-iam": "^1.103.0", 12 | "@aws-cdk/aws-route53": "^1.103.0", 13 | "@aws-cdk/aws-route53-patterns": "^1.103.0", 14 | "@aws-cdk/aws-route53-targets": "^1.103.0", 15 | "@aws-cdk/aws-s3": "^1.103.0", 16 | "@aws-cdk/aws-s3-deployment": "^1.103.0", 17 | "@aws-cdk/core": "^1.103.0", 18 | "constructs": "^3.3.75" 19 | }, 20 | "dependencyClosure": { 21 | "@aws-cdk/assets": { 22 | "targets": { 23 | "dotnet": { 24 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 25 | "namespace": "Amazon.CDK.Assets", 26 | "packageId": "Amazon.CDK.Assets" 27 | }, 28 | "java": { 29 | "maven": { 30 | "artifactId": "cdk-assets", 31 | "groupId": "software.amazon.awscdk" 32 | }, 33 | "package": "software.amazon.awscdk.assets" 34 | }, 35 | "js": { 36 | "npm": "@aws-cdk/assets" 37 | }, 38 | "python": { 39 | "classifiers": [ 40 | "Framework :: AWS CDK", 41 | "Framework :: AWS CDK :: 1" 42 | ], 43 | "distName": "aws-cdk.assets", 44 | "module": "aws_cdk.assets" 45 | } 46 | } 47 | }, 48 | "@aws-cdk/aws-apigateway": { 49 | "targets": { 50 | "dotnet": { 51 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 52 | "namespace": "Amazon.CDK.AWS.APIGateway", 53 | "packageId": "Amazon.CDK.AWS.APIGateway" 54 | }, 55 | "java": { 56 | "maven": { 57 | "artifactId": "apigateway", 58 | "groupId": "software.amazon.awscdk" 59 | }, 60 | "package": "software.amazon.awscdk.services.apigateway" 61 | }, 62 | "js": { 63 | "npm": "@aws-cdk/aws-apigateway" 64 | }, 65 | "python": { 66 | "classifiers": [ 67 | "Framework :: AWS CDK", 68 | "Framework :: AWS CDK :: 1" 69 | ], 70 | "distName": "aws-cdk.aws-apigateway", 71 | "module": "aws_cdk.aws_apigateway" 72 | } 73 | } 74 | }, 75 | "@aws-cdk/aws-applicationautoscaling": { 76 | "targets": { 77 | "dotnet": { 78 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 79 | "namespace": "Amazon.CDK.AWS.ApplicationAutoScaling", 80 | "packageId": "Amazon.CDK.AWS.ApplicationAutoScaling" 81 | }, 82 | "java": { 83 | "maven": { 84 | "artifactId": "applicationautoscaling", 85 | "groupId": "software.amazon.awscdk" 86 | }, 87 | "package": "software.amazon.awscdk.services.applicationautoscaling" 88 | }, 89 | "js": { 90 | "npm": "@aws-cdk/aws-applicationautoscaling" 91 | }, 92 | "python": { 93 | "classifiers": [ 94 | "Framework :: AWS CDK", 95 | "Framework :: AWS CDK :: 1" 96 | ], 97 | "distName": "aws-cdk.aws-applicationautoscaling", 98 | "module": "aws_cdk.aws_applicationautoscaling" 99 | } 100 | } 101 | }, 102 | "@aws-cdk/aws-autoscaling-common": { 103 | "targets": { 104 | "dotnet": { 105 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 106 | "namespace": "Amazon.CDK.AWS.AutoScaling.Common", 107 | "packageId": "Amazon.CDK.AWS.AutoScaling.Common" 108 | }, 109 | "java": { 110 | "maven": { 111 | "artifactId": "autoscaling-common", 112 | "groupId": "software.amazon.awscdk" 113 | }, 114 | "package": "software.amazon.awscdk.services.autoscaling.common" 115 | }, 116 | "js": { 117 | "npm": "@aws-cdk/aws-autoscaling-common" 118 | }, 119 | "python": { 120 | "classifiers": [ 121 | "Framework :: AWS CDK", 122 | "Framework :: AWS CDK :: 1" 123 | ], 124 | "distName": "aws-cdk.aws-autoscaling-common", 125 | "module": "aws_cdk.aws_autoscaling_common" 126 | } 127 | } 128 | }, 129 | "@aws-cdk/aws-certificatemanager": { 130 | "targets": { 131 | "dotnet": { 132 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 133 | "namespace": "Amazon.CDK.AWS.CertificateManager", 134 | "packageId": "Amazon.CDK.AWS.CertificateManager" 135 | }, 136 | "java": { 137 | "maven": { 138 | "artifactId": "certificatemanager", 139 | "groupId": "software.amazon.awscdk" 140 | }, 141 | "package": "software.amazon.awscdk.services.certificatemanager" 142 | }, 143 | "js": { 144 | "npm": "@aws-cdk/aws-certificatemanager" 145 | }, 146 | "python": { 147 | "classifiers": [ 148 | "Framework :: AWS CDK", 149 | "Framework :: AWS CDK :: 1" 150 | ], 151 | "distName": "aws-cdk.aws-certificatemanager", 152 | "module": "aws_cdk.aws_certificatemanager" 153 | } 154 | } 155 | }, 156 | "@aws-cdk/aws-cloudformation": { 157 | "targets": { 158 | "dotnet": { 159 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 160 | "namespace": "Amazon.CDK.AWS.CloudFormation", 161 | "packageId": "Amazon.CDK.AWS.CloudFormation" 162 | }, 163 | "java": { 164 | "maven": { 165 | "artifactId": "cloudformation", 166 | "groupId": "software.amazon.awscdk" 167 | }, 168 | "package": "software.amazon.awscdk.services.cloudformation" 169 | }, 170 | "js": { 171 | "npm": "@aws-cdk/aws-cloudformation" 172 | }, 173 | "python": { 174 | "classifiers": [ 175 | "Framework :: AWS CDK", 176 | "Framework :: AWS CDK :: 1" 177 | ], 178 | "distName": "aws-cdk.aws-cloudformation", 179 | "module": "aws_cdk.aws_cloudformation" 180 | } 181 | } 182 | }, 183 | "@aws-cdk/aws-cloudfront": { 184 | "submodules": { 185 | "@aws-cdk/aws-cloudfront.experimental": { 186 | "locationInModule": { 187 | "filename": "lib/index.ts", 188 | "line": 11 189 | } 190 | } 191 | }, 192 | "targets": { 193 | "dotnet": { 194 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 195 | "namespace": "Amazon.CDK.AWS.CloudFront", 196 | "packageId": "Amazon.CDK.AWS.CloudFront" 197 | }, 198 | "java": { 199 | "maven": { 200 | "artifactId": "cloudfront", 201 | "groupId": "software.amazon.awscdk" 202 | }, 203 | "package": "software.amazon.awscdk.services.cloudfront" 204 | }, 205 | "js": { 206 | "npm": "@aws-cdk/aws-cloudfront" 207 | }, 208 | "python": { 209 | "classifiers": [ 210 | "Framework :: AWS CDK", 211 | "Framework :: AWS CDK :: 1" 212 | ], 213 | "distName": "aws-cdk.aws-cloudfront", 214 | "module": "aws_cdk.aws_cloudfront" 215 | } 216 | } 217 | }, 218 | "@aws-cdk/aws-cloudwatch": { 219 | "targets": { 220 | "dotnet": { 221 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 222 | "namespace": "Amazon.CDK.AWS.CloudWatch", 223 | "packageId": "Amazon.CDK.AWS.CloudWatch" 224 | }, 225 | "java": { 226 | "maven": { 227 | "artifactId": "cloudwatch", 228 | "groupId": "software.amazon.awscdk" 229 | }, 230 | "package": "software.amazon.awscdk.services.cloudwatch" 231 | }, 232 | "js": { 233 | "npm": "@aws-cdk/aws-cloudwatch" 234 | }, 235 | "python": { 236 | "classifiers": [ 237 | "Framework :: AWS CDK", 238 | "Framework :: AWS CDK :: 1" 239 | ], 240 | "distName": "aws-cdk.aws-cloudwatch", 241 | "module": "aws_cdk.aws_cloudwatch" 242 | } 243 | } 244 | }, 245 | "@aws-cdk/aws-codeguruprofiler": { 246 | "targets": { 247 | "dotnet": { 248 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 249 | "namespace": "Amazon.CDK.AWS.CodeGuruProfiler", 250 | "packageId": "Amazon.CDK.AWS.CodeGuruProfiler" 251 | }, 252 | "java": { 253 | "maven": { 254 | "artifactId": "codeguruprofiler", 255 | "groupId": "software.amazon.awscdk" 256 | }, 257 | "package": "software.amazon.awscdk.services.codeguruprofiler" 258 | }, 259 | "js": { 260 | "npm": "@aws-cdk/aws-codeguruprofiler" 261 | }, 262 | "python": { 263 | "classifiers": [ 264 | "Framework :: AWS CDK", 265 | "Framework :: AWS CDK :: 1" 266 | ], 267 | "distName": "aws-cdk.aws-codeguruprofiler", 268 | "module": "aws_cdk.aws_codeguruprofiler" 269 | } 270 | } 271 | }, 272 | "@aws-cdk/aws-cognito": { 273 | "targets": { 274 | "dotnet": { 275 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 276 | "namespace": "Amazon.CDK.AWS.Cognito", 277 | "packageId": "Amazon.CDK.AWS.Cognito" 278 | }, 279 | "java": { 280 | "maven": { 281 | "artifactId": "cognito", 282 | "groupId": "software.amazon.awscdk" 283 | }, 284 | "package": "software.amazon.awscdk.services.cognito" 285 | }, 286 | "js": { 287 | "npm": "@aws-cdk/aws-cognito" 288 | }, 289 | "python": { 290 | "classifiers": [ 291 | "Framework :: AWS CDK", 292 | "Framework :: AWS CDK :: 1" 293 | ], 294 | "distName": "aws-cdk.aws-cognito", 295 | "module": "aws_cdk.aws_cognito" 296 | } 297 | } 298 | }, 299 | "@aws-cdk/aws-ec2": { 300 | "targets": { 301 | "dotnet": { 302 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 303 | "namespace": "Amazon.CDK.AWS.EC2", 304 | "packageId": "Amazon.CDK.AWS.EC2" 305 | }, 306 | "java": { 307 | "maven": { 308 | "artifactId": "ec2", 309 | "groupId": "software.amazon.awscdk" 310 | }, 311 | "package": "software.amazon.awscdk.services.ec2" 312 | }, 313 | "js": { 314 | "npm": "@aws-cdk/aws-ec2" 315 | }, 316 | "python": { 317 | "classifiers": [ 318 | "Framework :: AWS CDK", 319 | "Framework :: AWS CDK :: 1" 320 | ], 321 | "distName": "aws-cdk.aws-ec2", 322 | "module": "aws_cdk.aws_ec2" 323 | } 324 | } 325 | }, 326 | "@aws-cdk/aws-ecr": { 327 | "targets": { 328 | "dotnet": { 329 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 330 | "namespace": "Amazon.CDK.AWS.ECR", 331 | "packageId": "Amazon.CDK.AWS.ECR" 332 | }, 333 | "java": { 334 | "maven": { 335 | "artifactId": "ecr", 336 | "groupId": "software.amazon.awscdk" 337 | }, 338 | "package": "software.amazon.awscdk.services.ecr" 339 | }, 340 | "js": { 341 | "npm": "@aws-cdk/aws-ecr" 342 | }, 343 | "python": { 344 | "classifiers": [ 345 | "Framework :: AWS CDK", 346 | "Framework :: AWS CDK :: 1" 347 | ], 348 | "distName": "aws-cdk.aws-ecr", 349 | "module": "aws_cdk.aws_ecr" 350 | } 351 | } 352 | }, 353 | "@aws-cdk/aws-ecr-assets": { 354 | "targets": { 355 | "dotnet": { 356 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 357 | "namespace": "Amazon.CDK.AWS.Ecr.Assets", 358 | "packageId": "Amazon.CDK.ECR.Assets" 359 | }, 360 | "java": { 361 | "maven": { 362 | "artifactId": "ecr-assets", 363 | "groupId": "software.amazon.awscdk" 364 | }, 365 | "package": "software.amazon.awscdk.services.ecr.assets" 366 | }, 367 | "js": { 368 | "npm": "@aws-cdk/aws-ecr-assets" 369 | }, 370 | "python": { 371 | "classifiers": [ 372 | "Framework :: AWS CDK", 373 | "Framework :: AWS CDK :: 1" 374 | ], 375 | "distName": "aws-cdk.aws-ecr-assets", 376 | "module": "aws_cdk.aws_ecr_assets" 377 | } 378 | } 379 | }, 380 | "@aws-cdk/aws-efs": { 381 | "targets": { 382 | "dotnet": { 383 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 384 | "namespace": "Amazon.CDK.AWS.EFS", 385 | "packageId": "Amazon.CDK.AWS.EFS" 386 | }, 387 | "java": { 388 | "maven": { 389 | "artifactId": "efs", 390 | "groupId": "software.amazon.awscdk" 391 | }, 392 | "package": "software.amazon.awscdk.services.efs" 393 | }, 394 | "js": { 395 | "npm": "@aws-cdk/aws-efs" 396 | }, 397 | "python": { 398 | "classifiers": [ 399 | "Framework :: AWS CDK", 400 | "Framework :: AWS CDK :: 1" 401 | ], 402 | "distName": "aws-cdk.aws-efs", 403 | "module": "aws_cdk.aws_efs" 404 | } 405 | } 406 | }, 407 | "@aws-cdk/aws-elasticloadbalancing": { 408 | "targets": { 409 | "dotnet": { 410 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 411 | "namespace": "Amazon.CDK.AWS.ElasticLoadBalancing", 412 | "packageId": "Amazon.CDK.AWS.ElasticLoadBalancing" 413 | }, 414 | "java": { 415 | "maven": { 416 | "artifactId": "elasticloadbalancing", 417 | "groupId": "software.amazon.awscdk" 418 | }, 419 | "package": "software.amazon.awscdk.services.elasticloadbalancing" 420 | }, 421 | "js": { 422 | "npm": "@aws-cdk/aws-elasticloadbalancing" 423 | }, 424 | "python": { 425 | "classifiers": [ 426 | "Framework :: AWS CDK", 427 | "Framework :: AWS CDK :: 1" 428 | ], 429 | "distName": "aws-cdk.aws-elasticloadbalancing", 430 | "module": "aws_cdk.aws_elasticloadbalancing" 431 | } 432 | } 433 | }, 434 | "@aws-cdk/aws-elasticloadbalancingv2": { 435 | "targets": { 436 | "dotnet": { 437 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 438 | "namespace": "Amazon.CDK.AWS.ElasticLoadBalancingV2", 439 | "packageId": "Amazon.CDK.AWS.ElasticLoadBalancingV2" 440 | }, 441 | "java": { 442 | "maven": { 443 | "artifactId": "elasticloadbalancingv2", 444 | "groupId": "software.amazon.awscdk" 445 | }, 446 | "package": "software.amazon.awscdk.services.elasticloadbalancingv2" 447 | }, 448 | "js": { 449 | "npm": "@aws-cdk/aws-elasticloadbalancingv2" 450 | }, 451 | "python": { 452 | "classifiers": [ 453 | "Framework :: AWS CDK", 454 | "Framework :: AWS CDK :: 1" 455 | ], 456 | "distName": "aws-cdk.aws-elasticloadbalancingv2", 457 | "module": "aws_cdk.aws_elasticloadbalancingv2" 458 | } 459 | } 460 | }, 461 | "@aws-cdk/aws-events": { 462 | "targets": { 463 | "dotnet": { 464 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 465 | "namespace": "Amazon.CDK.AWS.Events", 466 | "packageId": "Amazon.CDK.AWS.Events" 467 | }, 468 | "java": { 469 | "maven": { 470 | "artifactId": "events", 471 | "groupId": "software.amazon.awscdk" 472 | }, 473 | "package": "software.amazon.awscdk.services.events" 474 | }, 475 | "js": { 476 | "npm": "@aws-cdk/aws-events" 477 | }, 478 | "python": { 479 | "classifiers": [ 480 | "Framework :: AWS CDK", 481 | "Framework :: AWS CDK :: 1" 482 | ], 483 | "distName": "aws-cdk.aws-events", 484 | "module": "aws_cdk.aws_events" 485 | } 486 | } 487 | }, 488 | "@aws-cdk/aws-globalaccelerator": { 489 | "targets": { 490 | "dotnet": { 491 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 492 | "namespace": "Amazon.CDK.AWS.GlobalAccelerator", 493 | "packageId": "Amazon.CDK.AWS.GlobalAccelerator" 494 | }, 495 | "java": { 496 | "maven": { 497 | "artifactId": "globalaccelerator", 498 | "groupId": "software.amazon.awscdk" 499 | }, 500 | "package": "software.amazon.awscdk.services.globalaccelerator" 501 | }, 502 | "js": { 503 | "npm": "@aws-cdk/aws-globalaccelerator" 504 | }, 505 | "python": { 506 | "classifiers": [ 507 | "Framework :: AWS CDK", 508 | "Framework :: AWS CDK :: 1" 509 | ], 510 | "distName": "aws-cdk.aws-globalaccelerator", 511 | "module": "aws_cdk.aws_globalaccelerator" 512 | } 513 | } 514 | }, 515 | "@aws-cdk/aws-iam": { 516 | "targets": { 517 | "dotnet": { 518 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 519 | "namespace": "Amazon.CDK.AWS.IAM", 520 | "packageId": "Amazon.CDK.AWS.IAM" 521 | }, 522 | "java": { 523 | "maven": { 524 | "artifactId": "iam", 525 | "groupId": "software.amazon.awscdk" 526 | }, 527 | "package": "software.amazon.awscdk.services.iam" 528 | }, 529 | "js": { 530 | "npm": "@aws-cdk/aws-iam" 531 | }, 532 | "python": { 533 | "classifiers": [ 534 | "Framework :: AWS CDK", 535 | "Framework :: AWS CDK :: 1" 536 | ], 537 | "distName": "aws-cdk.aws-iam", 538 | "module": "aws_cdk.aws_iam" 539 | } 540 | } 541 | }, 542 | "@aws-cdk/aws-kms": { 543 | "targets": { 544 | "dotnet": { 545 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 546 | "namespace": "Amazon.CDK.AWS.KMS", 547 | "packageId": "Amazon.CDK.AWS.KMS" 548 | }, 549 | "java": { 550 | "maven": { 551 | "artifactId": "kms", 552 | "groupId": "software.amazon.awscdk" 553 | }, 554 | "package": "software.amazon.awscdk.services.kms" 555 | }, 556 | "js": { 557 | "npm": "@aws-cdk/aws-kms" 558 | }, 559 | "python": { 560 | "classifiers": [ 561 | "Framework :: AWS CDK", 562 | "Framework :: AWS CDK :: 1" 563 | ], 564 | "distName": "aws-cdk.aws-kms", 565 | "module": "aws_cdk.aws_kms" 566 | } 567 | } 568 | }, 569 | "@aws-cdk/aws-lambda": { 570 | "targets": { 571 | "dotnet": { 572 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 573 | "namespace": "Amazon.CDK.AWS.Lambda", 574 | "packageId": "Amazon.CDK.AWS.Lambda" 575 | }, 576 | "java": { 577 | "maven": { 578 | "artifactId": "lambda", 579 | "groupId": "software.amazon.awscdk" 580 | }, 581 | "package": "software.amazon.awscdk.services.lambda" 582 | }, 583 | "js": { 584 | "npm": "@aws-cdk/aws-lambda" 585 | }, 586 | "python": { 587 | "classifiers": [ 588 | "Framework :: AWS CDK", 589 | "Framework :: AWS CDK :: 1" 590 | ], 591 | "distName": "aws-cdk.aws-lambda", 592 | "module": "aws_cdk.aws_lambda" 593 | } 594 | } 595 | }, 596 | "@aws-cdk/aws-logs": { 597 | "targets": { 598 | "dotnet": { 599 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 600 | "namespace": "Amazon.CDK.AWS.Logs", 601 | "packageId": "Amazon.CDK.AWS.Logs" 602 | }, 603 | "java": { 604 | "maven": { 605 | "artifactId": "logs", 606 | "groupId": "software.amazon.awscdk" 607 | }, 608 | "package": "software.amazon.awscdk.services.logs" 609 | }, 610 | "js": { 611 | "npm": "@aws-cdk/aws-logs" 612 | }, 613 | "python": { 614 | "classifiers": [ 615 | "Framework :: AWS CDK", 616 | "Framework :: AWS CDK :: 1" 617 | ], 618 | "distName": "aws-cdk.aws-logs", 619 | "module": "aws_cdk.aws_logs" 620 | } 621 | } 622 | }, 623 | "@aws-cdk/aws-route53": { 624 | "targets": { 625 | "dotnet": { 626 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 627 | "namespace": "Amazon.CDK.AWS.Route53", 628 | "packageId": "Amazon.CDK.AWS.Route53" 629 | }, 630 | "java": { 631 | "maven": { 632 | "artifactId": "route53", 633 | "groupId": "software.amazon.awscdk" 634 | }, 635 | "package": "software.amazon.awscdk.services.route53" 636 | }, 637 | "js": { 638 | "npm": "@aws-cdk/aws-route53" 639 | }, 640 | "python": { 641 | "classifiers": [ 642 | "Framework :: AWS CDK", 643 | "Framework :: AWS CDK :: 1" 644 | ], 645 | "distName": "aws-cdk.aws-route53", 646 | "module": "aws_cdk.aws_route53" 647 | } 648 | } 649 | }, 650 | "@aws-cdk/aws-route53-patterns": { 651 | "targets": { 652 | "dotnet": { 653 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 654 | "namespace": "Amazon.CDK.AWS.Route53.Patterns", 655 | "packageId": "Amazon.CDK.AWS.Route53.Patterns" 656 | }, 657 | "java": { 658 | "maven": { 659 | "artifactId": "route53-patterns", 660 | "groupId": "software.amazon.awscdk" 661 | }, 662 | "package": "software.amazon.awscdk.services.route53.patterns" 663 | }, 664 | "js": { 665 | "npm": "@aws-cdk/aws-route53-patterns" 666 | }, 667 | "python": { 668 | "classifiers": [ 669 | "Framework :: AWS CDK", 670 | "Framework :: AWS CDK :: 1" 671 | ], 672 | "distName": "aws-cdk.aws-route53-patterns", 673 | "module": "aws_cdk.aws_route53_patterns" 674 | } 675 | } 676 | }, 677 | "@aws-cdk/aws-route53-targets": { 678 | "targets": { 679 | "dotnet": { 680 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 681 | "namespace": "Amazon.CDK.AWS.Route53.Targets", 682 | "packageId": "Amazon.CDK.AWS.Route53.Targets" 683 | }, 684 | "java": { 685 | "maven": { 686 | "artifactId": "route53-targets", 687 | "groupId": "software.amazon.awscdk" 688 | }, 689 | "package": "software.amazon.awscdk.services.route53.targets" 690 | }, 691 | "js": { 692 | "npm": "@aws-cdk/aws-route53-targets" 693 | }, 694 | "python": { 695 | "classifiers": [ 696 | "Framework :: AWS CDK", 697 | "Framework :: AWS CDK :: 1" 698 | ], 699 | "distName": "aws-cdk.aws-route53-targets", 700 | "module": "aws_cdk.aws_route53_targets" 701 | } 702 | } 703 | }, 704 | "@aws-cdk/aws-s3": { 705 | "targets": { 706 | "dotnet": { 707 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 708 | "namespace": "Amazon.CDK.AWS.S3", 709 | "packageId": "Amazon.CDK.AWS.S3" 710 | }, 711 | "java": { 712 | "maven": { 713 | "artifactId": "s3", 714 | "groupId": "software.amazon.awscdk" 715 | }, 716 | "package": "software.amazon.awscdk.services.s3" 717 | }, 718 | "js": { 719 | "npm": "@aws-cdk/aws-s3" 720 | }, 721 | "python": { 722 | "classifiers": [ 723 | "Framework :: AWS CDK", 724 | "Framework :: AWS CDK :: 1" 725 | ], 726 | "distName": "aws-cdk.aws-s3", 727 | "module": "aws_cdk.aws_s3" 728 | } 729 | } 730 | }, 731 | "@aws-cdk/aws-s3-assets": { 732 | "targets": { 733 | "dotnet": { 734 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 735 | "namespace": "Amazon.CDK.AWS.S3.Assets", 736 | "packageId": "Amazon.CDK.AWS.S3.Assets" 737 | }, 738 | "java": { 739 | "maven": { 740 | "artifactId": "s3-assets", 741 | "groupId": "software.amazon.awscdk" 742 | }, 743 | "package": "software.amazon.awscdk.services.s3.assets" 744 | }, 745 | "js": { 746 | "npm": "@aws-cdk/aws-s3-assets" 747 | }, 748 | "python": { 749 | "classifiers": [ 750 | "Framework :: AWS CDK", 751 | "Framework :: AWS CDK :: 1" 752 | ], 753 | "distName": "aws-cdk.aws-s3-assets", 754 | "module": "aws_cdk.aws_s3_assets" 755 | } 756 | } 757 | }, 758 | "@aws-cdk/aws-s3-deployment": { 759 | "targets": { 760 | "dotnet": { 761 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 762 | "namespace": "Amazon.CDK.AWS.S3.Deployment", 763 | "packageId": "Amazon.CDK.AWS.S3.Deployment" 764 | }, 765 | "java": { 766 | "maven": { 767 | "artifactId": "s3-deployment", 768 | "groupId": "software.amazon.awscdk" 769 | }, 770 | "package": "software.amazon.awscdk.services.s3.deployment" 771 | }, 772 | "js": { 773 | "npm": "@aws-cdk/aws-s3-deployment" 774 | }, 775 | "python": { 776 | "classifiers": [ 777 | "Framework :: AWS CDK", 778 | "Framework :: AWS CDK :: 1" 779 | ], 780 | "distName": "aws-cdk.aws-s3-deployment", 781 | "module": "aws_cdk.aws_s3_deployment" 782 | } 783 | } 784 | }, 785 | "@aws-cdk/aws-signer": { 786 | "targets": { 787 | "dotnet": { 788 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 789 | "namespace": "Amazon.CDK.AWS.Signer", 790 | "packageId": "Amazon.CDK.AWS.Signer" 791 | }, 792 | "java": { 793 | "maven": { 794 | "artifactId": "signer", 795 | "groupId": "software.amazon.awscdk" 796 | }, 797 | "package": "software.amazon.awscdk.services.signer" 798 | }, 799 | "js": { 800 | "npm": "@aws-cdk/aws-signer" 801 | }, 802 | "python": { 803 | "classifiers": [ 804 | "Framework :: AWS CDK", 805 | "Framework :: AWS CDK :: 1" 806 | ], 807 | "distName": "aws-cdk.aws-signer", 808 | "module": "aws_cdk.aws_signer" 809 | } 810 | } 811 | }, 812 | "@aws-cdk/aws-sns": { 813 | "targets": { 814 | "dotnet": { 815 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 816 | "namespace": "Amazon.CDK.AWS.SNS", 817 | "packageId": "Amazon.CDK.AWS.SNS" 818 | }, 819 | "java": { 820 | "maven": { 821 | "artifactId": "sns", 822 | "groupId": "software.amazon.awscdk" 823 | }, 824 | "package": "software.amazon.awscdk.services.sns" 825 | }, 826 | "js": { 827 | "npm": "@aws-cdk/aws-sns" 828 | }, 829 | "python": { 830 | "classifiers": [ 831 | "Framework :: AWS CDK", 832 | "Framework :: AWS CDK :: 1" 833 | ], 834 | "distName": "aws-cdk.aws-sns", 835 | "module": "aws_cdk.aws_sns" 836 | } 837 | } 838 | }, 839 | "@aws-cdk/aws-sqs": { 840 | "targets": { 841 | "dotnet": { 842 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 843 | "namespace": "Amazon.CDK.AWS.SQS", 844 | "packageId": "Amazon.CDK.AWS.SQS" 845 | }, 846 | "java": { 847 | "maven": { 848 | "artifactId": "sqs", 849 | "groupId": "software.amazon.awscdk" 850 | }, 851 | "package": "software.amazon.awscdk.services.sqs" 852 | }, 853 | "js": { 854 | "npm": "@aws-cdk/aws-sqs" 855 | }, 856 | "python": { 857 | "classifiers": [ 858 | "Framework :: AWS CDK", 859 | "Framework :: AWS CDK :: 1" 860 | ], 861 | "distName": "aws-cdk.aws-sqs", 862 | "module": "aws_cdk.aws_sqs" 863 | } 864 | } 865 | }, 866 | "@aws-cdk/aws-ssm": { 867 | "targets": { 868 | "dotnet": { 869 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 870 | "namespace": "Amazon.CDK.AWS.SSM", 871 | "packageId": "Amazon.CDK.AWS.SSM" 872 | }, 873 | "java": { 874 | "maven": { 875 | "artifactId": "ssm", 876 | "groupId": "software.amazon.awscdk" 877 | }, 878 | "package": "software.amazon.awscdk.services.ssm" 879 | }, 880 | "js": { 881 | "npm": "@aws-cdk/aws-ssm" 882 | }, 883 | "python": { 884 | "classifiers": [ 885 | "Framework :: AWS CDK", 886 | "Framework :: AWS CDK :: 1" 887 | ], 888 | "distName": "aws-cdk.aws-ssm", 889 | "module": "aws_cdk.aws_ssm" 890 | } 891 | } 892 | }, 893 | "@aws-cdk/cloud-assembly-schema": { 894 | "targets": { 895 | "dotnet": { 896 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 897 | "namespace": "Amazon.CDK.CloudAssembly.Schema", 898 | "packageId": "Amazon.CDK.CloudAssembly.Schema" 899 | }, 900 | "java": { 901 | "maven": { 902 | "artifactId": "cdk-cloud-assembly-schema", 903 | "groupId": "software.amazon.awscdk" 904 | }, 905 | "package": "software.amazon.awscdk.cloudassembly.schema" 906 | }, 907 | "js": { 908 | "npm": "@aws-cdk/cloud-assembly-schema" 909 | }, 910 | "python": { 911 | "classifiers": [ 912 | "Framework :: AWS CDK", 913 | "Framework :: AWS CDK :: 1" 914 | ], 915 | "distName": "aws-cdk.cloud-assembly-schema", 916 | "module": "aws_cdk.cloud_assembly_schema" 917 | } 918 | } 919 | }, 920 | "@aws-cdk/core": { 921 | "targets": { 922 | "dotnet": { 923 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 924 | "namespace": "Amazon.CDK", 925 | "packageId": "Amazon.CDK" 926 | }, 927 | "java": { 928 | "maven": { 929 | "artifactId": "core", 930 | "groupId": "software.amazon.awscdk" 931 | }, 932 | "package": "software.amazon.awscdk.core" 933 | }, 934 | "js": { 935 | "npm": "@aws-cdk/core" 936 | }, 937 | "python": { 938 | "classifiers": [ 939 | "Framework :: AWS CDK", 940 | "Framework :: AWS CDK :: 1" 941 | ], 942 | "distName": "aws-cdk.core", 943 | "module": "aws_cdk.core" 944 | } 945 | } 946 | }, 947 | "@aws-cdk/custom-resources": { 948 | "targets": { 949 | "dotnet": { 950 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 951 | "namespace": "Amazon.CDK.CustomResources", 952 | "packageId": "Amazon.CDK.AWS.CustomResources" 953 | }, 954 | "java": { 955 | "maven": { 956 | "artifactId": "cdk-customresources", 957 | "groupId": "software.amazon.awscdk" 958 | }, 959 | "package": "software.amazon.awscdk.customresources" 960 | }, 961 | "js": { 962 | "npm": "@aws-cdk/custom-resources" 963 | }, 964 | "python": { 965 | "classifiers": [ 966 | "Framework :: AWS CDK", 967 | "Framework :: AWS CDK :: 1" 968 | ], 969 | "distName": "aws-cdk.custom-resources", 970 | "module": "aws_cdk.custom_resources" 971 | } 972 | } 973 | }, 974 | "@aws-cdk/cx-api": { 975 | "targets": { 976 | "dotnet": { 977 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 978 | "namespace": "Amazon.CDK.CXAPI", 979 | "packageId": "Amazon.CDK.CXAPI" 980 | }, 981 | "java": { 982 | "maven": { 983 | "artifactId": "cdk-cx-api", 984 | "groupId": "software.amazon.awscdk" 985 | }, 986 | "package": "software.amazon.awscdk.cxapi" 987 | }, 988 | "js": { 989 | "npm": "@aws-cdk/cx-api" 990 | }, 991 | "python": { 992 | "classifiers": [ 993 | "Framework :: AWS CDK", 994 | "Framework :: AWS CDK :: 1" 995 | ], 996 | "distName": "aws-cdk.cx-api", 997 | "module": "aws_cdk.cx_api" 998 | } 999 | } 1000 | }, 1001 | "@aws-cdk/lambda-layer-awscli": { 1002 | "targets": { 1003 | "dotnet": { 1004 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 1005 | "namespace": "Amazon.CDK.LambdaLayer.AwsCli", 1006 | "packageId": "Amazon.CDK.LambdaLayer.AwsCli" 1007 | }, 1008 | "java": { 1009 | "maven": { 1010 | "artifactId": "cdk-lambda-layer-awscli", 1011 | "groupId": "software.amazon.awscdk" 1012 | }, 1013 | "package": "software.amazon.awscdk.lambdalayer.awscli" 1014 | }, 1015 | "js": { 1016 | "npm": "@aws-cdk/lambda-layer-awscli" 1017 | }, 1018 | "python": { 1019 | "classifiers": [ 1020 | "Framework :: AWS CDK", 1021 | "Framework :: AWS CDK :: 1" 1022 | ], 1023 | "distName": "aws-cdk.lambda-layer-awscli", 1024 | "module": "aws_cdk.lambda_layer_awscli" 1025 | } 1026 | } 1027 | }, 1028 | "@aws-cdk/region-info": { 1029 | "targets": { 1030 | "dotnet": { 1031 | "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", 1032 | "namespace": "Amazon.CDK.RegionInfo", 1033 | "packageId": "Amazon.CDK.RegionInfo" 1034 | }, 1035 | "java": { 1036 | "maven": { 1037 | "artifactId": "cdk-region-info", 1038 | "groupId": "software.amazon.awscdk" 1039 | }, 1040 | "package": "software.amazon.awscdk.regioninfo" 1041 | }, 1042 | "js": { 1043 | "npm": "@aws-cdk/region-info" 1044 | }, 1045 | "python": { 1046 | "classifiers": [ 1047 | "Framework :: AWS CDK", 1048 | "Framework :: AWS CDK :: 1" 1049 | ], 1050 | "distName": "aws-cdk.region-info", 1051 | "module": "aws_cdk.region_info" 1052 | } 1053 | } 1054 | }, 1055 | "constructs": { 1056 | "targets": { 1057 | "dotnet": { 1058 | "namespace": "Constructs", 1059 | "packageId": "Constructs" 1060 | }, 1061 | "go": { 1062 | "moduleName": "github.com/aws/constructs-go" 1063 | }, 1064 | "java": { 1065 | "maven": { 1066 | "artifactId": "constructs", 1067 | "groupId": "software.constructs" 1068 | }, 1069 | "package": "software.constructs" 1070 | }, 1071 | "js": { 1072 | "npm": "constructs" 1073 | }, 1074 | "python": { 1075 | "distName": "constructs", 1076 | "module": "constructs" 1077 | } 1078 | } 1079 | } 1080 | }, 1081 | "description": "This is an AWS CDK Construct to make deploying a single page website (Angular/React/Vue) to AWS S3 behind SSL/Cloudfront as easy as 5 lines of code.", 1082 | "homepage": "https://github.com/nideveloper/CDK-SPA-Deploy.git", 1083 | "jsiiVersion": "1.29.0 (build 41df200)", 1084 | "keywords": [ 1085 | "aws", 1086 | "cdk", 1087 | "spa", 1088 | "website", 1089 | "deploy", 1090 | "cloudfront" 1091 | ], 1092 | "license": "MIT", 1093 | "metadata": { 1094 | "jsii": { 1095 | "pacmak": { 1096 | "hasDefaultInterfaces": true 1097 | } 1098 | } 1099 | }, 1100 | "name": "cdk-spa-deploy", 1101 | "readme": { 1102 | "markdown": "# CDK-SPA-Deploy\n[![npm](https://img.shields.io/npm/dt/cdk-spa-deploy)](https://www.npmjs.com/package/cdk-spa-deploy)\n[![Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/cdk-spa-deploy)](https://www.npmjs.com/package/cdk-spa-deploy)\n\nThis is an AWS CDK Construct to make deploying a single page website (Angular/React/Vue) to AWS S3 behind SSL/Cloudfront as easy as 5 lines of code.\n\n\n## Installation and Usage\n\n### Typescript\n\n```console\nnpm install --save cdk-spa-deploy\n```\n\nAs of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself, here is a list:\n```json\n{\n \"constructs\": \"^3.3.75\",\n \"@aws-cdk/aws-certificatemanager\": \"^1.103.0\",\n \"@aws-cdk/aws-cloudfront\": \"^1.103.0\",\n \"@aws-cdk/aws-iam\": \"^1.103.0\",\n \"@aws-cdk/aws-route53\": \"^1.103.0\",\n \"@aws-cdk/aws-route53-patterns\": \"^1.103.0\",\n \"@aws-cdk/aws-route53-targets\": \"^1.103.0\",\n \"@aws-cdk/aws-s3\": \"^1.103.0\",\n \"@aws-cdk/aws-s3-deployment\": \"^1.103.0\",\n \"@aws-cdk/core\": \"^1.103.0\"\n}\n```\n\n```typescript\nimport cdk = require('@aws-cdk/core');\nimport { SPADeploy } from 'cdk-spa-deploy';\n\nexport class CdkStack extends cdk.Stack {\n constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {\n super(scope, id, props);\n\n new SPADeploy(this, 'spaDeploy')\n .createBasicSite({\n indexDoc: 'index.html',\n websiteFolder: '../blog/dist/blog'\n });\n\n new SPADeploy(this, 'cfDeploy')\n .createSiteWithCloudfront({\n indexDoc: 'index.html',\n websiteFolder: '../blog/dist/blog'\n });\n }\n}\n\n```\n\n### Python\n```console\npip install cdk-spa-deploy\n```\n\nNote As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above.\n\n```python\nfrom aws_cdk import core\nfrom spa_deploy import SPADeploy\n\nclass PythonStack(core.Stack):\n def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:\n super().__init__(scope, id, **kwargs)\n\n SPADeploy(self, 'spaDeploy').create_basic_site(\n index_doc='index.html',\n website_folder='../blog/blog/dist/blog'\n )\n\n\n SPADeploy(self, 'cfDeploy').create_site_with_cloudfront(\n index_doc='index.html',\n website_folder='../blog/blog/dist/blog'\n )\n```\n\n### Dotnet / C#\n\nThis project has now been published to nuget, more details to follow soon but you can find it [here](https://www.nuget.org/packages/CDKSPADeploy/1.80.0)\n\nNote As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above.\n\n```bash\n# package manager\nInstall-Package CDKSPADeploy -Version 1.80.0\n# .NET CLI\ndotnet add package CDKSPADeploy --version 1.80.0\n# Package reference\n\n# Paket CLI\npaket add CDKSPADeploy --version 1.80.0\n```\n\n### Java\n\nA version has now been published to maven.\n\nNote As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above.\n\n```xml\n\n com.cdkpatterns\n CDKSPADeploy\n 1.81.0\n\n```\n\n## Advanced Usage\n\n### Auto Deploy From Hosted Zone Name\n\nIf you purchased your domain through route 53 and already have a hosted zone then just use the name to deploy your site behind cloudfront. This handles the SSL cert and everything for you.\n\n```typescript\nnew SPADeploy(this, 'spaDeploy', { encryptBucket: true })\n .createSiteFromHostedZone({\n zoneName: 'cdkpatterns.com',\n indexDoc: 'index.html',\n websiteFolder: '../website/dist/website'\n });\n\n```\n\n### Custom Domain and SSL Certificates\n\nYou can also pass the ARN for an SSL certification and your alias routes to cloudfront\n\n```typescript\nimport cdk = require('@aws-cdk/core');\nimport { SPADeploy } from 'cdk-spa-deploy';\n\nexport class CdkStack extends cdk.Stack {\n constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {\n super(scope, id, props);\n\n new SPADeploy(this, 'cfDeploy')\n .createSiteWithCloudfront({\n indexDoc: '../blog/dist/blog',\n certificateARN: 'arn:...',\n cfAliases: ['www.alias.com']\n });\n }\n}\n\n```\n\n### Encrypted S3 Bucket\n\nPass in one boolean to tell SPA Deploy to encrypt your website bucket\n\n```typescript\nnew SPADeploy(this, 'cfDeploy', {encryptBucket: true}).createBasicSite({\n indexDoc: 'index.html',\n websiteFolder: 'website'\n});\n\n```\n\n### Custom Origin Behaviors\n\nPass in an array of CloudFront Behaviors\n\n```typescript\nnew SPADeploy(this, 'cfDeploy').createSiteWithCloudfront({\n indexDoc: 'index.html',\n websiteFolder: 'website',\n cfBehaviors: [\n {\n isDefaultBehavior: true,\n allowedMethods: cf.CloudFrontAllowedMethods.ALL,\n forwardedValues: {\n queryString: true,\n cookies: { forward: 'all' },\n headers: ['*'],\n },\n },\n {\n pathPattern: '/virtual-path',\n allowedMethods: cf.CloudFrontAllowedMethods.GET_HEAD,\n cachedMethods: cf.CloudFrontAllowedCachedMethods.GET_HEAD,\n },\n ],\n});\n```\n\n### Restrict Access to Known IPs\n\nPass in a boolean and an array of IP addresses and your site is locked down!\n\n```typescript\nnew SPADeploy(stack, 'spaDeploy', {\n encryptBucket: true,\n ipFilter: true,\n ipList: ['1.1.1.1']\n}).createBasicSite({\n indexDoc: 'index.html',\n websiteFolder: 'website'\n })\n```\n\n### Modifying S3 Bucket Created in Construct\n\nAn object is now returned containing relevant artifacts created if you need to make any further modifications:\n * The S3 bucket is present for all of the methods\n * When a CloudFront Web distribution is created it will be present in the return object\n\n```typescript\nexport interface SPADeployment {\n readonly websiteBucket: s3.Bucket,\n}\n\nexport interface SPADeploymentWithCloudFront extends SPADeployment {\n readonly distribution: CloudFrontWebDistribution,\n}\n```\n\n## Issues / Feature Requests\n\nhttps://github.com/nideveloper/CDK-SPA-Deploy\n" 1103 | }, 1104 | "repository": { 1105 | "type": "git", 1106 | "url": "https://github.com/nideveloper/CDK-SPA-Deploy.git" 1107 | }, 1108 | "schema": "jsii/0.10.0", 1109 | "targets": { 1110 | "dotnet": { 1111 | "namespace": "com.cdkpatterns", 1112 | "packageId": "CDKSPADeploy" 1113 | }, 1114 | "java": { 1115 | "maven": { 1116 | "artifactId": "CDKSPADeploy", 1117 | "groupId": "com.cdkpatterns" 1118 | }, 1119 | "package": "com.cdkpatterns" 1120 | }, 1121 | "js": { 1122 | "npm": "cdk-spa-deploy" 1123 | }, 1124 | "python": { 1125 | "distName": "cdk-spa-deploy", 1126 | "module": "spa_deploy" 1127 | } 1128 | }, 1129 | "types": { 1130 | "cdk-spa-deploy.HostedZoneConfig": { 1131 | "assembly": "cdk-spa-deploy", 1132 | "datatype": true, 1133 | "fqn": "cdk-spa-deploy.HostedZoneConfig", 1134 | "kind": "interface", 1135 | "locationInModule": { 1136 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1137 | "line": 33 1138 | }, 1139 | "name": "HostedZoneConfig", 1140 | "properties": [ 1141 | { 1142 | "abstract": true, 1143 | "immutable": true, 1144 | "locationInModule": { 1145 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1146 | "line": 34 1147 | }, 1148 | "name": "indexDoc", 1149 | "type": { 1150 | "primitive": "string" 1151 | } 1152 | }, 1153 | { 1154 | "abstract": true, 1155 | "immutable": true, 1156 | "locationInModule": { 1157 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1158 | "line": 37 1159 | }, 1160 | "name": "websiteFolder", 1161 | "type": { 1162 | "primitive": "string" 1163 | } 1164 | }, 1165 | { 1166 | "abstract": true, 1167 | "immutable": true, 1168 | "locationInModule": { 1169 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1170 | "line": 38 1171 | }, 1172 | "name": "zoneName", 1173 | "type": { 1174 | "primitive": "string" 1175 | } 1176 | }, 1177 | { 1178 | "abstract": true, 1179 | "immutable": true, 1180 | "locationInModule": { 1181 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1182 | "line": 36 1183 | }, 1184 | "name": "cfBehaviors", 1185 | "optional": true, 1186 | "type": { 1187 | "collection": { 1188 | "elementtype": { 1189 | "fqn": "@aws-cdk/aws-cloudfront.Behavior" 1190 | }, 1191 | "kind": "array" 1192 | } 1193 | } 1194 | }, 1195 | { 1196 | "abstract": true, 1197 | "immutable": true, 1198 | "locationInModule": { 1199 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1200 | "line": 35 1201 | }, 1202 | "name": "errorDoc", 1203 | "optional": true, 1204 | "type": { 1205 | "primitive": "string" 1206 | } 1207 | }, 1208 | { 1209 | "abstract": true, 1210 | "immutable": true, 1211 | "locationInModule": { 1212 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1213 | "line": 40 1214 | }, 1215 | "name": "role", 1216 | "optional": true, 1217 | "type": { 1218 | "fqn": "@aws-cdk/aws-iam.Role" 1219 | } 1220 | }, 1221 | { 1222 | "abstract": true, 1223 | "immutable": true, 1224 | "locationInModule": { 1225 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1226 | "line": 39 1227 | }, 1228 | "name": "subdomain", 1229 | "optional": true, 1230 | "type": { 1231 | "primitive": "string" 1232 | } 1233 | } 1234 | ] 1235 | }, 1236 | "cdk-spa-deploy.SPADeploy": { 1237 | "assembly": "cdk-spa-deploy", 1238 | "base": "@aws-cdk/core.Construct", 1239 | "fqn": "cdk-spa-deploy.SPADeploy", 1240 | "initializer": { 1241 | "locationInModule": { 1242 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1243 | "line": 61 1244 | }, 1245 | "parameters": [ 1246 | { 1247 | "name": "scope", 1248 | "type": { 1249 | "fqn": "@aws-cdk/core.Construct" 1250 | } 1251 | }, 1252 | { 1253 | "name": "id", 1254 | "type": { 1255 | "primitive": "string" 1256 | } 1257 | }, 1258 | { 1259 | "name": "config", 1260 | "optional": true, 1261 | "type": { 1262 | "fqn": "cdk-spa-deploy.SPAGlobalConfig" 1263 | } 1264 | } 1265 | ] 1266 | }, 1267 | "kind": "class", 1268 | "locationInModule": { 1269 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1270 | "line": 58 1271 | }, 1272 | "methods": [ 1273 | { 1274 | "docs": { 1275 | "summary": "Basic setup needed for a non-ssl, non vanity url, non cached s3 website." 1276 | }, 1277 | "locationInModule": { 1278 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1279 | "line": 169 1280 | }, 1281 | "name": "createBasicSite", 1282 | "parameters": [ 1283 | { 1284 | "name": "config", 1285 | "type": { 1286 | "fqn": "cdk-spa-deploy.SPADeployConfig" 1287 | } 1288 | } 1289 | ], 1290 | "returns": { 1291 | "type": { 1292 | "fqn": "cdk-spa-deploy.SPADeployment" 1293 | } 1294 | } 1295 | }, 1296 | { 1297 | "docs": { 1298 | "summary": "S3 Deployment, cloudfront distribution, ssl cert and error forwarding auto\r configured by using the details in the hosted zone provided." 1299 | }, 1300 | "locationInModule": { 1301 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1302 | "line": 225 1303 | }, 1304 | "name": "createSiteFromHostedZone", 1305 | "parameters": [ 1306 | { 1307 | "name": "config", 1308 | "type": { 1309 | "fqn": "cdk-spa-deploy.HostedZoneConfig" 1310 | } 1311 | } 1312 | ], 1313 | "returns": { 1314 | "type": { 1315 | "fqn": "cdk-spa-deploy.SPADeploymentWithCloudFront" 1316 | } 1317 | } 1318 | }, 1319 | { 1320 | "docs": { 1321 | "summary": "This will create an s3 deployment fronted by a cloudfront distribution\r It will also setup error forwarding and unauth forwarding back to indexDoc." 1322 | }, 1323 | "locationInModule": { 1324 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1325 | "line": 199 1326 | }, 1327 | "name": "createSiteWithCloudfront", 1328 | "parameters": [ 1329 | { 1330 | "name": "config", 1331 | "type": { 1332 | "fqn": "cdk-spa-deploy.SPADeployConfig" 1333 | } 1334 | } 1335 | ], 1336 | "returns": { 1337 | "type": { 1338 | "fqn": "cdk-spa-deploy.SPADeploymentWithCloudFront" 1339 | } 1340 | } 1341 | } 1342 | ], 1343 | "name": "SPADeploy", 1344 | "properties": [ 1345 | { 1346 | "locationInModule": { 1347 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1348 | "line": 59 1349 | }, 1350 | "name": "globalConfig", 1351 | "type": { 1352 | "fqn": "cdk-spa-deploy.SPAGlobalConfig" 1353 | } 1354 | } 1355 | ] 1356 | }, 1357 | "cdk-spa-deploy.SPADeployConfig": { 1358 | "assembly": "cdk-spa-deploy", 1359 | "datatype": true, 1360 | "fqn": "cdk-spa-deploy.SPADeployConfig", 1361 | "kind": "interface", 1362 | "locationInModule": { 1363 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1364 | "line": 18 1365 | }, 1366 | "name": "SPADeployConfig", 1367 | "properties": [ 1368 | { 1369 | "abstract": true, 1370 | "immutable": true, 1371 | "locationInModule": { 1372 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1373 | "line": 19 1374 | }, 1375 | "name": "indexDoc", 1376 | "type": { 1377 | "primitive": "string" 1378 | } 1379 | }, 1380 | { 1381 | "abstract": true, 1382 | "immutable": true, 1383 | "locationInModule": { 1384 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1385 | "line": 21 1386 | }, 1387 | "name": "websiteFolder", 1388 | "type": { 1389 | "primitive": "string" 1390 | } 1391 | }, 1392 | { 1393 | "abstract": true, 1394 | "immutable": true, 1395 | "locationInModule": { 1396 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1397 | "line": 27 1398 | }, 1399 | "name": "blockPublicAccess", 1400 | "optional": true, 1401 | "type": { 1402 | "fqn": "@aws-cdk/aws-s3.BlockPublicAccess" 1403 | } 1404 | }, 1405 | { 1406 | "abstract": true, 1407 | "immutable": true, 1408 | "locationInModule": { 1409 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1410 | "line": 22 1411 | }, 1412 | "name": "certificateARN", 1413 | "optional": true, 1414 | "type": { 1415 | "primitive": "string" 1416 | } 1417 | }, 1418 | { 1419 | "abstract": true, 1420 | "immutable": true, 1421 | "locationInModule": { 1422 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1423 | "line": 24 1424 | }, 1425 | "name": "cfAliases", 1426 | "optional": true, 1427 | "type": { 1428 | "collection": { 1429 | "elementtype": { 1430 | "primitive": "string" 1431 | }, 1432 | "kind": "array" 1433 | } 1434 | } 1435 | }, 1436 | { 1437 | "abstract": true, 1438 | "immutable": true, 1439 | "locationInModule": { 1440 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1441 | "line": 23 1442 | }, 1443 | "name": "cfBehaviors", 1444 | "optional": true, 1445 | "type": { 1446 | "collection": { 1447 | "elementtype": { 1448 | "fqn": "@aws-cdk/aws-cloudfront.Behavior" 1449 | }, 1450 | "kind": "array" 1451 | } 1452 | } 1453 | }, 1454 | { 1455 | "abstract": true, 1456 | "immutable": true, 1457 | "locationInModule": { 1458 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1459 | "line": 20 1460 | }, 1461 | "name": "errorDoc", 1462 | "optional": true, 1463 | "type": { 1464 | "primitive": "string" 1465 | } 1466 | }, 1467 | { 1468 | "abstract": true, 1469 | "immutable": true, 1470 | "locationInModule": { 1471 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1472 | "line": 26 1473 | }, 1474 | "name": "exportWebsiteUrlName", 1475 | "optional": true, 1476 | "type": { 1477 | "primitive": "string" 1478 | } 1479 | }, 1480 | { 1481 | "abstract": true, 1482 | "immutable": true, 1483 | "locationInModule": { 1484 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1485 | "line": 25 1486 | }, 1487 | "name": "exportWebsiteUrlOutput", 1488 | "optional": true, 1489 | "type": { 1490 | "primitive": "boolean" 1491 | } 1492 | }, 1493 | { 1494 | "abstract": true, 1495 | "immutable": true, 1496 | "locationInModule": { 1497 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1498 | "line": 30 1499 | }, 1500 | "name": "role", 1501 | "optional": true, 1502 | "type": { 1503 | "fqn": "@aws-cdk/aws-iam.Role" 1504 | } 1505 | }, 1506 | { 1507 | "abstract": true, 1508 | "immutable": true, 1509 | "locationInModule": { 1510 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1511 | "line": 29 1512 | }, 1513 | "name": "securityPolicy", 1514 | "optional": true, 1515 | "type": { 1516 | "fqn": "@aws-cdk/aws-cloudfront.SecurityPolicyProtocol" 1517 | } 1518 | }, 1519 | { 1520 | "abstract": true, 1521 | "immutable": true, 1522 | "locationInModule": { 1523 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1524 | "line": 28 1525 | }, 1526 | "name": "sslMethod", 1527 | "optional": true, 1528 | "type": { 1529 | "fqn": "@aws-cdk/aws-cloudfront.SSLMethod" 1530 | } 1531 | } 1532 | ] 1533 | }, 1534 | "cdk-spa-deploy.SPADeployment": { 1535 | "assembly": "cdk-spa-deploy", 1536 | "datatype": true, 1537 | "fqn": "cdk-spa-deploy.SPADeployment", 1538 | "kind": "interface", 1539 | "locationInModule": { 1540 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1541 | "line": 50 1542 | }, 1543 | "name": "SPADeployment", 1544 | "properties": [ 1545 | { 1546 | "abstract": true, 1547 | "immutable": true, 1548 | "locationInModule": { 1549 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1550 | "line": 51 1551 | }, 1552 | "name": "websiteBucket", 1553 | "type": { 1554 | "fqn": "@aws-cdk/aws-s3.Bucket" 1555 | } 1556 | } 1557 | ] 1558 | }, 1559 | "cdk-spa-deploy.SPADeploymentWithCloudFront": { 1560 | "assembly": "cdk-spa-deploy", 1561 | "datatype": true, 1562 | "fqn": "cdk-spa-deploy.SPADeploymentWithCloudFront", 1563 | "interfaces": [ 1564 | "cdk-spa-deploy.SPADeployment" 1565 | ], 1566 | "kind": "interface", 1567 | "locationInModule": { 1568 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1569 | "line": 54 1570 | }, 1571 | "name": "SPADeploymentWithCloudFront", 1572 | "properties": [ 1573 | { 1574 | "abstract": true, 1575 | "immutable": true, 1576 | "locationInModule": { 1577 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1578 | "line": 55 1579 | }, 1580 | "name": "distribution", 1581 | "type": { 1582 | "fqn": "@aws-cdk/aws-cloudfront.CloudFrontWebDistribution" 1583 | } 1584 | } 1585 | ] 1586 | }, 1587 | "cdk-spa-deploy.SPAGlobalConfig": { 1588 | "assembly": "cdk-spa-deploy", 1589 | "datatype": true, 1590 | "fqn": "cdk-spa-deploy.SPAGlobalConfig", 1591 | "kind": "interface", 1592 | "locationInModule": { 1593 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1594 | "line": 43 1595 | }, 1596 | "name": "SPAGlobalConfig", 1597 | "properties": [ 1598 | { 1599 | "abstract": true, 1600 | "immutable": true, 1601 | "locationInModule": { 1602 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1603 | "line": 44 1604 | }, 1605 | "name": "encryptBucket", 1606 | "optional": true, 1607 | "type": { 1608 | "primitive": "boolean" 1609 | } 1610 | }, 1611 | { 1612 | "abstract": true, 1613 | "immutable": true, 1614 | "locationInModule": { 1615 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1616 | "line": 45 1617 | }, 1618 | "name": "ipFilter", 1619 | "optional": true, 1620 | "type": { 1621 | "primitive": "boolean" 1622 | } 1623 | }, 1624 | { 1625 | "abstract": true, 1626 | "immutable": true, 1627 | "locationInModule": { 1628 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1629 | "line": 46 1630 | }, 1631 | "name": "ipList", 1632 | "optional": true, 1633 | "type": { 1634 | "collection": { 1635 | "elementtype": { 1636 | "primitive": "string" 1637 | }, 1638 | "kind": "array" 1639 | } 1640 | } 1641 | }, 1642 | { 1643 | "abstract": true, 1644 | "immutable": true, 1645 | "locationInModule": { 1646 | "filename": "lib/spa-deploy/spa-deploy-construct.ts", 1647 | "line": 47 1648 | }, 1649 | "name": "role", 1650 | "optional": true, 1651 | "type": { 1652 | "fqn": "@aws-cdk/aws-iam.Role" 1653 | } 1654 | } 1655 | ] 1656 | } 1657 | }, 1658 | "version": "1.104.0", 1659 | "fingerprint": "BnRHUz0fqnj/xPlmrwWCqyZ1SRzIsULYDaB3gmSMt4U=" 1660 | } 1661 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | 8 | 9 | # Exclude jsii outdir 10 | dist 11 | 12 | # Include .jsii 13 | !.jsii 14 | 15 | 16 | # Exclude jsii outdir 17 | dist 18 | 19 | # Include .jsii 20 | !.jsii 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matt Coulter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDK-SPA-Deploy 2 | [![npm](https://img.shields.io/npm/dt/cdk-spa-deploy)](https://www.npmjs.com/package/cdk-spa-deploy) 3 | [![Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/cdk-spa-deploy)](https://www.npmjs.com/package/cdk-spa-deploy) 4 | 5 | This is an AWS CDK Construct to make deploying a single page website (Angular/React/Vue) to AWS S3 behind SSL/Cloudfront as easy as 5 lines of code. 6 | 7 | 8 | ## Installation and Usage 9 | 10 | ### Typescript 11 | 12 | ```console 13 | npm install --save cdk-spa-deploy 14 | ``` 15 | 16 | There is now a v1 and a v2 CDK version of this construct 17 | 18 | #### For AWS CDK V1 Usage: 19 | 20 | As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself, here is a list: 21 | ```json 22 | { 23 | "constructs": "^3.3.75", 24 | "@aws-cdk/aws-certificatemanager": "^1.103.0", 25 | "@aws-cdk/aws-cloudfront": "^1.103.0", 26 | "@aws-cdk/aws-iam": "^1.103.0", 27 | "@aws-cdk/aws-route53": "^1.103.0", 28 | "@aws-cdk/aws-route53-patterns": "^1.103.0", 29 | "@aws-cdk/aws-route53-targets": "^1.103.0", 30 | "@aws-cdk/aws-s3": "^1.103.0", 31 | "@aws-cdk/aws-s3-deployment": "^1.103.0", 32 | "@aws-cdk/core": "^1.103.0" 33 | } 34 | ``` 35 | 36 | #### For AWS CDK V2 usage: 37 | Install v2.0.0-alpha.1 and use it like below based on your chosen language, no extra steps 38 | 39 | ```typescript 40 | import * as cdk from '@aws-cdk/core'; 41 | import { SPADeploy } from 'cdk-spa-deploy'; 42 | 43 | export class CdkStack extends cdk.Stack { 44 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 45 | super(scope, id, props); 46 | 47 | new SPADeploy(this, 'spaDeploy') 48 | .createBasicSite({ 49 | indexDoc: 'index.html', 50 | websiteFolder: '../blog/dist/blog' 51 | }); 52 | 53 | new SPADeploy(this, 'cfDeploy') 54 | .createSiteWithCloudfront({ 55 | indexDoc: 'index.html', 56 | websiteFolder: '../blog/dist/blog' 57 | }); 58 | } 59 | } 60 | 61 | ``` 62 | 63 | ### Python 64 | ```console 65 | pip install cdk-spa-deploy 66 | ``` 67 | 68 | Note As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above. 69 | 70 | ```python 71 | from aws_cdk import core 72 | from spa_deploy import SPADeploy 73 | 74 | class PythonStack(core.Stack): 75 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 76 | super().__init__(scope, id, **kwargs) 77 | 78 | SPADeploy(self, 'spaDeploy').create_basic_site( 79 | index_doc='index.html', 80 | website_folder='../blog/blog/dist/blog' 81 | ) 82 | 83 | 84 | SPADeploy(self, 'cfDeploy').create_site_with_cloudfront( 85 | index_doc='index.html', 86 | website_folder='../blog/blog/dist/blog' 87 | ) 88 | ``` 89 | 90 | ### Dotnet / C# 91 | 92 | This project has now been published to nuget, more details to follow soon but you can find it [here](https://www.nuget.org/packages/CDKSPADeploy/1.80.0) 93 | 94 | Note As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above. 95 | 96 | ```bash 97 | # package manager 98 | Install-Package CDKSPADeploy -Version 1.80.0 99 | # .NET CLI 100 | dotnet add package CDKSPADeploy --version 1.80.0 101 | # Package reference 102 | 103 | # Paket CLI 104 | paket add CDKSPADeploy --version 1.80.0 105 | ``` 106 | 107 | ### Java 108 | 109 | A version has now been published to maven. 110 | 111 | Note As of version 103.0 this construct now declares peer dependencies rather than bundling them so you can use it with any version of CDK higher than 103.0 without waiting on me to release a new version. The downside is that you will need to install the dependencies it uses for yourself. The npm versioms are listed above. 112 | 113 | ```xml 114 | 115 | com.cdkpatterns 116 | CDKSPADeploy 117 | 1.81.0 118 | 119 | ``` 120 | 121 | ## Advanced Usage 122 | 123 | ### Auto Deploy From Hosted Zone Name 124 | 125 | If you purchased your domain through route 53 and already have a hosted zone then just use the name to deploy your site behind cloudfront. This handles the SSL cert and everything for you. 126 | 127 | ```typescript 128 | new SPADeploy(this, 'spaDeploy', { encryptBucket: true }) 129 | .createSiteFromHostedZone({ 130 | zoneName: 'cdkpatterns.com', 131 | indexDoc: 'index.html', 132 | websiteFolder: '../website/dist/website' 133 | }); 134 | 135 | ``` 136 | 137 | ### Custom Domain and SSL Certificates 138 | 139 | You can also pass the ARN for an SSL certification and your alias routes to cloudfront 140 | 141 | ```typescript 142 | import cdk = require('@aws-cdk/core'); 143 | import { SPADeploy } from 'cdk-spa-deploy'; 144 | 145 | export class CdkStack extends cdk.Stack { 146 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 147 | super(scope, id, props); 148 | 149 | new SPADeploy(this, 'cfDeploy') 150 | .createSiteWithCloudfront({ 151 | indexDoc: '../blog/dist/blog', 152 | certificateARN: 'arn:...', 153 | cfAliases: ['www.alias.com'] 154 | }); 155 | } 156 | } 157 | 158 | ``` 159 | 160 | ### Encrypted S3 Bucket 161 | 162 | Pass in one boolean to tell SPA Deploy to encrypt your website bucket 163 | 164 | ```typescript 165 | new SPADeploy(this, 'cfDeploy', {encryptBucket: true}).createBasicSite({ 166 | indexDoc: 'index.html', 167 | websiteFolder: 'website' 168 | }); 169 | 170 | ``` 171 | 172 | ### Custom Origin Behaviors 173 | 174 | Pass in an array of CloudFront Behaviors 175 | 176 | ```typescript 177 | new SPADeploy(this, 'cfDeploy').createSiteWithCloudfront({ 178 | indexDoc: 'index.html', 179 | websiteFolder: 'website', 180 | cfBehaviors: [ 181 | { 182 | isDefaultBehavior: true, 183 | allowedMethods: cf.CloudFrontAllowedMethods.ALL, 184 | forwardedValues: { 185 | queryString: true, 186 | cookies: { forward: 'all' }, 187 | headers: ['*'], 188 | }, 189 | }, 190 | { 191 | pathPattern: '/virtual-path', 192 | allowedMethods: cf.CloudFrontAllowedMethods.GET_HEAD, 193 | cachedMethods: cf.CloudFrontAllowedCachedMethods.GET_HEAD, 194 | }, 195 | ], 196 | }); 197 | ``` 198 | 199 | ### Restrict Access to Known IPs 200 | 201 | Pass in a boolean and an array of IP addresses and your site is locked down! 202 | 203 | ```typescript 204 | new SPADeploy(stack, 'spaDeploy', { 205 | encryptBucket: true, 206 | ipFilter: true, 207 | ipList: ['1.1.1.1'] 208 | }).createBasicSite({ 209 | indexDoc: 'index.html', 210 | websiteFolder: 'website' 211 | }) 212 | ``` 213 | 214 | ### Modifying S3 Bucket Created in Construct 215 | 216 | An object is now returned containing relevant artifacts created if you need to make any further modifications: 217 | * The S3 bucket is present for all of the methods 218 | * When a CloudFront Web distribution is created it will be present in the return object 219 | 220 | ```typescript 221 | export interface SPADeployment { 222 | readonly websiteBucket: s3.Bucket, 223 | } 224 | 225 | export interface SPADeploymentWithCloudFront extends SPADeployment { 226 | readonly distribution: CloudFrontWebDistribution, 227 | } 228 | ``` 229 | 230 | ## Issues / Feature Requests 231 | 232 | https://github.com/nideveloper/CDK-SPA-Deploy 233 | -------------------------------------------------------------------------------- /cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | "@aws-cdk/core:enableStackNameDuplicates": "true", 3 | "aws-cdk:enableDiffNoFail": "true" 4 | } 5 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/cdk-spa-deploy.ts" 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "/test" 4 | ], 5 | testMatch: [ '**/*.test.ts'], 6 | "transform": { 7 | "^.+\\.tsx?$": "ts-jest" 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './spa-deploy/spa-deploy-construct'; 2 | -------------------------------------------------------------------------------- /lib/spa-deploy/spa-deploy-construct.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CloudFrontWebDistribution, 3 | ViewerCertificate, 4 | OriginAccessIdentity, 5 | Behavior, 6 | SSLMethod, 7 | SecurityPolicyProtocol, 8 | } from 'aws-cdk-lib/aws-cloudfront'; 9 | import { PolicyStatement, Role, AnyPrincipal, Effect } from 'aws-cdk-lib/aws-iam'; 10 | import { HostedZone, ARecord, RecordTarget } from 'aws-cdk-lib/aws-route53'; 11 | import { DnsValidatedCertificate } from 'aws-cdk-lib/aws-certificatemanager'; 12 | import { HttpsRedirect } from 'aws-cdk-lib/aws-route53-patterns'; 13 | import { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets'; 14 | import { CfnOutput } from 'aws-cdk-lib'; 15 | import s3deploy= require('aws-cdk-lib/aws-s3-deployment'); 16 | import s3 = require('aws-cdk-lib/aws-s3'); 17 | import { Construct } from 'constructs'; 18 | 19 | export interface SPADeployConfig { 20 | readonly indexDoc:string, 21 | readonly errorDoc?:string, 22 | readonly websiteFolder: string, 23 | readonly certificateARN?: string, 24 | readonly cfBehaviors?: Behavior[], 25 | readonly cfAliases?: string[], 26 | readonly exportWebsiteUrlOutput?:boolean, 27 | readonly exportWebsiteUrlName?: string, 28 | readonly blockPublicAccess?:s3.BlockPublicAccess 29 | readonly sslMethod?: SSLMethod, 30 | readonly securityPolicy?: SecurityPolicyProtocol, 31 | readonly role?:Role, 32 | } 33 | 34 | export interface HostedZoneConfig { 35 | readonly indexDoc:string, 36 | readonly errorDoc?:string, 37 | readonly cfBehaviors?: Behavior[], 38 | readonly websiteFolder: string, 39 | readonly zoneName: string, 40 | readonly subdomain?: string, 41 | readonly role?: Role, 42 | } 43 | 44 | export interface SPAGlobalConfig { 45 | readonly encryptBucket?:boolean, 46 | readonly ipFilter?:boolean, 47 | readonly ipList?:string[], 48 | readonly role?:Role, 49 | } 50 | 51 | export interface SPADeployment { 52 | readonly websiteBucket: s3.Bucket, 53 | } 54 | 55 | export interface SPADeploymentWithCloudFront extends SPADeployment { 56 | readonly distribution: CloudFrontWebDistribution, 57 | } 58 | 59 | export class SPADeploy extends Construct { 60 | globalConfig: SPAGlobalConfig; 61 | 62 | constructor(scope: Construct, id:string, config?:SPAGlobalConfig) { 63 | super(scope, id); 64 | 65 | if (typeof config !== 'undefined') { 66 | this.globalConfig = config; 67 | } else { 68 | this.globalConfig = { 69 | encryptBucket: false, 70 | ipFilter: false, 71 | }; 72 | } 73 | } 74 | 75 | /** 76 | * Helper method to provide a configured s3 bucket 77 | */ 78 | private getS3Bucket(config:SPADeployConfig, isForCloudFront: boolean) { 79 | const bucketConfig:any = { 80 | websiteIndexDocument: config.indexDoc, 81 | websiteErrorDocument: config.errorDoc, 82 | publicReadAccess: true, 83 | }; 84 | 85 | if (this.globalConfig.encryptBucket === true) { 86 | bucketConfig.encryption = s3.BucketEncryption.S3_MANAGED; 87 | } 88 | 89 | if (this.globalConfig.ipFilter === true || isForCloudFront === true || typeof config.blockPublicAccess !== 'undefined') { 90 | bucketConfig.publicReadAccess = false; 91 | if (typeof config.blockPublicAccess !== 'undefined') { 92 | bucketConfig.blockPublicAccess = config.blockPublicAccess; 93 | } 94 | } 95 | 96 | const bucket = new s3.Bucket(this, 'WebsiteBucket', bucketConfig); 97 | 98 | if (this.globalConfig.ipFilter === true && isForCloudFront === false) { 99 | if (typeof this.globalConfig.ipList === 'undefined') { 100 | throw new Error('When IP Filter is true then the IP List is required'); 101 | } 102 | 103 | const bucketPolicy = new PolicyStatement(); 104 | bucketPolicy.addAnyPrincipal(); 105 | bucketPolicy.addActions('s3:GetObject'); 106 | bucketPolicy.addResources(`${bucket.bucketArn}/*`); 107 | bucketPolicy.addCondition('IpAddress', { 108 | 'aws:SourceIp': this.globalConfig.ipList, 109 | }); 110 | 111 | bucket.addToResourcePolicy(bucketPolicy); 112 | } 113 | 114 | //The below "reinforces" the IAM Role's attached policy, it's not required but it allows for customers using permission boundaries to write into the bucket. 115 | if (config.role) { 116 | bucket.addToResourcePolicy( 117 | new PolicyStatement({ 118 | actions: [ 119 | "s3:GetObject*", 120 | "s3:GetBucket*", 121 | "s3:List*", 122 | "s3:DeleteObject*", 123 | "s3:PutObject*", 124 | "s3:Abort*" 125 | ], 126 | effect: Effect.ALLOW, 127 | resources: [bucket.arnForObjects('*'), bucket.bucketArn], 128 | conditions: { 129 | StringEquals: { 130 | 'aws:PrincipalArn': config.role.roleArn, 131 | }, 132 | }, 133 | principals: [new AnyPrincipal()] 134 | }) 135 | ); 136 | } 137 | 138 | return bucket; 139 | } 140 | 141 | /** 142 | * Helper method to provide configuration for cloudfront 143 | */ 144 | private getCFConfig(websiteBucket:s3.Bucket, config:any, accessIdentity: OriginAccessIdentity, cert?:DnsValidatedCertificate) { 145 | const cfConfig:any = { 146 | originConfigs: [ 147 | { 148 | s3OriginSource: { 149 | s3BucketSource: websiteBucket, 150 | originAccessIdentity: accessIdentity, 151 | }, 152 | behaviors: config.cfBehaviors ? config.cfBehaviors : [{ isDefaultBehavior: true }], 153 | }, 154 | ], 155 | // We need to redirect all unknown routes back to index.html for angular routing to work 156 | errorConfigurations: [{ 157 | errorCode: 403, 158 | responsePagePath: (config.errorDoc ? `/${config.errorDoc}` : `/${config.indexDoc}`), 159 | responseCode: 200, 160 | }, 161 | { 162 | errorCode: 404, 163 | responsePagePath: (config.errorDoc ? `/${config.errorDoc}` : `/${config.indexDoc}`), 164 | responseCode: 200, 165 | }], 166 | }; 167 | 168 | if (typeof config.certificateARN !== 'undefined' && typeof config.cfAliases !== 'undefined') { 169 | cfConfig.aliasConfiguration = { 170 | acmCertRef: config.certificateARN, 171 | names: config.cfAliases, 172 | }; 173 | } 174 | if (typeof config.sslMethod !== 'undefined') { 175 | cfConfig.aliasConfiguration.sslMethod = config.sslMethod; 176 | } 177 | 178 | if (typeof config.securityPolicy !== 'undefined') { 179 | cfConfig.aliasConfiguration.securityPolicy = config.securityPolicy; 180 | } 181 | 182 | if (typeof config.zoneName !== 'undefined' && typeof cert !== 'undefined') { 183 | cfConfig.viewerCertificate = ViewerCertificate.fromAcmCertificate(cert, { 184 | aliases: [config.subdomain ? `${config.subdomain}.${config.zoneName}` : config.zoneName], 185 | }); 186 | } 187 | 188 | return cfConfig; 189 | } 190 | 191 | /** 192 | * Basic setup needed for a non-ssl, non vanity url, non cached s3 website 193 | */ 194 | public createBasicSite(config:SPADeployConfig): SPADeployment { 195 | const websiteBucket = this.getS3Bucket(config, false); 196 | 197 | new s3deploy.BucketDeployment(this, 'BucketDeployment', { 198 | sources: [s3deploy.Source.asset(config.websiteFolder)], 199 | role: config.role, 200 | destinationBucket: websiteBucket, 201 | }); 202 | 203 | const cfnOutputConfig:any = { 204 | description: 'The url of the website', 205 | value: websiteBucket.bucketWebsiteUrl, 206 | }; 207 | 208 | if (config.exportWebsiteUrlOutput === true) { 209 | if (typeof config.exportWebsiteUrlName === 'undefined' || config.exportWebsiteUrlName === '') { 210 | throw new Error('When Output URL as AWS Export property is true then the output name is required'); 211 | } 212 | cfnOutputConfig.exportName = config.exportWebsiteUrlName; 213 | } 214 | 215 | let output = new CfnOutput(this, 'URL', cfnOutputConfig); 216 | //set the output name to be the same as the export name 217 | if(typeof config.exportWebsiteUrlName !== 'undefined' && config.exportWebsiteUrlName !== ''){ 218 | output.overrideLogicalId(config.exportWebsiteUrlName); 219 | } 220 | 221 | return { websiteBucket }; 222 | } 223 | 224 | /** 225 | * This will create an s3 deployment fronted by a cloudfront distribution 226 | * It will also setup error forwarding and unauth forwarding back to indexDoc 227 | */ 228 | public createSiteWithCloudfront(config:SPADeployConfig): SPADeploymentWithCloudFront { 229 | const websiteBucket = this.getS3Bucket(config, true); 230 | const accessIdentity = new OriginAccessIdentity(this, 'OriginAccessIdentity', { comment: `${websiteBucket.bucketName}-access-identity` }); 231 | const distribution = new CloudFrontWebDistribution(this, 'cloudfrontDistribution', this.getCFConfig(websiteBucket, config, accessIdentity)); 232 | 233 | new s3deploy.BucketDeployment(this, 'BucketDeployment', { 234 | sources: [s3deploy.Source.asset(config.websiteFolder)], 235 | destinationBucket: websiteBucket, 236 | // Invalidate the cache for / and index.html when we deploy so that cloudfront serves latest site 237 | distribution, 238 | distributionPaths: ['/', `/${config.indexDoc}`], 239 | role: config.role, 240 | }); 241 | 242 | new CfnOutput(this, 'cloudfront domain', { 243 | description: 'The domain of the website', 244 | value: distribution.distributionDomainName, 245 | }); 246 | 247 | return { websiteBucket, distribution }; 248 | } 249 | 250 | /** 251 | * S3 Deployment, cloudfront distribution, ssl cert and error forwarding auto 252 | * configured by using the details in the hosted zone provided 253 | */ 254 | public createSiteFromHostedZone(config:HostedZoneConfig): SPADeploymentWithCloudFront { 255 | const websiteBucket = this.getS3Bucket(config, true); 256 | const zone = HostedZone.fromLookup(this, 'HostedZone', { domainName: config.zoneName }); 257 | const domainName = config.subdomain ? `${config.subdomain}.${config.zoneName}` : config.zoneName; 258 | const cert = new DnsValidatedCertificate(this, 'Certificate', { 259 | hostedZone: zone, 260 | domainName, 261 | region: 'us-east-1', 262 | }); 263 | 264 | const accessIdentity = new OriginAccessIdentity(this, 'OriginAccessIdentity', { comment: `${websiteBucket.bucketName}-access-identity` }); 265 | const distribution = new CloudFrontWebDistribution(this, 'cloudfrontDistribution', this.getCFConfig(websiteBucket, config, accessIdentity, cert)); 266 | 267 | new s3deploy.BucketDeployment(this, 'BucketDeployment', { 268 | sources: [s3deploy.Source.asset(config.websiteFolder)], 269 | destinationBucket: websiteBucket, 270 | // Invalidate the cache for / and index.html when we deploy so that cloudfront serves latest site 271 | distribution, 272 | role: config.role, 273 | distributionPaths: ['/', `/${config.indexDoc}`], 274 | }); 275 | 276 | new ARecord(this, 'Alias', { 277 | zone, 278 | recordName: domainName, 279 | target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)), 280 | }); 281 | 282 | if (!config.subdomain) { 283 | new HttpsRedirect(this, 'Redirect', { 284 | zone, 285 | recordNames: [`www.${config.zoneName}`], 286 | targetDomain: config.zoneName, 287 | }); 288 | } 289 | 290 | return { websiteBucket, distribution }; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-spa-deploy", 3 | "version": "2.0.0-alpha.1", 4 | "description": "This is an AWS CDK Construct to make deploying a single page website (Angular/React/Vue) to AWS S3 behind SSL/Cloudfront as easy as 5 lines of code.", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "publishConfig": { 8 | "registry": "https://registry.npmjs.org/", 9 | "access": "public" 10 | }, 11 | "scripts": { 12 | "build": "jsii", 13 | "build:watch": "jsii -w", 14 | "test": "tsc && jest", 15 | "lint": "eslint --ext .ts,.tsx --format node_modules/eslint-formatter-pretty .", 16 | "lint:fix": "eslint --fix --ext .ts,.tsx --format node_modules/eslint-formatter-pretty .", 17 | "cdk": "cdk", 18 | "package": "jsii-pacmak" 19 | }, 20 | "jsii": { 21 | "outdir": "dist", 22 | "targets": { 23 | "python": { 24 | "distName": "cdk-spa-deploy", 25 | "module": "spa_deploy" 26 | }, 27 | "java": { 28 | "package": "com.cdkpatterns", 29 | "maven": { 30 | "groupId": "com.cdkpatterns", 31 | "artifactId": "CDKSPADeploy" 32 | } 33 | }, 34 | "dotnet": { 35 | "namespace": "com.cdkpatterns", 36 | "packageId": "CDKSPADeploy" 37 | } 38 | } 39 | }, 40 | "awscdkio": { 41 | "twitter": "nideveloper" 42 | }, 43 | "devDependencies": { 44 | "@types/jest": "^26.0.23", 45 | "@types/node": "17.0.2", 46 | "@typescript-eslint/eslint-plugin": "^4.23.0", 47 | "@typescript-eslint/parser": "^4.23.0", 48 | "aws-cdk-lib": "^2.2.0", 49 | "constructs": "^10.0.13", 50 | "eslint": "^7.26.0", 51 | "eslint-config-airbnb-base": "^14.2.1", 52 | "eslint-formatter-pretty": "^4.0.0", 53 | "eslint-plugin-import": "^2.22.1", 54 | "eslint-plugin-jest": "^24.3.6", 55 | "jest": "^26.6.3", 56 | "jest-extended": "^0.11.5", 57 | "jest-junit": "^12.0.0", 58 | "jsii": "1.48.0", 59 | "jsii-pacmak": "1.48.0", 60 | "source-map-support": "^0.5.19", 61 | "ts-jest": "^26.5.6", 62 | "ts-node": "^9.1.1", 63 | "typescript": "~4.2.4" 64 | }, 65 | "dependencies": {}, 66 | "peerDependencies": { 67 | "constructs": "^10.0.13", 68 | "aws-cdk-lib": "^2.2.0" 69 | }, 70 | "keywords": [ 71 | "aws", 72 | "cdk", 73 | "spa", 74 | "website", 75 | "deploy", 76 | "cloudfront" 77 | ], 78 | "author": "hi@cdkpatterns.com", 79 | "repository": { 80 | "url": "https://github.com/nideveloper/CDK-SPA-Deploy.git", 81 | "type": "git" 82 | }, 83 | "license": "MIT" 84 | } 85 | -------------------------------------------------------------------------------- /test/cdk-spa-deploy.test.ts: -------------------------------------------------------------------------------- 1 | import { Match, Template } from "aws-cdk-lib/assertions"; 2 | import * as cf from 'aws-cdk-lib/aws-cloudfront'; 3 | import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; 4 | import { BlockPublicAccess } from 'aws-cdk-lib/aws-s3'; 5 | import { Stack, App } from 'aws-cdk-lib/'; 6 | import { SPADeploy } from '../lib'; 7 | 8 | 9 | test('Cloudfront Distribution Included', () => { 10 | const stack = new Stack(); 11 | // WHEN 12 | const deploy = new SPADeploy(stack, 'spaDeploy'); 13 | 14 | deploy.createSiteWithCloudfront({ 15 | indexDoc: 'index.html', 16 | websiteFolder: 'website', 17 | }); 18 | 19 | const template = Template.fromStack(stack); 20 | // THEN 21 | template.hasResourceProperties('AWS::S3::Bucket', 22 | Match.objectLike({ 23 | WebsiteConfiguration: { 24 | IndexDocument: 'index.html', 25 | }, 26 | })); 27 | 28 | template.hasResource('Custom::CDKBucketDeployment', {}); 29 | 30 | template.hasResourceProperties('AWS::CloudFront::Distribution', 31 | Match.objectLike({ 32 | DistributionConfig: { 33 | CustomErrorResponses: [ 34 | { 35 | ErrorCode: 403, 36 | ResponseCode: 200, 37 | ResponsePagePath: '/index.html', 38 | }, 39 | { 40 | ErrorCode: 404, 41 | ResponseCode: 200, 42 | ResponsePagePath: '/index.html', 43 | }, 44 | ], 45 | DefaultCacheBehavior: { 46 | ViewerProtocolPolicy: 'redirect-to-https', 47 | }, 48 | DefaultRootObject: 'index.html', 49 | HttpVersion: 'http2', 50 | IPV6Enabled: true, 51 | PriceClass: 'PriceClass_100', 52 | ViewerCertificate: { 53 | CloudFrontDefaultCertificate: true, 54 | }, 55 | }, 56 | })); 57 | 58 | template.hasResourceProperties('AWS::S3::BucketPolicy', 59 | Match.objectLike({ 60 | PolicyDocument: { 61 | Statement: [ 62 | Match.objectLike({ 63 | Action: 's3:GetObject', 64 | Effect: 'Allow' 65 | })], 66 | }, 67 | })); 68 | }); 69 | 70 | test('Cloudfront Distribution Included - with non-default error-doc cfg set', () => { 71 | const stack = new Stack(); 72 | // WHEN 73 | const deploy = new SPADeploy(stack, 'spaDeploy'); 74 | 75 | deploy.createSiteWithCloudfront({ 76 | indexDoc: 'index.html', 77 | errorDoc: 'error.html', 78 | websiteFolder: 'website', 79 | }); 80 | 81 | const template = Template.fromStack(stack); 82 | 83 | 84 | // THEN 85 | template.hasResourceProperties('AWS::S3::Bucket', 86 | Match.objectLike({ 87 | WebsiteConfiguration: { 88 | IndexDocument: 'index.html', 89 | ErrorDocument: 'error.html', 90 | }, 91 | })); 92 | 93 | template.hasResource('Custom::CDKBucketDeployment', {}); 94 | 95 | template.hasResourceProperties('AWS::CloudFront::Distribution', 96 | Match.objectLike({ 97 | DistributionConfig: { 98 | CustomErrorResponses: [ 99 | { 100 | ErrorCode: 403, 101 | ResponseCode: 200, 102 | ResponsePagePath: '/error.html', 103 | }, 104 | { 105 | ErrorCode: 404, 106 | ResponseCode: 200, 107 | ResponsePagePath: '/error.html', 108 | }, 109 | ], 110 | DefaultCacheBehavior: { 111 | ViewerProtocolPolicy: 'redirect-to-https', 112 | }, 113 | DefaultRootObject: 'index.html', 114 | HttpVersion: 'http2', 115 | IPV6Enabled: true, 116 | PriceClass: 'PriceClass_100', 117 | ViewerCertificate: { 118 | CloudFrontDefaultCertificate: true, 119 | }, 120 | }, 121 | })); 122 | 123 | template.hasResourceProperties('AWS::S3::BucketPolicy', 124 | Match.objectLike({ 125 | PolicyDocument: { 126 | Statement: [ 127 | Match.objectLike({ 128 | Action: 's3:GetObject', 129 | Effect: 'Allow' 130 | })], 131 | }, 132 | })); 133 | }); 134 | 135 | test('Cloudfront With Custom Cert and Aliases', () => { 136 | const stack = new Stack(); 137 | // WHEN 138 | const deploy = new SPADeploy(stack, 'spaDeploy'); 139 | 140 | deploy.createSiteWithCloudfront({ 141 | indexDoc: 'index.html', 142 | websiteFolder: 'website', 143 | certificateARN: 'arn:1234', 144 | cfAliases: ['www.test.com'], 145 | }); 146 | 147 | const template = Template.fromStack(stack); 148 | 149 | // THEN 150 | template.hasResourceProperties('AWS::S3::Bucket', 151 | Match.objectLike({ 152 | WebsiteConfiguration: { 153 | IndexDocument: 'index.html' 154 | }, 155 | })); 156 | 157 | template.hasResource('Custom::CDKBucketDeployment', {}); 158 | 159 | template.hasResourceProperties('AWS::CloudFront::Distribution', 160 | Match.objectLike({ 161 | DistributionConfig: { 162 | Aliases: [ 163 | 'www.test.com', 164 | ], 165 | ViewerCertificate: { 166 | AcmCertificateArn: 'arn:1234', 167 | SslSupportMethod: 'sni-only', 168 | }, 169 | }, 170 | })); 171 | }); 172 | 173 | 174 | test('Cloudfront With Custom Role', () => { 175 | const stack = new Stack(); 176 | // WHEN 177 | const deploy = new SPADeploy(stack, 'spaDeploy'); 178 | 179 | deploy.createSiteWithCloudfront({ 180 | indexDoc: 'index.html', 181 | websiteFolder: 'website', 182 | certificateARN: 'arn:1234', 183 | cfAliases: ['www.test.com'], 184 | role: new Role(stack, 'myRole', {roleName: 'testRole', assumedBy: new ServicePrincipal('lambda.amazonaws.com')}) 185 | }); 186 | 187 | const template = Template.fromStack(stack); 188 | 189 | // THEN 190 | template.hasResourceProperties('AWS::Lambda::Function', 191 | Match.objectLike({ 192 | Role: { 193 | "Fn::GetAtt": [ 194 | "myRoleE60D68E8", 195 | "Arn" 196 | ] 197 | } 198 | })); 199 | }); 200 | 201 | 202 | test('Basic Site Setup', () => { 203 | const stack = new Stack(); 204 | 205 | // WHEN 206 | const deploy = new SPADeploy(stack, 'spaDeploy'); 207 | 208 | deploy.createBasicSite({ 209 | indexDoc: 'index.html', 210 | websiteFolder: 'website', 211 | }); 212 | 213 | const template = Template.fromStack(stack); 214 | 215 | // THEN 216 | template.hasResourceProperties('AWS::S3::Bucket', 217 | Match.objectLike({ 218 | WebsiteConfiguration: { 219 | IndexDocument: 'index.html' 220 | }, 221 | })); 222 | 223 | template.hasResource('Custom::CDKBucketDeployment', {}); 224 | 225 | template.hasResourceProperties('AWS::S3::BucketPolicy', 226 | Match.objectLike({ 227 | PolicyDocument: { 228 | Statement: [ 229 | Match.objectLike({ 230 | Action: 's3:GetObject', 231 | Effect: 'Allow', 232 | Principal: { 233 | "AWS": "*" 234 | }, 235 | })], 236 | }, 237 | })); 238 | }); 239 | 240 | test('Basic Site Setup with Error Doc set', () => { 241 | const stack = new Stack(); 242 | 243 | // WHEN 244 | const deploy = new SPADeploy(stack, 'spaDeploy'); 245 | 246 | deploy.createBasicSite({ 247 | indexDoc: 'index.html', 248 | errorDoc: 'error.html', 249 | websiteFolder: 'website', 250 | }); 251 | 252 | const template = Template.fromStack(stack); 253 | 254 | // THEN 255 | template.hasResourceProperties('AWS::S3::Bucket', 256 | Match.objectLike({ 257 | WebsiteConfiguration: { 258 | IndexDocument: 'index.html', 259 | ErrorDocument: 'error.html', 260 | }, 261 | })); 262 | 263 | template.hasResource('Custom::CDKBucketDeployment', {}); 264 | 265 | template.hasResourceProperties('AWS::S3::BucketPolicy', 266 | Match.objectLike({ 267 | PolicyDocument: { 268 | Statement: [ 269 | Match.objectLike({ 270 | Action: 's3:GetObject', 271 | Effect: 'Allow', 272 | Principal: { 273 | "AWS": "*" 274 | }, 275 | })], 276 | }, 277 | })); 278 | }); 279 | 280 | test('Basic Site Setup with Custom Role', () => { 281 | const stack = new Stack(); 282 | 283 | // WHEN 284 | const deploy = new SPADeploy(stack, 'spaDeploy'); 285 | 286 | deploy.createBasicSite({ 287 | indexDoc: 'index.html', 288 | errorDoc: 'error.html', 289 | websiteFolder: 'website', 290 | role: new Role(stack, 'myRole', {roleName: 'testRole', assumedBy: new ServicePrincipal('lambda.amazonaws.com')}), 291 | }); 292 | 293 | const template = Template.fromStack(stack); 294 | 295 | // THEN 296 | template.hasResourceProperties('AWS::Lambda::Function', 297 | Match.objectLike({ 298 | Role: { 299 | "Fn::GetAtt": [ 300 | "myRoleE60D68E8", 301 | "Arn" 302 | ] 303 | } 304 | })); 305 | 306 | template.hasResourceProperties('AWS::S3::BucketPolicy', 307 | Match.objectLike({ 308 | PolicyDocument: { 309 | Statement: [ 310 | Match.objectLike({ 311 | Action: 's3:GetObject', 312 | Effect: 'Allow', 313 | Principal: { 314 | "AWS": "*" 315 | } 316 | }), 317 | Match.objectLike({ 318 | Action: [ 319 | "s3:GetObject*", 320 | "s3:GetBucket*", 321 | "s3:List*", 322 | "s3:DeleteObject*", 323 | "s3:PutObject*", 324 | "s3:Abort*" 325 | ], 326 | Condition: { 327 | StringEquals: { 328 | "aws:PrincipalArn": { 329 | "Fn::GetAtt": [ 330 | "myRoleE60D68E8", 331 | "Arn" 332 | ] 333 | } 334 | } 335 | }, 336 | Effect: 'Allow', 337 | Principal: { 338 | "AWS": "*" 339 | } 340 | })], 341 | } 342 | })); 343 | }); 344 | 345 | 346 | test('Basic Site Setup with Undefined Role', () => { 347 | const stack = new Stack(); 348 | 349 | // WHEN 350 | const deploy = new SPADeploy(stack, 'spaDeploy'); 351 | 352 | deploy.createBasicSite({ 353 | indexDoc: 'index.html', 354 | errorDoc: 'error.html', 355 | websiteFolder: 'website', 356 | role: undefined 357 | }); 358 | 359 | const template = Template.fromStack(stack); 360 | 361 | // THEN 362 | template.hasResourceProperties('AWS::Lambda::Function', 363 | Match.objectLike({ 364 | Runtime: "python3.7", 365 | Role: { 366 | "Fn::GetAtt": [ 367 | "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", 368 | "Arn" 369 | ] 370 | } 371 | })); 372 | }); 373 | 374 | 375 | test('Basic Site Setup, Encrypted Bucket', () => { 376 | const stack = new Stack(); 377 | 378 | // WHEN 379 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }) 380 | .createBasicSite({ 381 | indexDoc: 'index.html', 382 | websiteFolder: 'website', 383 | }); 384 | 385 | const template = Template.fromStack(stack); 386 | 387 | // THEN 388 | template.hasResourceProperties('AWS::S3::Bucket', 389 | Match.objectLike({ 390 | BucketEncryption: { 391 | ServerSideEncryptionConfiguration: [ 392 | { 393 | ServerSideEncryptionByDefault: { 394 | SSEAlgorithm: 'AES256', 395 | }, 396 | }, 397 | ], 398 | }, 399 | WebsiteConfiguration: { 400 | IndexDocument: 'index.html', 401 | }, 402 | })); 403 | 404 | template.hasResource('Custom::CDKBucketDeployment', {}); 405 | 406 | template.hasResourceProperties('AWS::S3::BucketPolicy', 407 | Match.objectLike({ 408 | PolicyDocument: { 409 | Statement: [ 410 | Match.objectLike({ 411 | Action: 's3:GetObject', 412 | Effect: 'Allow', 413 | Principal: { 414 | "AWS": "*" 415 | }, 416 | })], 417 | }, 418 | })); 419 | }); 420 | 421 | test('Cloudfront With Encrypted Bucket', () => { 422 | const stack = new Stack(); 423 | // WHEN 424 | const deploy = new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }); 425 | 426 | deploy.createSiteWithCloudfront({ 427 | indexDoc: 'index.html', 428 | websiteFolder: 'website', 429 | certificateARN: 'arn:1234', 430 | cfAliases: ['www.test.com'], 431 | }); 432 | 433 | const template = Template.fromStack(stack); 434 | 435 | // THEN 436 | template.hasResourceProperties('AWS::S3::Bucket', 437 | Match.objectLike({ 438 | BucketEncryption: { 439 | ServerSideEncryptionConfiguration: [ 440 | { 441 | ServerSideEncryptionByDefault: { 442 | SSEAlgorithm: 'AES256', 443 | }, 444 | }, 445 | ], 446 | }, 447 | WebsiteConfiguration: { 448 | IndexDocument: 'index.html', 449 | }, 450 | })); 451 | 452 | template.hasResource('Custom::CDKBucketDeployment', {}); 453 | 454 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 455 | DistributionConfig: { 456 | Aliases: [ 457 | 'www.test.com', 458 | ], 459 | ViewerCertificate: { 460 | AcmCertificateArn: 'arn:1234', 461 | SslSupportMethod: 'sni-only', 462 | }, 463 | }, 464 | })); 465 | }); 466 | 467 | test('Cloudfront With Custom Defined Behaviors', () => { 468 | const stack = new Stack(); 469 | 470 | // WHEN 471 | const deploy = new SPADeploy(stack, 'spaDeploy'); 472 | 473 | deploy.createSiteWithCloudfront({ 474 | indexDoc: 'index.html', 475 | websiteFolder: 'website', 476 | cfBehaviors: [ 477 | { 478 | isDefaultBehavior: true, 479 | allowedMethods: cf.CloudFrontAllowedMethods.ALL, 480 | forwardedValues: { 481 | queryString: true, 482 | cookies: { forward: 'all' }, 483 | headers: ['*'], 484 | }, 485 | }, 486 | { 487 | pathPattern: '/virtual-path', 488 | allowedMethods: cf.CloudFrontAllowedMethods.GET_HEAD, 489 | cachedMethods: cf.CloudFrontAllowedCachedMethods.GET_HEAD, 490 | }, 491 | ], 492 | }); 493 | 494 | const template = Template.fromStack(stack); 495 | 496 | // THEN 497 | template.hasResource('Custom::CDKBucketDeployment', {}); 498 | 499 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 500 | DistributionConfig: { 501 | CacheBehaviors: [ 502 | Match.objectLike({ 503 | AllowedMethods: ['GET', 'HEAD'], 504 | CachedMethods: ['GET', 'HEAD'], 505 | PathPattern: '/virtual-path', 506 | }), 507 | ], 508 | DefaultCacheBehavior: { 509 | AllowedMethods: ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'], 510 | ForwardedValues: { 511 | Cookies: { Forward: 'all' }, 512 | Headers: ['*'], 513 | QueryString: true, 514 | }, 515 | TargetOriginId: 'origin1', 516 | }, 517 | }, 518 | })); 519 | }); 520 | 521 | test('Cloudfront With Custom Security Policy', () => { 522 | const stack = new Stack(); 523 | // WHEN 524 | const deploy = new SPADeploy(stack, 'spaDeploy'); 525 | 526 | deploy.createSiteWithCloudfront({ 527 | indexDoc: 'index.html', 528 | websiteFolder: 'website', 529 | certificateARN: 'arn:1234', 530 | cfAliases: ['www.test.com'], 531 | securityPolicy: cf.SecurityPolicyProtocol.TLS_V1_2_2019, 532 | }); 533 | 534 | const template = Template.fromStack(stack); 535 | 536 | // THEN 537 | template.hasResource('Custom::CDKBucketDeployment', {}); 538 | 539 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 540 | DistributionConfig: { 541 | Aliases: [ 542 | 'www.test.com', 543 | ], 544 | ViewerCertificate: { 545 | AcmCertificateArn: 'arn:1234', 546 | SslSupportMethod: 'sni-only', 547 | MinimumProtocolVersion: 'TLSv1.2_2019', 548 | }, 549 | }, 550 | })); 551 | }); 552 | 553 | test('Cloudfront With Custom SSL Method', () => { 554 | const stack = new Stack(); 555 | // WHEN 556 | const deploy = new SPADeploy(stack, 'spaDeploy'); 557 | 558 | deploy.createSiteWithCloudfront({ 559 | indexDoc: 'index.html', 560 | websiteFolder: 'website', 561 | certificateARN: 'arn:1234', 562 | cfAliases: ['www.test.com'], 563 | sslMethod: cf.SSLMethod.VIP, 564 | }); 565 | 566 | const template = Template.fromStack(stack); 567 | 568 | // THEN 569 | template.hasResource('Custom::CDKBucketDeployment', {}); 570 | 571 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 572 | DistributionConfig: { 573 | Aliases: [ 574 | 'www.test.com', 575 | ], 576 | ViewerCertificate: { 577 | AcmCertificateArn: 'arn:1234', 578 | SslSupportMethod: 'vip', 579 | }, 580 | }, 581 | })); 582 | }); 583 | 584 | test('Basic Site Setup, IP Filter', () => { 585 | const stack = new Stack(); 586 | 587 | // WHEN 588 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true, ipFilter: true, ipList: ['1.1.1.1'] }) 589 | .createBasicSite({ 590 | indexDoc: 'index.html', 591 | websiteFolder: 'website', 592 | }); 593 | 594 | const template = Template.fromStack(stack); 595 | 596 | // THEN 597 | template.hasResourceProperties('AWS::S3::Bucket', 598 | Match.objectLike({ 599 | BucketEncryption: { 600 | ServerSideEncryptionConfiguration: [ 601 | { 602 | ServerSideEncryptionByDefault: { 603 | SSEAlgorithm: 'AES256', 604 | }, 605 | }, 606 | ], 607 | }, 608 | WebsiteConfiguration: { 609 | IndexDocument: 'index.html', 610 | }, 611 | })); 612 | 613 | template.hasResource('Custom::CDKBucketDeployment', {}); 614 | 615 | template.hasResourceProperties('AWS::S3::BucketPolicy', Match.objectLike({ 616 | PolicyDocument: { 617 | Statement: [ 618 | Match.objectLike({ 619 | Action: 's3:GetObject', 620 | Condition: { 621 | IpAddress: { 622 | 'aws:SourceIp': [ 623 | '1.1.1.1', 624 | ], 625 | }, 626 | }, 627 | Effect: 'Allow', 628 | Principal: { 629 | "AWS": "*" 630 | }, 631 | })], 632 | }, 633 | })); 634 | }); 635 | 636 | test('Create From Hosted Zone', () => { 637 | const app = new App(); 638 | const stack = new Stack(app, 'testStack', { 639 | env: { 640 | region: 'us-east-1', 641 | account: '1234', 642 | }, 643 | }); 644 | // WHEN 645 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }) 646 | .createSiteFromHostedZone({ 647 | zoneName: 'cdkspadeploy.com', 648 | indexDoc: 'index.html', 649 | websiteFolder: 'website', 650 | }); 651 | 652 | const template = Template.fromStack(stack); 653 | 654 | // THEN 655 | template.hasResourceProperties('AWS::S3::Bucket', 656 | Match.objectLike({ 657 | BucketEncryption: { 658 | ServerSideEncryptionConfiguration: [ 659 | { 660 | ServerSideEncryptionByDefault: { 661 | SSEAlgorithm: 'AES256', 662 | }, 663 | }, 664 | ], 665 | }, 666 | WebsiteConfiguration: { 667 | IndexDocument: 'index.html', 668 | }, 669 | })); 670 | 671 | template.hasResource('Custom::CDKBucketDeployment', {}); 672 | 673 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 674 | DistributionConfig: { 675 | Aliases: [ 676 | 'www.cdkspadeploy.com', 677 | ], 678 | ViewerCertificate: { 679 | SslSupportMethod: 'sni-only', 680 | }, 681 | }, 682 | })); 683 | 684 | template.hasResourceProperties('AWS::S3::BucketPolicy', 685 | Match.objectLike({ 686 | PolicyDocument: { 687 | Statement: [ 688 | Match.objectLike({ 689 | Action: 's3:GetObject', 690 | Effect: 'Allow' 691 | })], 692 | }, 693 | })); 694 | }); 695 | 696 | test('Create From Hosted Zone with subdomain', () => { 697 | const app = new App(); 698 | const stack = new Stack(app, 'testStack', { 699 | env: { 700 | region: 'us-east-1', 701 | account: '1234', 702 | }, 703 | }); 704 | // WHEN 705 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }) 706 | .createSiteFromHostedZone({ 707 | zoneName: 'cdkspadeploy.com', 708 | indexDoc: 'index.html', 709 | websiteFolder: 'website', 710 | subdomain: 'myhost', 711 | }); 712 | 713 | const template = Template.fromStack(stack); 714 | 715 | // THEN 716 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 717 | DistributionConfig: { 718 | Aliases: [ 719 | 'myhost.cdkspadeploy.com', 720 | ], 721 | ViewerCertificate: { 722 | SslSupportMethod: 'sni-only', 723 | }, 724 | }, 725 | })); 726 | }); 727 | 728 | test('Create From Hosted Zone with Custom Role', () => { 729 | const app = new App(); 730 | const stack = new Stack(app, 'testStack', { 731 | env: { 732 | region: 'us-east-1', 733 | account: '1234', 734 | }, 735 | }); 736 | // WHEN 737 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }) 738 | .createSiteFromHostedZone({ 739 | zoneName: 'cdkspadeploy.com', 740 | indexDoc: 'index.html', 741 | errorDoc: 'error.html', 742 | websiteFolder: 'website', 743 | role: new Role(stack, 'myRole', {roleName: 'testRole', assumedBy: new ServicePrincipal('lambda.amazonaws.com')}) 744 | }); 745 | 746 | const template = Template.fromStack(stack); 747 | 748 | // THEN 749 | 750 | template.hasResourceProperties('AWS::Lambda::Function', Match.objectLike({ 751 | Role: { 752 | "Fn::GetAtt": [ 753 | "myRoleE60D68E8", 754 | "Arn" 755 | ] 756 | } 757 | })); 758 | }); 759 | 760 | test('Create From Hosted Zone with Error Bucket', () => { 761 | const app = new App(); 762 | const stack = new Stack(app, 'testStack', { 763 | env: { 764 | region: 'us-east-1', 765 | account: '1234', 766 | }, 767 | }); 768 | // WHEN 769 | new SPADeploy(stack, 'spaDeploy', { encryptBucket: true }) 770 | .createSiteFromHostedZone({ 771 | zoneName: 'cdkspadeploy.com', 772 | indexDoc: 'index.html', 773 | errorDoc: 'error.html', 774 | websiteFolder: 'website', 775 | }); 776 | 777 | const template = Template.fromStack(stack); 778 | 779 | // THEN 780 | template.hasResourceProperties('AWS::S3::Bucket', Match.objectLike({ 781 | BucketEncryption: { 782 | ServerSideEncryptionConfiguration: [ 783 | { 784 | ServerSideEncryptionByDefault: { 785 | SSEAlgorithm: 'AES256', 786 | }, 787 | }, 788 | ], 789 | }, 790 | WebsiteConfiguration: { 791 | IndexDocument: 'index.html', 792 | ErrorDocument: 'error.html', 793 | }, 794 | })); 795 | 796 | template.hasResource('Custom::CDKBucketDeployment', {}); 797 | 798 | template.hasResourceProperties('AWS::CloudFront::Distribution', Match.objectLike({ 799 | DistributionConfig: { 800 | Aliases: [ 801 | 'www.cdkspadeploy.com', 802 | ], 803 | ViewerCertificate: { 804 | SslSupportMethod: 'sni-only', 805 | }, 806 | }, 807 | })); 808 | }); 809 | 810 | test('Basic Site Setup, Block Public Enabled', () => { 811 | const stack = new Stack(); 812 | 813 | // WHEN 814 | new SPADeploy(stack, 'spaDeploy') 815 | .createBasicSite({ 816 | indexDoc: 'index.html', 817 | websiteFolder: 'website', 818 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL 819 | }); 820 | 821 | const template = Template.fromStack(stack); 822 | 823 | // THEN 824 | template.hasResourceProperties('AWS::S3::Bucket', Match.objectLike({ 825 | WebsiteConfiguration: { 826 | IndexDocument: 'index.html', 827 | }, 828 | PublicAccessBlockConfiguration: { 829 | BlockPublicAcls: true, 830 | BlockPublicPolicy: true, 831 | IgnorePublicAcls: true, 832 | RestrictPublicBuckets: true, 833 | }, 834 | })); 835 | }); 836 | 837 | 838 | test('Basic Site Setup, URL Output Enabled With Name', () => { 839 | const stack = new Stack(); 840 | const exportName = 'test-export-name'; 841 | 842 | // WHEN 843 | new SPADeploy(stack, 'spaDeploy', {}) 844 | .createBasicSite({ 845 | indexDoc: 'index.html', 846 | websiteFolder: 'website', 847 | exportWebsiteUrlOutput: true, 848 | exportWebsiteUrlName: exportName, 849 | }); 850 | 851 | const template = Template.fromStack(stack); 852 | 853 | // THEN 854 | template.hasOutput(exportName, { 855 | "Export": { 856 | "Name": exportName 857 | } 858 | }); 859 | }); 860 | 861 | test('Basic Site Setup, URL Output Enabled With No Name', () => { 862 | const stack = new Stack(); 863 | 864 | // WHEN 865 | expect(() => {new SPADeploy(stack, 'spaDeploy', {}) 866 | .createBasicSite({ 867 | indexDoc: 'index.html', 868 | websiteFolder: 'website', 869 | exportWebsiteUrlOutput: true, 870 | })}).toThrowError(); 871 | }); 872 | 873 | test('Basic Site Setup, URL Output Not Enabled', () => { 874 | const stack = new Stack(); 875 | const exportName = 'test-export-name'; 876 | 877 | // WHEN 878 | new SPADeploy(stack, 'spaDeploy', {}) 879 | .createBasicSite({ 880 | indexDoc: 'index.html', 881 | websiteFolder: 'website', 882 | exportWebsiteUrlOutput: false, 883 | }); 884 | 885 | const template = Template.fromStack(stack); 886 | 887 | // THEN 888 | expect(() => {template.hasOutput(exportName, { 889 | "Export": { 890 | "Name": exportName 891 | } 892 | })}).toThrowError(); 893 | }); 894 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "charset": "utf8", 5 | "declaration": true, 6 | "experimentalDecorators": true, 7 | "incremental": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2018" 12 | ], 13 | "module": "CommonJS", 14 | "newLine": "lf", 15 | "noEmitOnError": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "resolveJsonModule": true, 23 | "strict": true, 24 | "strictNullChecks": true, 25 | "strictPropertyInitialization": true, 26 | "stripInternal": false, 27 | "target": "ES2018", 28 | "composite": false, 29 | "tsBuildInfoFile": "tsconfig.tsbuildinfo" 30 | }, 31 | "include": [ 32 | "**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ], 37 | "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" 38 | } 39 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 |

This is a test page

--------------------------------------------------------------------------------