@gradientedge/cdk-utils 9.1.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,28 @@ pnpm add @gradientedge/cdk-utils
57
57
  "@gradientedge/cdk-utils": "latest"
58
58
  ```
59
59
 
60
+ ## Testing
61
+
62
+ To run test cases, use the following command:
63
+
64
+ ```shell
65
+ pnpm run test
66
+ ```
67
+
68
+ To focus on the test and watch when you make changes, use the following command:
69
+
70
+ ```shell
71
+ pnpm test:watch static-asset-deployment-distribution-ref.test.ts
72
+ ```
73
+
74
+ ### Toolkit
75
+
76
+ There are common utilities that help with testing constructs which you can find in the [test tools](./src/test/tools/cdk) directory.
77
+
78
+ ### Debug
79
+
80
+ There is a debug utility that can be used to print out the contents of a `template`. This is useful for debugging and understanding the structure which you can find in the [debug](./src/test/tools/debug) directory.
81
+
60
82
  <!-- references -->
61
83
 
62
84
  [aws-cdk]: https://docs.aws.amazon.com/cdk/latest/guide/home.html
@@ -6,9 +6,9 @@ case `uname` in
6
6
  esac
7
7
 
8
8
  if [ -z "$NODE_PATH" ]; then
9
- export NODE_PATH="/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/dist/esm/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/dist/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/node_modules"
9
+ export NODE_PATH="/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/dist/esm/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/dist/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/node_modules"
10
10
  else
11
- export NODE_PATH="/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/dist/esm/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/dist/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules/rimraf/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.5/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/node_modules:$NODE_PATH"
11
+ export NODE_PATH="/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/dist/esm/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/dist/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules/rimraf/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/rimraf@5.0.7/node_modules:/home/runner/work/cdk-utils/cdk-utils/node_modules/.pnpm/node_modules:$NODE_PATH"
12
12
  fi
13
13
  if [ -x "$basedir/node" ]; then
14
14
  exec "$basedir/node" "$basedir/../rimraf/dist/esm/bin.mjs" "$@"
@@ -7,6 +7,7 @@ exports.CommonStack = void 0;
7
7
  const aws_cdk_lib_1 = require("aws-cdk-lib");
8
8
  const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
9
9
  const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
10
11
  const construct_1 = require("./construct");
11
12
  const app_root_path_1 = __importDefault(require("app-root-path"));
12
13
  const lodash_1 = __importDefault(require("lodash"));
@@ -71,7 +72,7 @@ class CommonStack extends aws_cdk_lib_1.Stack {
71
72
  return;
72
73
  }
73
74
  lodash_1.default.forEach(extraContexts, (context) => {
74
- const extraContextPath = `${app_root_path_1.default.path}/${context}`;
75
+ const extraContextPath = path_1.default.join(app_root_path_1.default.path, context);
75
76
  /* scenario where extra context is configured in cdk.json but absent in file system */
76
77
  if (!fs_1.default.existsSync(extraContextPath))
77
78
  throw `Extra context properties unavailable in path:${extraContextPath}`;
@@ -95,7 +96,7 @@ class CommonStack extends aws_cdk_lib_1.Stack {
95
96
  determineStageContexts() {
96
97
  const stage = this.node.tryGetContext('stage');
97
98
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv';
98
- const stageContextFilePath = `${app_root_path_1.default.path}/${stageContextPath}/${stage}.json`;
99
+ const stageContextFilePath = path_1.default.join(app_root_path_1.default.path, stageContextPath, `${stage}.json`);
99
100
  const debug = this.node.tryGetContext('debug');
100
101
  if ((0, common_1.isDevStage)(stage)) {
101
102
  if (debug)
@@ -1,4 +1,5 @@
1
1
  import { IBucket } from 'aws-cdk-lib/aws-s3';
2
+ import { IDistribution } from 'aws-cdk-lib/aws-cloudfront';
2
3
  import { Construct } from 'constructs';
3
4
  import { CommonConstruct } from '../../common';
4
5
  import { StaticAssetDeploymentProps } from './types';
@@ -11,16 +12,15 @@ import { StaticAssetDeploymentProps } from './types';
11
12
  * class CustomConstruct extends StaticAssetDeployment {
12
13
  * constructor(parent: Construct, id: string, props: StaticAssetDeploymentProps) {
13
14
  * super(parent, id, props)
14
- * this.props = props
15
- * this.id = id
16
15
  * this.initResources()
17
16
  * }
18
17
  * }
19
18
  */
20
19
  export declare class StaticAssetDeployment extends CommonConstruct {
20
+ staticAssetBucket: IBucket;
21
+ cloudfrontDistribution?: IDistribution;
21
22
  props: StaticAssetDeploymentProps;
22
23
  id: string;
23
- staticAssetBucket: IBucket;
24
24
  constructor(parent: Construct, id: string, props: StaticAssetDeploymentProps);
25
25
  /**
26
26
  * @summary Initialise and provision resources
@@ -30,6 +30,10 @@ export declare class StaticAssetDeployment extends CommonConstruct {
30
30
  * @summary Create the static asset bucket
31
31
  */
32
32
  protected createAssetBucket(): void;
33
+ /**
34
+ * @summary Distribute the load for the static asset bucket if both distribution and paths are provided
35
+ */
36
+ protected resolveDistribution(): void;
33
37
  /**
34
38
  * @summary Deploy the static assets into the static asset bucket
35
39
  */
@@ -4,9 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.StaticAssetDeployment = void 0;
7
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
7
8
  const aws_s3_deployment_1 = require("aws-cdk-lib/aws-s3-deployment");
8
9
  const lodash_1 = __importDefault(require("lodash"));
9
10
  const common_1 = require("../../common");
11
+ const app_root_path_1 = __importDefault(require("app-root-path"));
12
+ const path_1 = __importDefault(require("path"));
10
13
  /**
11
14
  * @classdesc Provides a construct to create and deploy static assets into S3 bucket
12
15
  * @example
@@ -16,28 +19,27 @@ const common_1 = require("../../common");
16
19
  * class CustomConstruct extends StaticAssetDeployment {
17
20
  * constructor(parent: Construct, id: string, props: StaticAssetDeploymentProps) {
18
21
  * super(parent, id, props)
19
- * this.props = props
20
- * this.id = id
21
22
  * this.initResources()
22
23
  * }
23
24
  * }
24
25
  */
25
26
  class StaticAssetDeployment extends common_1.CommonConstruct {
26
- /* construct properties */
27
- props;
28
- id;
29
27
  /* construct resources */
30
28
  staticAssetBucket;
29
+ cloudfrontDistribution;
30
+ props;
31
+ id;
31
32
  constructor(parent, id, props) {
32
33
  super(parent, id, props);
33
- this.props = props;
34
34
  this.id = id;
35
+ this.props = props;
35
36
  }
36
37
  /**
37
38
  * @summary Initialise and provision resources
38
39
  */
39
40
  initResources() {
40
41
  this.createAssetBucket();
42
+ this.resolveDistribution();
41
43
  this.deployStaticAssets();
42
44
  }
43
45
  /**
@@ -46,14 +48,65 @@ class StaticAssetDeployment extends common_1.CommonConstruct {
46
48
  createAssetBucket() {
47
49
  this.staticAssetBucket = this.s3Manager.createS3Bucket(`${this.id}-sa-bucket`, this, this.props.staticAssetBucket);
48
50
  }
51
+ /**
52
+ * @summary Distribute the load for the static asset bucket if both distribution and paths are provided
53
+ */
54
+ resolveDistribution() {
55
+ if (this.props.cloudFrontDistribution &&
56
+ (this.props.cloudFrontDistribution.domainName || this.props.cloudFrontDistribution.domainNameRef) &&
57
+ (this.props.cloudFrontDistribution.distributionId || this.props.cloudFrontDistribution.distributionIdRef) &&
58
+ this.props.cloudFrontDistribution.invalidationPaths &&
59
+ this.props.cloudFrontDistribution.invalidationPaths.length > 0) {
60
+ let domainName = this.props.cloudFrontDistribution.domainName;
61
+ if (this.props.cloudFrontDistribution.domainNameRef) {
62
+ domainName = aws_cdk_lib_1.Fn.importValue(this.props.cloudFrontDistribution.domainNameRef);
63
+ }
64
+ let distributionId = this.props.cloudFrontDistribution.distributionId;
65
+ if (this.props.cloudFrontDistribution.distributionIdRef) {
66
+ distributionId = aws_cdk_lib_1.Fn.importValue(this.props.cloudFrontDistribution.distributionIdRef);
67
+ }
68
+ const distributionAttributes = {
69
+ domainName: domainName,
70
+ distributionId: distributionId,
71
+ };
72
+ this.cloudfrontDistribution = this.cloudFrontManager.resolveDistribution(this, distributionAttributes);
73
+ }
74
+ }
49
75
  /**
50
76
  * @summary Deploy the static assets into the static asset bucket
51
77
  */
52
78
  deployStaticAssets() {
79
+ let sources = [];
80
+ if (Array.isArray(this.props.staticAssetSources) &&
81
+ this.props.staticAssetSources.length > 0 &&
82
+ typeof this.props.staticAssetSources[0] === 'string') {
83
+ sources = this.props.staticAssetSources.map(source => {
84
+ const resolvedPath = path_1.default.join(app_root_path_1.default.path, source);
85
+ return aws_s3_deployment_1.Source.asset(resolvedPath);
86
+ });
87
+ }
88
+ else {
89
+ sources = this.props.staticAssetSources;
90
+ }
91
+ let distributionOptions = {};
92
+ if (this.cloudfrontDistribution) {
93
+ distributionOptions = {
94
+ distribution: this.cloudfrontDistribution,
95
+ distributionPaths: this.props.cloudFrontDistribution?.invalidationPaths,
96
+ };
97
+ }
98
+ let destinationKeyPrefixOptions = {};
99
+ if (this.props.destinationKeyPrefix) {
100
+ destinationKeyPrefixOptions = {
101
+ destinationKeyPrefix: this.props.destinationKeyPrefix,
102
+ };
103
+ }
53
104
  new aws_s3_deployment_1.BucketDeployment(this, `${this.id}-static-deployment`, {
54
105
  ...this.props.staticAssetDeployment,
55
106
  destinationBucket: this.staticAssetBucket,
56
- sources: this.props.staticAssetSources,
107
+ sources: sources,
108
+ ...destinationKeyPrefixOptions,
109
+ ...distributionOptions,
57
110
  });
58
111
  const staticAssetsForExport = this.props.staticAssetsForExport;
59
112
  if (!staticAssetsForExport)
@@ -4,9 +4,38 @@ export interface AssetExport {
4
4
  key: string;
5
5
  value: string;
6
6
  }
7
+ /**
8
+ * The CloudFront distribution to associate with the bucket.
9
+ * When value is configured, the construct will invalidate the distribution after the deployment.
10
+ * Use either domainName or distributionId or domainNameRef or distributionIdRef.
11
+ */
12
+ export interface StaticCloudFrontDistribution {
13
+ /**
14
+ * @summary The domain name to associate with the bucket.
15
+ */
16
+ domainName?: string;
17
+ /**
18
+ * @summary The distribution ID to associate with the bucket.
19
+ */
20
+ distributionId?: string;
21
+ /**
22
+ * @summary The reference to domain name to associate with the bucket.
23
+ */
24
+ domainNameRef?: string;
25
+ /**
26
+ * @summary The reference to distribution ID to associate with the bucket.
27
+ */
28
+ distributionIdRef?: string;
29
+ /**
30
+ * @summary The paths to invalidate after deployment. Default is ['/*']
31
+ */
32
+ invalidationPaths: string[];
33
+ }
7
34
  export interface StaticAssetDeploymentProps extends CommonStackProps {
8
35
  staticAssetBucket: S3BucketProps;
9
36
  staticAssetDeployment: BucketDeploymentProps;
10
- staticAssetSources: any[];
37
+ staticAssetSources: any[] | string[];
11
38
  staticAssetsForExport?: AssetExport[];
39
+ destinationKeyPrefix?: string;
40
+ cloudFrontDistribution?: StaticCloudFrontDistribution;
12
41
  }
@@ -1,6 +1,6 @@
1
1
  import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';
2
2
  import * as cf from 'aws-cdk-lib/aws-cloudfront';
3
- import { FunctionAssociation, IResponseHeadersPolicy, OriginAccessIdentity } from 'aws-cdk-lib/aws-cloudfront';
3
+ import { IDistribution, DistributionAttributes, FunctionAssociation, IResponseHeadersPolicy, OriginAccessIdentity } from 'aws-cdk-lib/aws-cloudfront';
4
4
  import { HttpOrigin, S3Origin } from 'aws-cdk-lib/aws-cloudfront-origins';
5
5
  import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2';
6
6
  import { IAccessPoint } from 'aws-cdk-lib/aws-efs';
@@ -105,4 +105,5 @@ export declare class CloudFrontManager {
105
105
  * @param props
106
106
  */
107
107
  createCloudfrontFunction(id: string, scope: CommonConstruct, props: CloudfrontFunctionProps): cf.Function;
108
+ resolveDistribution(scope: CommonConstruct, props: DistributionAttributes): IDistribution;
108
109
  }
@@ -297,5 +297,8 @@ class CloudFrontManager {
297
297
  (0, utils_1.createCfnOutput)(`${id}-functionName`, scope, cloudfrontFunction.functionName);
298
298
  return cloudfrontFunction;
299
299
  }
300
+ resolveDistribution(scope, props) {
301
+ return aws_cloudfront_1.Distribution.fromDistributionAttributes(scope, `${scope.node.id}-sa-distribution`, props);
302
+ }
300
303
  }
301
304
  exports.CloudFrontManager = CloudFrontManager;
@@ -10,6 +10,7 @@ const app_root_path_1 = __importDefault(require("app-root-path"));
10
10
  const cdktf_1 = require("cdktf");
11
11
  const lodash_1 = __importDefault(require("lodash"));
12
12
  const common_1 = require("../../common");
13
+ const path_1 = __importDefault(require("path"));
13
14
  /**
14
15
  * @classdesc Common stack to use as a base for all higher level constructs.
15
16
  * @example
@@ -66,7 +67,7 @@ class CommonAzureStack extends cdktf_1.TerraformStack {
66
67
  return;
67
68
  }
68
69
  lodash_1.default.forEach(extraContexts, (context) => {
69
- const extraContextPath = `${app_root_path_1.default.path}/${context}`;
70
+ const extraContextPath = path_1.default.join(app_root_path_1.default.path, context);
70
71
  /* scenario where extra context is configured in cdk.json but absent in file system */
71
72
  if (!fs_1.default.existsSync(extraContextPath))
72
73
  throw `Extra context properties unavailable in path:${extraContextPath}`;
@@ -90,7 +91,7 @@ class CommonAzureStack extends cdktf_1.TerraformStack {
90
91
  determineStageContexts() {
91
92
  const stage = this.node.tryGetContext('stage');
92
93
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv';
93
- const stageContextFilePath = `${app_root_path_1.default.path}/${stageContextPath}/${stage}.json`;
94
+ const stageContextFilePath = path_1.default.join(app_root_path_1.default.path, stageContextPath, `${stage}.json`);
94
95
  const debug = this.node.tryGetContext('debug');
95
96
  if ((0, common_1.isDevStage)(stage)) {
96
97
  if (debug)
@@ -9,6 +9,7 @@ const app_root_path_1 = __importDefault(require("app-root-path"));
9
9
  const cdktf_1 = require("cdktf");
10
10
  const lodash_1 = __importDefault(require("lodash"));
11
11
  const common_1 = require("../../common");
12
+ const path_1 = __importDefault(require("path"));
12
13
  /**
13
14
  * @classdesc Common stack to use as a base for all higher level constructs.
14
15
  * @example
@@ -65,7 +66,7 @@ class CommonCloudflareStack extends cdktf_1.TerraformStack {
65
66
  return;
66
67
  }
67
68
  lodash_1.default.forEach(extraContexts, (context) => {
68
- const extraContextPath = `${app_root_path_1.default.path}/${context}`;
69
+ const extraContextPath = path_1.default.join(app_root_path_1.default.path, context);
69
70
  /* scenario where extra context is configured in cdk.json but absent in file system */
70
71
  if (!fs_1.default.existsSync(extraContextPath))
71
72
  throw `Extra context properties unavailable in path:${extraContextPath}`;
@@ -89,7 +90,7 @@ class CommonCloudflareStack extends cdktf_1.TerraformStack {
89
90
  determineStageContexts() {
90
91
  const stage = this.node.tryGetContext('stage');
91
92
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv';
92
- const stageContextFilePath = `${app_root_path_1.default.path}/${stageContextPath}/${stage}.json`;
93
+ const stageContextFilePath = path_1.default.join(app_root_path_1.default.path, stageContextPath, `${stage}.json`);
93
94
  const debug = this.node.tryGetContext('debug');
94
95
  if ((0, common_1.isDevStage)(stage)) {
95
96
  if (debug)
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@gradientedge/cdk-utils",
3
- "version": "9.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "Utilities for AWS CDK provisioning",
5
5
  "main": "dist/index.js",
6
6
  "engines": {
7
- "node": ">=16 <=20"
7
+ "node": ">=16 <=20",
8
+ "pnpm": "=8"
8
9
  },
10
+ "packageManager": "pnpm@8.15.8",
9
11
  "repository": {
10
12
  "type": "git",
11
13
  "url": "git+https://github.com/gradientedge/cdk-utils.git"
@@ -38,6 +40,7 @@
38
40
  "prettier": "npx prettier --cache --check \"**/*.{ts,json,md}\"",
39
41
  "prettify": "npx prettier --cache --write \"**/*.{ts,json,md}\"",
40
42
  "test": "npx rimraf coverage && npx jest --ci --maxWorkers=100%",
43
+ "test:watch": "npx jest --coverage=false --watchAll",
41
44
  "update:deps": "ncu -u --deep --reject react,react-dom",
42
45
  "validate": "pnpm prettier && pnpm lint && pnpm test"
43
46
  },
@@ -57,7 +60,7 @@
57
60
  "@types/node": "^20.12.7",
58
61
  "@types/uuid": "^9.0.8",
59
62
  "app-root-path": "^3.1.0",
60
- "aws-cdk-lib": "^2.137.0",
63
+ "aws-cdk-lib": "^2.141.0",
61
64
  "cdktf": "^0.20.7",
62
65
  "cdktf-local-exec": "^0.5.13",
63
66
  "constructs": "^10.3.0",
@@ -101,7 +104,8 @@
101
104
  "taffydb": "^2.7.3",
102
105
  "ts-jest": "^29.1.2",
103
106
  "ts-node": "^10.9.2",
104
- "typescript": "5.4.5"
107
+ "typescript": "5.4.5",
108
+ "yaml": "^2.4.2"
105
109
  },
106
110
  "optionalDependencies": {
107
111
  "prop-types": "^15.8.1",
@@ -1,6 +1,8 @@
1
1
  import { App, Stack, StackProps } from 'aws-cdk-lib'
2
2
  import { Runtime } from 'aws-cdk-lib/aws-lambda'
3
3
  import fs from 'fs'
4
+ import path from 'path'
5
+
4
6
  import { CommonConstruct } from './construct'
5
7
  import { CommonStackProps } from './types'
6
8
 
@@ -77,7 +79,7 @@ export class CommonStack extends Stack {
77
79
  }
78
80
 
79
81
  _.forEach(extraContexts, (context: string) => {
80
- const extraContextPath = `${appRoot.path}/${context}`
82
+ const extraContextPath = path.join(appRoot.path, context)
81
83
 
82
84
  /* scenario where extra context is configured in cdk.json but absent in file system */
83
85
  if (!fs.existsSync(extraContextPath)) throw `Extra context properties unavailable in path:${extraContextPath}`
@@ -104,7 +106,8 @@ export class CommonStack extends Stack {
104
106
  protected determineStageContexts() {
105
107
  const stage = this.node.tryGetContext('stage')
106
108
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv'
107
- const stageContextFilePath = `${appRoot.path}/${stageContextPath}/${stage}.json`
109
+ const stageContextFilePath = path.join(appRoot.path, stageContextPath, `${stage}.json`)
110
+
108
111
  const debug = this.node.tryGetContext('debug')
109
112
 
110
113
  if (isDevStage(stage)) {
@@ -1,9 +1,13 @@
1
+ import { Fn } from 'aws-cdk-lib'
1
2
  import { IBucket } from 'aws-cdk-lib/aws-s3'
2
- import { BucketDeployment } from 'aws-cdk-lib/aws-s3-deployment'
3
+ import { IDistribution, DistributionAttributes } from 'aws-cdk-lib/aws-cloudfront'
3
4
  import { Construct } from 'constructs'
5
+ import { BucketDeployment, Source, BucketDeploymentProps, ISource } from 'aws-cdk-lib/aws-s3-deployment'
4
6
  import _ from 'lodash'
5
7
  import { CommonConstruct } from '../../common'
6
8
  import { StaticAssetDeploymentProps } from './types'
9
+ import appRoot from 'app-root-path'
10
+ import path from 'path'
7
11
 
8
12
  /**
9
13
  * @classdesc Provides a construct to create and deploy static assets into S3 bucket
@@ -14,31 +18,28 @@ import { StaticAssetDeploymentProps } from './types'
14
18
  * class CustomConstruct extends StaticAssetDeployment {
15
19
  * constructor(parent: Construct, id: string, props: StaticAssetDeploymentProps) {
16
20
  * super(parent, id, props)
17
- * this.props = props
18
- * this.id = id
19
21
  * this.initResources()
20
22
  * }
21
23
  * }
22
24
  */
23
25
  export class StaticAssetDeployment extends CommonConstruct {
24
- /* construct properties */
25
- props: StaticAssetDeploymentProps
26
- id: string
27
-
28
26
  /* construct resources */
29
27
  staticAssetBucket: IBucket
28
+ cloudfrontDistribution?: IDistribution
29
+ props: StaticAssetDeploymentProps
30
+ id: string
30
31
 
31
32
  constructor(parent: Construct, id: string, props: StaticAssetDeploymentProps) {
32
33
  super(parent, id, props)
33
- this.props = props
34
34
  this.id = id
35
+ this.props = props
35
36
  }
36
-
37
37
  /**
38
38
  * @summary Initialise and provision resources
39
39
  */
40
40
  public initResources() {
41
41
  this.createAssetBucket()
42
+ this.resolveDistribution()
42
43
  this.deployStaticAssets()
43
44
  }
44
45
 
@@ -49,14 +50,74 @@ export class StaticAssetDeployment extends CommonConstruct {
49
50
  this.staticAssetBucket = this.s3Manager.createS3Bucket(`${this.id}-sa-bucket`, this, this.props.staticAssetBucket)
50
51
  }
51
52
 
53
+ /**
54
+ * @summary Distribute the load for the static asset bucket if both distribution and paths are provided
55
+ */
56
+ protected resolveDistribution() {
57
+ if (
58
+ this.props.cloudFrontDistribution &&
59
+ (this.props.cloudFrontDistribution.domainName || this.props.cloudFrontDistribution.domainNameRef) &&
60
+ (this.props.cloudFrontDistribution.distributionId || this.props.cloudFrontDistribution.distributionIdRef) &&
61
+ this.props.cloudFrontDistribution.invalidationPaths &&
62
+ this.props.cloudFrontDistribution.invalidationPaths.length > 0
63
+ ) {
64
+ let domainName = this.props.cloudFrontDistribution.domainName
65
+ if (this.props.cloudFrontDistribution.domainNameRef) {
66
+ domainName = Fn.importValue(this.props.cloudFrontDistribution.domainNameRef)
67
+ }
68
+ let distributionId = this.props.cloudFrontDistribution.distributionId
69
+ if (this.props.cloudFrontDistribution.distributionIdRef) {
70
+ distributionId = Fn.importValue(this.props.cloudFrontDistribution.distributionIdRef)
71
+ }
72
+
73
+ const distributionAttributes = {
74
+ domainName: domainName,
75
+ distributionId: distributionId,
76
+ } as any as DistributionAttributes
77
+
78
+ this.cloudfrontDistribution = this.cloudFrontManager.resolveDistribution(this, distributionAttributes)
79
+ }
80
+ }
81
+
52
82
  /**
53
83
  * @summary Deploy the static assets into the static asset bucket
54
84
  */
55
85
  protected deployStaticAssets() {
86
+ let sources: Array<ISource> = []
87
+ if (
88
+ Array.isArray(this.props.staticAssetSources) &&
89
+ this.props.staticAssetSources.length > 0 &&
90
+ typeof this.props.staticAssetSources[0] === 'string'
91
+ ) {
92
+ sources = this.props.staticAssetSources.map(source => {
93
+ const resolvedPath = path.join(appRoot.path, source)
94
+ return Source.asset(resolvedPath)
95
+ })
96
+ } else {
97
+ sources = this.props.staticAssetSources as ISource[]
98
+ }
99
+
100
+ let distributionOptions: Pick<BucketDeploymentProps, 'distribution' | 'distributionPaths'> = {}
101
+ if (this.cloudfrontDistribution) {
102
+ distributionOptions = {
103
+ distribution: this.cloudfrontDistribution,
104
+ distributionPaths: this.props.cloudFrontDistribution?.invalidationPaths,
105
+ }
106
+ }
107
+
108
+ let destinationKeyPrefixOptions = {}
109
+ if (this.props.destinationKeyPrefix) {
110
+ destinationKeyPrefixOptions = {
111
+ destinationKeyPrefix: this.props.destinationKeyPrefix,
112
+ }
113
+ }
114
+
56
115
  new BucketDeployment(this, `${this.id}-static-deployment`, {
57
116
  ...this.props.staticAssetDeployment,
58
117
  destinationBucket: this.staticAssetBucket,
59
- sources: this.props.staticAssetSources,
118
+ sources: sources,
119
+ ...destinationKeyPrefixOptions,
120
+ ...distributionOptions,
60
121
  })
61
122
 
62
123
  const staticAssetsForExport = this.props.staticAssetsForExport
@@ -6,9 +6,39 @@ export interface AssetExport {
6
6
  value: string
7
7
  }
8
8
 
9
+ /**
10
+ * The CloudFront distribution to associate with the bucket.
11
+ * When value is configured, the construct will invalidate the distribution after the deployment.
12
+ * Use either domainName or distributionId or domainNameRef or distributionIdRef.
13
+ */
14
+ export interface StaticCloudFrontDistribution {
15
+ /**
16
+ * @summary The domain name to associate with the bucket.
17
+ */
18
+ domainName?: string
19
+ /**
20
+ * @summary The distribution ID to associate with the bucket.
21
+ */
22
+ distributionId?: string
23
+ /**
24
+ * @summary The reference to domain name to associate with the bucket.
25
+ */
26
+ domainNameRef?: string
27
+ /**
28
+ * @summary The reference to distribution ID to associate with the bucket.
29
+ */
30
+ distributionIdRef?: string
31
+ /**
32
+ * @summary The paths to invalidate after deployment. Default is ['/*']
33
+ */
34
+ invalidationPaths: string[]
35
+ }
36
+
9
37
  export interface StaticAssetDeploymentProps extends CommonStackProps {
10
38
  staticAssetBucket: S3BucketProps
11
39
  staticAssetDeployment: BucketDeploymentProps
12
- staticAssetSources: any[]
40
+ staticAssetSources: any[] | string[]
13
41
  staticAssetsForExport?: AssetExport[]
42
+ destinationKeyPrefix?: string
43
+ cloudFrontDistribution?: StaticCloudFrontDistribution
14
44
  }
@@ -2,6 +2,8 @@ import { Duration, Tags } from 'aws-cdk-lib'
2
2
  import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager'
3
3
  import * as cf from 'aws-cdk-lib/aws-cloudfront'
4
4
  import {
5
+ IDistribution,
6
+ DistributionAttributes,
5
7
  CloudFrontWebDistribution,
6
8
  Distribution,
7
9
  Function,
@@ -362,4 +364,8 @@ export class CloudFrontManager {
362
364
 
363
365
  return cloudfrontFunction
364
366
  }
367
+
368
+ public resolveDistribution(scope: CommonConstruct, props: DistributionAttributes): IDistribution {
369
+ return Distribution.fromDistributionAttributes(scope, `${scope.node.id}-sa-distribution`, props)
370
+ }
365
371
  }
@@ -7,6 +7,7 @@ import { TerraformStack } from 'cdktf'
7
7
  import { Construct } from 'constructs'
8
8
  import _ from 'lodash'
9
9
  import { isDevStage } from '../../common'
10
+ import path from 'path'
10
11
 
11
12
  /**
12
13
  * @classdesc Common stack to use as a base for all higher level constructs.
@@ -72,7 +73,7 @@ export class CommonAzureStack extends TerraformStack {
72
73
  }
73
74
 
74
75
  _.forEach(extraContexts, (context: string) => {
75
- const extraContextPath = `${appRoot.path}/${context}`
76
+ const extraContextPath = path.join(appRoot.path, context)
76
77
 
77
78
  /* scenario where extra context is configured in cdk.json but absent in file system */
78
79
  if (!fs.existsSync(extraContextPath)) throw `Extra context properties unavailable in path:${extraContextPath}`
@@ -99,7 +100,7 @@ export class CommonAzureStack extends TerraformStack {
99
100
  protected determineStageContexts() {
100
101
  const stage = this.node.tryGetContext('stage')
101
102
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv'
102
- const stageContextFilePath = `${appRoot.path}/${stageContextPath}/${stage}.json`
103
+ const stageContextFilePath = path.join(appRoot.path, stageContextPath, `${stage}.json`)
103
104
  const debug = this.node.tryGetContext('debug')
104
105
 
105
106
  if (isDevStage(stage)) {
@@ -7,7 +7,7 @@ import { TerraformStack } from 'cdktf'
7
7
  import { Construct } from 'constructs'
8
8
  import _ from 'lodash'
9
9
  import { isDevStage } from '../../common'
10
- import { RemoteBackend } from './constants'
10
+ import path from 'path'
11
11
 
12
12
  /**
13
13
  * @classdesc Common stack to use as a base for all higher level constructs.
@@ -72,7 +72,7 @@ export class CommonCloudflareStack extends TerraformStack {
72
72
  }
73
73
 
74
74
  _.forEach(extraContexts, (context: string) => {
75
- const extraContextPath = `${appRoot.path}/${context}`
75
+ const extraContextPath = path.join(appRoot.path, context)
76
76
 
77
77
  /* scenario where extra context is configured in cdk.json but absent in file system */
78
78
  if (!fs.existsSync(extraContextPath)) throw `Extra context properties unavailable in path:${extraContextPath}`
@@ -99,7 +99,7 @@ export class CommonCloudflareStack extends TerraformStack {
99
99
  protected determineStageContexts() {
100
100
  const stage = this.node.tryGetContext('stage')
101
101
  const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv'
102
- const stageContextFilePath = `${appRoot.path}/${stageContextPath}/${stage}.json`
102
+ const stageContextFilePath = path.join(appRoot.path, stageContextPath, `${stage}.json`)
103
103
  const debug = this.node.tryGetContext('debug')
104
104
 
105
105
  if (isDevStage(stage)) {