@sitblueprint/website-construct 0.1.4 → 0.1.6

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/lib/index.js CHANGED
@@ -15,15 +15,25 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Website = void 0;
36
+ exports.PreviewEnvironment = exports.Website = void 0;
27
37
  const constructs_1 = require("constructs");
28
38
  const s3 = __importStar(require("aws-cdk-lib/aws-s3"));
29
39
  const cloudfont = __importStar(require("aws-cdk-lib/aws-cloudfront"));
@@ -32,9 +42,14 @@ const certificatemanager = __importStar(require("aws-cdk-lib/aws-certificatemana
32
42
  const cdk = __importStar(require("aws-cdk-lib"));
33
43
  const iam = __importStar(require("aws-cdk-lib/aws-iam"));
34
44
  const route53 = __importStar(require("aws-cdk-lib/aws-route53"));
45
+ const dynamodb = __importStar(require("aws-cdk-lib/aws-dynamodb"));
46
+ const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
47
+ const apigateway = __importStar(require("aws-cdk-lib/aws-apigateway"));
48
+ const path = __importStar(require("path"));
35
49
  class Website extends constructs_1.Construct {
36
50
  bucket;
37
51
  distribution;
52
+ previewEnvironment;
38
53
  constructor(scope, id, props) {
39
54
  super(scope, id);
40
55
  this.bucket = new s3.Bucket(this, props.bucketName, {
@@ -54,6 +69,14 @@ class Website extends constructs_1.Construct {
54
69
  new iam.CanonicalUserPrincipal(oai.cloudFrontOriginAccessIdentityS3CanonicalUserId),
55
70
  ],
56
71
  }));
72
+ const domainNames = [];
73
+ if (props.domainConfig) {
74
+ domainNames.push(this._getFullDomainName(props.domainConfig));
75
+ if (props.domainConfig.includeRootDomain &&
76
+ props.domainConfig.subdomainName) {
77
+ domainNames.push(props.domainConfig.domainName);
78
+ }
79
+ }
57
80
  this.distribution = new cloudfont.Distribution(this, `${props.bucketName}-distribution`, {
58
81
  defaultBehavior: {
59
82
  origin: new origins.S3StaticWebsiteOrigin(this.bucket),
@@ -70,7 +93,7 @@ class Website extends constructs_1.Construct {
70
93
  priceClass: cloudfont.PriceClass.PRICE_CLASS_100,
71
94
  ...(props.domainConfig
72
95
  ? {
73
- domainNames: [this._getFullDomainName(props.domainConfig)],
96
+ domainNames: domainNames,
74
97
  certificate: this._getCertificate(props.domainConfig.certificateArn),
75
98
  }
76
99
  : {}),
@@ -85,6 +108,14 @@ class Website extends constructs_1.Construct {
85
108
  target: cdk.aws_route53.RecordTarget.fromAlias(new cdk.aws_route53_targets.CloudFrontTarget(this.distribution)),
86
109
  });
87
110
  domainARecord.node.addDependency(this.distribution);
111
+ if (props.domainConfig.includeRootDomain &&
112
+ props.domainConfig.subdomainName) {
113
+ new route53.ARecord(this, "RootDomainARecord", {
114
+ zone: hostedZone,
115
+ recordName: props.domainConfig.domainName,
116
+ target: cdk.aws_route53.RecordTarget.fromAlias(new cdk.aws_route53_targets.CloudFrontTarget(this.distribution)),
117
+ }).node.addDependency(this.distribution);
118
+ }
88
119
  }
89
120
  new cdk.CfnOutput(this, "cloudfront-website-url", {
90
121
  value: this.distribution.distributionDomainName,
@@ -100,6 +131,13 @@ class Website extends constructs_1.Construct {
100
131
  description: "Website URL",
101
132
  });
102
133
  }
134
+ if (props.previewConfig) {
135
+ this.previewEnvironment = new PreviewEnvironment(this, "PreviewEnvironment", {
136
+ ...props.previewConfig,
137
+ indexFile: props.indexFile,
138
+ errorFile: props.errorFile,
139
+ });
140
+ }
103
141
  }
104
142
  _getFullDomainName(domainConfig) {
105
143
  return domainConfig.subdomainName
@@ -111,4 +149,129 @@ class Website extends constructs_1.Construct {
111
149
  }
112
150
  }
113
151
  exports.Website = Website;
114
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDJDQUF1QztBQUN2Qyx1REFBeUM7QUFDekMsc0VBQXdEO0FBQ3hELDRFQUE4RDtBQUM5RCx1RkFBeUU7QUFDekUsaURBQW1DO0FBQ25DLHlEQUEyQztBQUMzQyxpRUFBbUQ7QUE4Qm5ELE1BQWEsT0FBUSxTQUFRLHNCQUFTO0lBQ3BCLE1BQU0sQ0FBWTtJQUNsQixZQUFZLENBQXlCO0lBRXJELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBbUI7UUFDM0QsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLFVBQVUsRUFBRTtZQUNsRCxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxnQkFBZ0IsRUFBRSxJQUFJO1lBQ3RCLGFBQWEsRUFBRSxHQUFHLENBQUMsYUFBYSxDQUFDLE9BQU87WUFDeEMsaUJBQWlCLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLGVBQWU7WUFDdkQsYUFBYSxFQUFFLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyx5QkFBeUI7WUFDL0QsVUFBVSxFQUFFLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVO1NBQzNDLENBQUMsQ0FBQztRQUNILE1BQU0sR0FBRyxHQUFHLElBQUksU0FBUyxDQUFDLG9CQUFvQixDQUM1QyxJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxNQUFNLENBQzFCLENBQUM7UUFDRixJQUFJLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUM3QixJQUFJLEdBQUcsQ0FBQyxlQUFlLENBQUM7WUFDdEIsT0FBTyxFQUFFLENBQUMs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QUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7SUFFTyxrQkFBa0IsQ0FBQyxZQUEwQjtRQUNuRCxPQUFPLFlBQVksQ0FBQyxhQUFhO1lBQy9CLENBQUMsQ0FBQyxHQUFHLFlBQVksQ0FBQyxhQUFhLElBQUksWUFBWSxDQUFDLFVBQVUsRUFBRTtZQUM1RCxDQUFDLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQztJQUM5QixDQUFDO0lBRU8sZUFBZSxDQUFDLEdBQVc7UUFDakMsT0FBTyxrQkFBa0IsQ0FBQyxXQUFXLENBQUMsa0JBQWtCLENBQ3RELElBQUksRUFDSixjQUFjLEVBQ2QsR0FBRyxDQUNKLENBQUM7SUFDSixDQUFDO0NBQ0Y7QUF6R0QsMEJBeUdDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCAqIGFzIHMzIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtczNcIjtcbmltcG9ydCAqIGFzIGNsb3VkZm9udCBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWNsb3VkZnJvbnRcIjtcbmltcG9ydCAqIGFzIG9yaWdpbnMgZnJvbSBcImF3cy1jZGstbGliL2F3cy1jbG91ZGZyb250LW9yaWdpbnNcIjtcbmltcG9ydCAqIGFzIGNlcnRpZmljYXRlbWFuYWdlciBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWNlcnRpZmljYXRlbWFuYWdlclwiO1xuaW1wb3J0ICogYXMgY2RrIGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0ICogYXMgaWFtIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtaWFtXCI7XG5pbXBvcnQgKiBhcyByb3V0ZTUzIGZyb20gXCJhd3MtY2RrLWxpYi9hd3Mtcm91dGU1M1wiO1xuXG5leHBvcnQgaW50ZXJmYWNlIERvbWFpbkNvbmZpZyB7XG4gIC8qKiBUaGUgcm9vdCBkb21haW4gbmFtZSAoZS5nLiwgZXhhbXBsZS5jb20pLlxuICAgKiBUaGVyZSBtdXN0IGJlIGFuIGFzc29jaWF0ZWQgaG9zdGVkIHpvbmUgaW4gUm91dGUgNTMgZm9yIHRoaXMgZG9tYWluLlxuICAgKi9cbiAgZG9tYWluTmFtZTogc3RyaW5nO1xuICAvKiogVGhlIHN1YmRvbWFpbiBuYW1lICovXG4gIHN1YmRvbWFpbk5hbWU6IHN0cmluZztcbiAgLyoqIFRoZSBBUk4gb2YgdGhlIFNTTCBjZXJ0aWZpY2F0ZSB0byB1c2UgZm9yIHRoZSBkb21haW4uICovXG4gIGNlcnRpZmljYXRlQXJuOiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgV2Vic2l0ZVByb3BzIHtcbiAgLyoqIFRoZSBuYW1lIG9mIHRoZSBTMyBidWNrZXQgdGhhdCB3aWxsIGhvc3QgdGhlIHdlYnNpdGUgY29udGVudC4gKi9cbiAgYnVja2V0TmFtZTogc3RyaW5nO1xuXG4gIC8qKiBUaGUgcGF0aCB0byB0aGUgaW5kZXggZG9jdW1lbnQgdGhhdCB3aWxsIGJlIHNlcnZlZCBhcyB0aGUgZGVmYXVsdCBwYWdlLiAqL1xuICBpbmRleEZpbGU6IHN0cmluZztcblxuICAvKiogVGhlIHBhdGggdG8gdGhlIGVycm9yIGRvY3VtZW50IHRoYXQgd2lsbCBiZSBzZXJ2ZWQgd2hlbiBhbiBlcnJvciBvY2N1cnMuICovXG4gIGVycm9yRmlsZTogc3RyaW5nO1xuXG4gIC8qKiBPcHRpb25hbCBjb25maWd1cmF0aW9uIGZvciBjdXN0b20gZG9tYWluIHNldHVwLiAqL1xuICBkb21haW5Db25maWc/OiBEb21haW5Db25maWc7XG5cbiAgLyoqIE9wdGlvbmFsIHBhdGggdG8gYSBjdXN0b20gNDA0IHBhZ2UuIElmIG5vdCBzcGVjaWZpZWQsIHRoZSBlcnJvciBmaWxlIHdpbGwgYmUgdXNlZC4gKi9cbiAgbm90Rm91bmRSZXNwb25zZVBhZ2VQYXRoPzogc3RyaW5nO1xufVxuXG5leHBvcnQgY2xhc3MgV2Vic2l0ZSBleHRlbmRzIENvbnN0cnVjdCB7XG4gIHB1YmxpYyByZWFkb25seSBidWNrZXQ6IHMzLkJ1Y2tldDtcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbjogY2xvdWRmb250LkRpc3RyaWJ1dGlvbjtcblxuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogV2Vic2l0ZVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcbiAgICB0aGlzLmJ1Y2tldCA9IG5ldyBzMy5CdWNrZXQodGhpcywgcHJvcHMuYnVja2V0TmFtZSwge1xuICAgICAgd2Vic2l0ZUluZGV4RG9jdW1lbnQ6IHByb3BzLmluZGV4RmlsZSxcbiAgICAgIHdlYnNpdGVFcnJvckRvY3VtZW50OiBwcm9wcy5lcnJvckZpbGUsXG4gICAgICBwdWJsaWNSZWFkQWNjZXNzOiB0cnVlLFxuICAgICAgcmVtb3ZhbFBvbGljeTogY2RrLlJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgIGJsb2NrUHVibGljQWNjZXNzOiBzMy5CbG9ja1B1YmxpY0FjY2Vzcy5CTE9DS19BQ0xTX09OTFksXG4gICAgICBhY2Nlc3NDb250cm9sOiBzMy5CdWNrZXRBY2Nlc3NDb250cm9sLkJVQ0tFVF9PV05FUl9GVUxMX0NPTlRST0wsXG4gICAgICBlbmNyeXB0aW9uOiBzMy5CdWNrZXRFbmNyeXB0aW9uLlMzX01BTkFHRUQsXG4gICAgfSk7XG4gICAgY29uc3Qgb2FpID0gbmV3IGNsb3VkZm9udC5PcmlnaW5BY2Nlc3NJZGVudGl0eShcbiAgICAgIHRoaXMsXG4gICAgICBgJHtwcm9wcy5idWNrZXROYW1lfS1PQUlgXG4gICAgKTtcbiAgICB0aGlzLmJ1Y2tldC5hZGRUb1Jlc291cmNlUG9saWN5KFxuICAgICAgbmV3IGlhbS5Qb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICBhY3Rpb25zOiBbXCJzMzpHZXRPYmplY3RcIl0sXG4gICAgICAgIHJlc291cmNlczogW3RoaXMuYnVja2V0LmFybkZvck9iamVjdHMoXCIqXCIpXSxcbiAgICAgICAgcHJpbmNpcGFsczogW1xuICAgICAgICAgIG5ldyBpYW0uQ2Fub25pY2FsVXNlclByaW5jaXBhbChcbiAgICAgICAgICAgIG9haS5jbG91ZEZyb250T3JpZ2luQWNjZXNzSWRlbnRpdHlTM0Nhbm9uaWNhbFVzZXJJZFxuICAgICAgICAgICksXG4gICAgICAgIF0sXG4gICAgICB9KVxuICAgICk7XG5cbiAgICB0aGlzLmRpc3RyaWJ1dGlvbiA9IG5ldyBjbG91ZGZvbnQuRGlzdHJpYnV0aW9uKFxuICAgICAgdGhpcyxcbiAgICAgIGAke3Byb3BzLmJ1Y2tldE5hbWV9LWRpc3RyaWJ1dGlvbmAsXG4gICAgICB7XG4gICAgICAgIGRlZmF1bHRCZWhhdmlvcjoge1xuICAgICAgICAgIG9yaWdpbjogbmV3IG9yaWdpbnMuUzNTdGF0aWNXZWJzaXRlT3JpZ2luKHRoaXMuYnVja2V0KSxcbiAgICAgICAgICB2aWV3ZXJQcm90b2NvbFBvbGljeTpcbiAgICAgICAgICAgIGNsb3VkZm9udC5WaWV3ZXJQcm90b2NvbFBvbGljeS5SRURJUkVDVF9UT19IVFRQUyxcbiAgICAgICAgfSxcbiAgICAgICAgZXJyb3JSZXNwb25zZXM6IFtcbiAgICAgICAgICB7XG4gICAgICAgICAgICBodHRwU3RhdHVzOiA0MDQsXG4gICAgICAgICAgICByZXNwb25zZUh0dHBTdGF0dXM6IDQwNCxcbiAgICAgICAgICAgIHJlc3BvbnNlUGFnZVBhdGg6IHByb3BzLm5vdEZvdW5kUmVzcG9uc2VQYWdlUGF0aCB8fCBgLzQwNC5odG1sYCxcbiAgICAgICAgICAgIHR0bDogY2RrLkR1cmF0aW9uLm1pbnV0ZXMoMzApLFxuICAgICAgICAgIH0sXG4gICAgICAgIF0sXG4gICAgICAgIHByaWNlQ2xhc3M6IGNsb3VkZm9udC5QcmljZUNsYXNzLlBSSUNFX0NMQVNTXzEwMCxcbiAgICAgICAgLi4uKHByb3BzLmRvbWFpbkNvbmZpZ1xuICAgICAgICAgID8ge1xuICAgICAgICAgICAgICBkb21haW5OYW1lczogW3RoaXMuX2dldEZ1bGxEb21haW5OYW1lKHByb3BzLmRvbWFpbkNvbmZpZyldLFxuICAgICAgICAgICAgICBjZXJ0aWZpY2F0ZTogdGhpcy5fZ2V0Q2VydGlmaWNhdGUoXG4gICAgICAgICAgICAgICAgcHJvcHMuZG9tYWluQ29uZmlnLmNlcnRpZmljYXRlQXJuXG4gICAgICAgICAgICAgICksXG4gICAgICAgICAgICB9XG4gICAgICAgICAgOiB7fSksXG4gICAgICB9XG4gICAgKTtcblxuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIGNvbnN0IGhvc3RlZFpvbmUgPSByb3V0ZTUzLkhvc3RlZFpvbmUuZnJvbUxvb2t1cCh0aGlzLCBcIkhvc3RlZFpvbmVcIiwge1xuICAgICAgICBkb21haW5OYW1lOiBwcm9wcy5kb21haW5Db25maWcuZG9tYWluTmFtZSxcbiAgICAgIH0pO1xuICAgICAgY29uc3QgZG9tYWluQVJlY29yZCA9IG5ldyByb3V0ZTUzLkFSZWNvcmQodGhpcywgXCJEb21haW5BUmVjb3JkXCIsIHtcbiAgICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgICAgcmVjb3JkTmFtZTogdGhpcy5fZ2V0RnVsbERvbWFpbk5hbWUocHJvcHMuZG9tYWluQ29uZmlnKSxcbiAgICAgICAgdGFyZ2V0OiBjZGsuYXdzX3JvdXRlNTMuUmVjb3JkVGFyZ2V0LmZyb21BbGlhcyhcbiAgICAgICAgICBuZXcgY2RrLmF3c19yb3V0ZTUzX3RhcmdldHMuQ2xvdWRGcm9udFRhcmdldCh0aGlzLmRpc3RyaWJ1dGlvbilcbiAgICAgICAgKSxcbiAgICAgIH0pO1xuICAgICAgZG9tYWluQVJlY29yZC5ub2RlLmFkZERlcGVuZGVuY3kodGhpcy5kaXN0cmlidXRpb24pO1xuICAgIH1cblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwiY2xvdWRmcm9udC13ZWJzaXRlLXVybFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZSxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIkNsb3VkRnJvbnQgRGlzdHJpYnV0aW9uIERvbWFpbiBOYW1lXCIsXG4gICAgfSk7XG5cbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcInMzLXdlYnNpdGUtdXJsXCIsIHtcbiAgICAgIHZhbHVlOiB0aGlzLmJ1Y2tldC5idWNrZXRXZWJzaXRlVXJsLFxuICAgICAgZGVzY3JpcHRpb246IFwiUzMgQnVja2V0IFdlYnNpdGUgVVJMXCIsXG4gICAgfSk7XG5cbiAgICBpZiAocHJvcHMuZG9tYWluQ29uZmlnKSB7XG4gICAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcIndlYnNpdGUtdXJsXCIsIHtcbiAgICAgICAgdmFsdWU6IHRoaXMuZGlzdHJpYnV0aW9uLmRvbWFpbk5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBcIldlYnNpdGUgVVJMXCIsXG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIF9nZXRGdWxsRG9tYWluTmFtZShkb21haW5Db25maWc6IERvbWFpbkNvbmZpZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGRvbWFpbkNvbmZpZy5zdWJkb21haW5OYW1lXG4gICAgICA/IGAke2RvbWFpbkNvbmZpZy5zdWJkb21haW5OYW1lfS4ke2RvbWFpbkNvbmZpZy5kb21haW5OYW1lfWBcbiAgICAgIDogZG9tYWluQ29uZmlnLmRvbWFpbk5hbWU7XG4gIH1cblxuICBwcml2YXRlIF9nZXRDZXJ0aWZpY2F0ZShhcm46IHN0cmluZyk6IGNlcnRpZmljYXRlbWFuYWdlci5JQ2VydGlmaWNhdGUge1xuICAgIHJldHVybiBjZXJ0aWZpY2F0ZW1hbmFnZXIuQ2VydGlmaWNhdGUuZnJvbUNlcnRpZmljYXRlQXJuKFxuICAgICAgdGhpcyxcbiAgICAgIGB3ZWJzaXRlLWNlcnRgLFxuICAgICAgYXJuXG4gICAgKTtcbiAgfVxufVxuIl19
152
+ class PreviewEnvironment extends constructs_1.Construct {
153
+ buckets;
154
+ distributions;
155
+ leaseTable;
156
+ api;
157
+ claimEndpoint;
158
+ heartbeatEndpoint;
159
+ releaseEndpoint;
160
+ constructor(scope, id, props) {
161
+ super(scope, id);
162
+ const bucketCount = props.bucketCount ?? 2;
163
+ if (bucketCount < 1) {
164
+ throw new Error("bucketCount must be greater than or equal to 1");
165
+ }
166
+ const indexFile = props.indexFile;
167
+ const errorFile = props.errorFile;
168
+ const createDistributions = props.createDistributions ?? true;
169
+ const maxLeaseHours = props.maxLeaseHours ?? 24;
170
+ const maxLeaseMs = cdk.Duration.hours(maxLeaseHours).toMilliseconds();
171
+ this.buckets = Array.from({ length: bucketCount }, (_, slotId) => {
172
+ const bucketName = `${props.bucketPrefix}-${slotId}`;
173
+ return new s3.Bucket(this, `PreviewBucket${slotId}`, {
174
+ bucketName,
175
+ websiteIndexDocument: indexFile,
176
+ websiteErrorDocument: errorFile,
177
+ publicReadAccess: true,
178
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
179
+ autoDeleteObjects: true,
180
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS_ONLY,
181
+ accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
182
+ encryption: s3.BucketEncryption.S3_MANAGED,
183
+ });
184
+ });
185
+ this.distributions = createDistributions
186
+ ? this.buckets.map((bucket, slotId) => {
187
+ const oai = new cloudfont.OriginAccessIdentity(this, `PreviewOAI${slotId}`);
188
+ bucket.addToResourcePolicy(new iam.PolicyStatement({
189
+ actions: ["s3:GetObject"],
190
+ resources: [bucket.arnForObjects("*")],
191
+ principals: [
192
+ new iam.CanonicalUserPrincipal(oai.cloudFrontOriginAccessIdentityS3CanonicalUserId),
193
+ ],
194
+ }));
195
+ return new cloudfont.Distribution(this, `PreviewDistribution${slotId}`, {
196
+ defaultBehavior: {
197
+ origin: new origins.S3StaticWebsiteOrigin(bucket),
198
+ viewerProtocolPolicy: cloudfont.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
199
+ },
200
+ priceClass: cloudfont.PriceClass.PRICE_CLASS_100,
201
+ });
202
+ })
203
+ : [];
204
+ this.leaseTable = new dynamodb.Table(this, "PreviewLeases", {
205
+ partitionKey: {
206
+ name: "slotId",
207
+ type: dynamodb.AttributeType.STRING,
208
+ },
209
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
210
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
211
+ timeToLiveAttribute: "ttlEpochSeconds",
212
+ });
213
+ this.leaseTable.addGlobalSecondaryIndex({
214
+ indexName: "RepoPrKeyIndex",
215
+ partitionKey: {
216
+ name: "repoPrKey",
217
+ type: dynamodb.AttributeType.STRING,
218
+ },
219
+ projectionType: dynamodb.ProjectionType.ALL,
220
+ });
221
+ const slotDefinitions = this.buckets.map((bucket, slotId) => ({
222
+ slotId,
223
+ bucketName: bucket.bucketName,
224
+ previewUrl: this.distributions[slotId]
225
+ ? `https://${this.distributions[slotId].distributionDomainName}`
226
+ : bucket.bucketWebsiteUrl,
227
+ }));
228
+ const leaseApiHandler = new lambda.Function(this, "PreviewLeaseApiHandler", {
229
+ runtime: lambda.Runtime.NODEJS_20_X,
230
+ handler: "index.handler",
231
+ timeout: cdk.Duration.seconds(15),
232
+ code: lambda.Code.fromAsset(path.join(__dirname, "..", "lambda")),
233
+ environment: {
234
+ TABLE_NAME: this.leaseTable.tableName,
235
+ SLOT_DEFINITIONS: JSON.stringify(slotDefinitions),
236
+ MAX_LEASE_MS: String(maxLeaseMs),
237
+ },
238
+ });
239
+ this.leaseTable.grantReadWriteData(leaseApiHandler);
240
+ this.api = new apigateway.RestApi(this, "PreviewLeaseApi", {
241
+ restApiName: `${cdk.Names.uniqueId(this)}-preview-lease-api`,
242
+ description: "API for claiming/releasing preview slots",
243
+ });
244
+ const claimResource = this.api.root.addResource("claim");
245
+ claimResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
246
+ const heartbeatResource = this.api.root.addResource("heartbeat");
247
+ heartbeatResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
248
+ const releaseResource = this.api.root.addResource("release");
249
+ releaseResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
250
+ this.claimEndpoint = `${this.api.url}claim`;
251
+ this.heartbeatEndpoint = `${this.api.url}heartbeat`;
252
+ this.releaseEndpoint = `${this.api.url}release`;
253
+ new cdk.CfnOutput(this, "preview-claim-endpoint", {
254
+ value: this.claimEndpoint,
255
+ description: "POST endpoint used to claim a preview slot",
256
+ });
257
+ new cdk.CfnOutput(this, "preview-heartbeat-endpoint", {
258
+ value: this.heartbeatEndpoint,
259
+ description: "POST endpoint used to refresh a preview slot lease",
260
+ });
261
+ new cdk.CfnOutput(this, "preview-release-endpoint", {
262
+ value: this.releaseEndpoint,
263
+ description: "POST endpoint used to release a preview slot",
264
+ });
265
+ }
266
+ grantDeploymentAccess(grantee) {
267
+ this.buckets.forEach((bucket) => bucket.grantReadWrite(grantee));
268
+ this.distributions.forEach((distribution) => {
269
+ grantee.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
270
+ actions: ["cloudfront:CreateInvalidation"],
271
+ resources: [distribution.distributionArn],
272
+ }));
273
+ });
274
+ }
275
+ }
276
+ exports.PreviewEnvironment = PreviewEnvironment;
277
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwyQ0FBdUM7QUFDdkMsdURBQXlDO0FBQ3pDLHNFQUF3RDtBQUN4RCw0RUFBOEQ7QUFDOUQsdUZBQXlFO0FBQ3pFLGlEQUFtQztBQUNuQyx5REFBMkM7QUFDM0MsaUVBQW1EO0FBQ25ELG1FQUFxRDtBQUNyRCwrREFBaUQ7QUFDakQsdUVBQXlEO0FBQ3pELDJDQUE2QjtBQWtFN0IsTUFBYSxPQUFRLFNBQVEsc0JBQVM7SUFDcEIsTUFBTSxDQUFZO0lBQ2xCLFlBQVksQ0FBeUI7SUFDckMsa0JBQWtCLENBQXNCO0lBRXhELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBbUI7UUFDM0QsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLFVBQVUsRUFBRTtZQUNsRCxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxnQkFBZ0IsRUFBRSxJQUFJO1lBQ3RCLGFBQWEsRUFBRSxHQUFHLENBQUMsYUFBYSxDQUFDLE9BQU87WUFDeEMsaUJBQWlCLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLGVBQWU7WUFDdkQsYUFBYSxFQUFFLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyx5QkFBeUI7WUFDL0QsVUFBVSxFQUFFLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVO1NBQzNDLENBQUMsQ0FBQztRQUNILE1BQU0sR0FBRyxHQUFHLElBQUksU0FBUyxDQUFDLG9CQUFvQixDQUM1QyxJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxNQUFNLENBQzFCLENBQUM7UUFD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LENBQUMsYUFBYTtZQUN6QixXQUFXLEVBQUUsNENBQTRDO1NBQzFELENBQUMsQ0FBQztRQUNILElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsNEJBQTRCLEVBQUU7WUFDcEQsS0FBSyxFQUFFLElBQUksQ0FBQyxpQkFBaUI7WUFDN0IsV0FBVyxFQUFFLG9EQUFvRDtTQUNsRSxDQUFDLENBQUM7UUFDSCxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLDBCQUEwQixFQUFFO1lBQ2xELEtBQUssRUFBRSxJQUFJLENBQUMsZUFBZTtZQUMzQixXQUFXLEVBQUUsOENBQThDO1NBQzVELENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTSxxQkFBcUIsQ0FBQyxPQUF1QjtRQUNsRCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQ2pFLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7WUFDMUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxvQkFBb0IsQ0FDekMsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDO2dCQUN0QixPQUFPLEVBQUUsQ0FBQywrQkFBK0IsQ0FBQztnQkFDMUMsU0FBUyxFQUFFLENBQUMsWUFBWSxDQUFDLGVBQWUsQ0FBQzthQUMxQyxDQUFDLENBQ0gsQ0FBQztRQUNKLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBcEtELGdEQW9LQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5pbXBvcnQgKiBhcyBzMyBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXMzXCI7XG5pbXBvcnQgKiBhcyBjbG91ZGZvbnQgZnJvbSBcImF3cy1jZGstbGliL2F3cy1jbG91ZGZyb250XCI7XG5pbXBvcnQgKiBhcyBvcmlnaW5zIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2xvdWRmcm9udC1vcmlnaW5zXCI7XG5pbXBvcnQgKiBhcyBjZXJ0aWZpY2F0ZW1hbmFnZXIgZnJvbSBcImF3cy1jZGstbGliL2F3cy1jZXJ0aWZpY2F0ZW1hbmFnZXJcIjtcbmltcG9ydCAqIGFzIGNkayBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCAqIGFzIGlhbSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWlhbVwiO1xuaW1wb3J0ICogYXMgcm91dGU1MyBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcbmltcG9ydCAqIGFzIGR5bmFtb2RiIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtZHluYW1vZGJcIjtcbmltcG9ydCAqIGFzIGxhbWJkYSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWxhbWJkYVwiO1xuaW1wb3J0ICogYXMgYXBpZ2F0ZXdheSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWFwaWdhdGV3YXlcIjtcbmltcG9ydCAqIGFzIHBhdGggZnJvbSBcInBhdGhcIjtcblxuZXhwb3J0IGludGVyZmFjZSBEb21haW5Db25maWcge1xuICAvKiogVGhlIHJvb3QgZG9tYWluIG5hbWUgKGUuZy4sIGV4YW1wbGUuY29tKS5cbiAgICogVGhlcmUgbXVzdCBiZSBhbiBhc3NvY2lhdGVkIGhvc3RlZCB6b25lIGluIFJvdXRlIDUzIGZvciB0aGlzIGRvbWFpbi5cbiAgICovXG4gIGRvbWFpbk5hbWU6IHN0cmluZztcbiAgLyoqIFRoZSBzdWJkb21haW4gbmFtZSAqL1xuICBzdWJkb21haW5OYW1lOiBzdHJpbmc7XG4gIC8qKiBUaGUgQVJOIG9mIHRoZSBTU0wgY2VydGlmaWNhdGUgdG8gdXNlIGZvciB0aGUgZG9tYWluLiAqL1xuICBjZXJ0aWZpY2F0ZUFybjogc3RyaW5nO1xuICAvKipcbiAgICogSWYgdHJ1ZSwgY3JlYXRlcyBhbiBhZGRpdGlvbmFsIFJvdXRlIDUzIHJlY29yZCBmb3IgdGhlIHJvb3QgZG9tYWluIHBvaW50aW5nIHRvIHRoZSBDbG91ZEZyb250IGRpc3RyaWJ1dGlvbi5cbiAgICogQGRlZmF1bHQgZmFsc2VcbiAgICovXG4gIGluY2x1ZGVSb290RG9tYWluPzogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBXZWJzaXRlUHJvcHMge1xuICAvKiogVGhlIG5hbWUgb2YgdGhlIFMzIGJ1Y2tldCB0aGF0IHdpbGwgaG9zdCB0aGUgd2Vic2l0ZSBjb250ZW50LiAqL1xuICBidWNrZXROYW1lOiBzdHJpbmc7XG5cbiAgLyoqIFRoZSBwYXRoIHRvIHRoZSBpbmRleCBkb2N1bWVudCB0aGF0IHdpbGwgYmUgc2VydmVkIGFzIHRoZSBkZWZhdWx0IHBhZ2UuICovXG4gIGluZGV4RmlsZTogc3RyaW5nO1xuXG4gIC8qKiBUaGUgcGF0aCB0byB0aGUgZXJyb3IgZG9jdW1lbnQgdGhhdCB3aWxsIGJlIHNlcnZlZCB3aGVuIGFuIGVycm9yIG9jY3Vycy4gKi9cbiAgZXJyb3JGaWxlOiBzdHJpbmc7XG5cbiAgLyoqIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gZm9yIGN1c3RvbSBkb21haW4gc2V0dXAuICovXG4gIGRvbWFpbkNvbmZpZz86IERvbWFpbkNvbmZpZztcblxuICAvKiogT3B0aW9uYWwgcGF0aCB0byBhIGN1c3RvbSA0MDQgcGFnZS4gSWYgbm90IHNwZWNpZmllZCwgdGhlIGVycm9yIGZpbGUgd2lsbCBiZSB1c2VkLiAqL1xuICBub3RGb3VuZFJlc3BvbnNlUGFnZVBhdGg/OiBzdHJpbmc7XG5cbiAgLyoqIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gZm9yIHB1bGwgcmVxdWVzdCBwcmV2aWV3IGVudmlyb25tZW50cy4gKi9cbiAgcHJldmlld0NvbmZpZz86IFByZXZpZXdDb25maWc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld0NvbmZpZyB7XG4gIC8qKiBQcmVmaXggdXNlZCB0byBuYW1lIHByZXZpZXcgYnVja2V0cy4gQnVja2V0cyBhcmUgY3JlYXRlZCBhcyBgJHtwcmVmaXh9LTBgLCBgJHtwcmVmaXh9LTFgLCAuLi4gKi9cbiAgYnVja2V0UHJlZml4OiBzdHJpbmc7XG5cbiAgLyoqIE51bWJlciBvZiBwcmV2aWV3IGJ1Y2tldHMgdG8gY3JlYXRlLlxuICAgKiBAZGVmYXVsdCAyXG4gICAqL1xuICBidWNrZXRDb3VudD86IG51bWJlcjtcblxuICAvKiogSWYgdHJ1ZSwgY3JlYXRlcyBvbmUgQ2xvdWRGcm9udCBkaXN0cmlidXRpb24gcGVyIHByZXZpZXcgYnVja2V0LlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICBjcmVhdGVEaXN0cmlidXRpb25zPzogYm9vbGVhbjtcblxuICAvKiogTWF4aW11bSBsZWFzZSBsaWZldGltZSBpbiBob3VycyBiZWZvcmUgYSBzbG90IGlzIGNvbnNpZGVyZWQgZXhwaXJlZC5cbiAgICogQGRlZmF1bHQgMjRcbiAgICovXG4gIG1heExlYXNlSG91cnM/OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld0Vudmlyb25tZW50UHJvcHMgZXh0ZW5kcyBQcmV2aWV3Q29uZmlnIHtcbiAgLyoqIEluZGV4IGRvY3VtZW50IGZvciBwcmV2aWV3IGJ1Y2tldHMuICovXG4gIGluZGV4RmlsZTogc3RyaW5nO1xuXG4gIC8qKiBFcnJvciBkb2N1bWVudCBmb3IgcHJldmlldyBidWNrZXRzLiAqL1xuICBlcnJvckZpbGU6IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIFdlYnNpdGUgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0OiBzMy5CdWNrZXQ7XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb246IGNsb3VkZm9udC5EaXN0cmlidXRpb247XG4gIHB1YmxpYyByZWFkb25seSBwcmV2aWV3RW52aXJvbm1lbnQ/OiBQcmV2aWV3RW52aXJvbm1lbnQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFdlYnNpdGVQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG4gICAgdGhpcy5idWNrZXQgPSBuZXcgczMuQnVja2V0KHRoaXMsIHByb3BzLmJ1Y2tldE5hbWUsIHtcbiAgICAgIHdlYnNpdGVJbmRleERvY3VtZW50OiBwcm9wcy5pbmRleEZpbGUsXG4gICAgICB3ZWJzaXRlRXJyb3JEb2N1bWVudDogcHJvcHMuZXJyb3JGaWxlLFxuICAgICAgcHVibGljUmVhZEFjY2VzczogdHJ1ZSxcbiAgICAgIHJlbW92YWxQb2xpY3k6IGNkay5SZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICBibG9ja1B1YmxpY0FjY2VzczogczMuQmxvY2tQdWJsaWNBY2Nlc3MuQkxPQ0tfQUNMU19PTkxZLFxuICAgICAgYWNjZXNzQ29udHJvbDogczMuQnVja2V0QWNjZXNzQ29udHJvbC5CVUNLRVRfT1dORVJfRlVMTF9DT05UUk9MLFxuICAgICAgZW5jcnlwdGlvbjogczMuQnVja2V0RW5jcnlwdGlvbi5TM19NQU5BR0VELFxuICAgIH0pO1xuICAgIGNvbnN0IG9haSA9IG5ldyBjbG91ZGZvbnQuT3JpZ2luQWNjZXNzSWRlbnRpdHkoXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuYnVja2V0TmFtZX0tT0FJYCxcbiAgICApO1xuICAgIHRoaXMuYnVja2V0LmFkZFRvUmVzb3VyY2VQb2xpY3koXG4gICAgICBuZXcgaWFtLlBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgIGFjdGlvbnM6IFtcInMzOkdldE9iamVjdFwiXSxcbiAgICAgICAgcmVzb3VyY2VzOiBbdGhpcy5idWNrZXQuYXJuRm9yT2JqZWN0cyhcIipcIildLFxuICAgICAgICBwcmluY2lwYWxzOiBbXG4gICAgICAgICAgbmV3IGlhbS5DYW5vbmljYWxVc2VyUHJpbmNpcGFsKFxuICAgICAgICAgICAgb2FpLmNsb3VkRnJvbnRPcmlnaW5BY2Nlc3NJZGVudGl0eVMzQ2Fub25pY2FsVXNlcklkLFxuICAgICAgICAgICksXG4gICAgICAgIF0sXG4gICAgICB9KSxcbiAgICApO1xuICAgIGNvbnN0IGRvbWFpbk5hbWVzOiBzdHJpbmdbXSA9IFtdO1xuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIGRvbWFpbk5hbWVzLnB1c2godGhpcy5fZ2V0RnVsbERvbWFpbk5hbWUocHJvcHMuZG9tYWluQ29uZmlnKSk7XG4gICAgICBpZiAoXG4gICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5pbmNsdWRlUm9vdERvbWFpbiAmJlxuICAgICAgICBwcm9wcy5kb21haW5Db25maWcuc3ViZG9tYWluTmFtZVxuICAgICAgKSB7XG4gICAgICAgIGRvbWFpbk5hbWVzLnB1c2gocHJvcHMuZG9tYWluQ29uZmlnLmRvbWFpbk5hbWUpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHRoaXMuZGlzdHJpYnV0aW9uID0gbmV3IGNsb3VkZm9udC5EaXN0cmlidXRpb24oXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuYnVja2V0TmFtZX0tZGlzdHJpYnV0aW9uYCxcbiAgICAgIHtcbiAgICAgICAgZGVmYXVsdEJlaGF2aW9yOiB7XG4gICAgICAgICAgb3JpZ2luOiBuZXcgb3JpZ2lucy5TM1N0YXRpY1dlYnNpdGVPcmlnaW4odGhpcy5idWNrZXQpLFxuICAgICAgICAgIHZpZXdlclByb3RvY29sUG9saWN5OlxuICAgICAgICAgICAgY2xvdWRmb250LlZpZXdlclByb3RvY29sUG9saWN5LlJFRElSRUNUX1RPX0hUVFBTLFxuICAgICAgICB9LFxuICAgICAgICBlcnJvclJlc3BvbnNlczogW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIGh0dHBTdGF0dXM6IDQwNCxcbiAgICAgICAgICAgIHJlc3BvbnNlSHR0cFN0YXR1czogNDA0LFxuICAgICAgICAgICAgcmVzcG9uc2VQYWdlUGF0aDogcHJvcHMubm90Rm91bmRSZXNwb25zZVBhZ2VQYXRoIHx8IGAvNDA0Lmh0bWxgLFxuICAgICAgICAgICAgdHRsOiBjZGsuRHVyYXRpb24ubWludXRlcygzMCksXG4gICAgICAgICAgfSxcbiAgICAgICAgXSxcbiAgICAgICAgcHJpY2VDbGFzczogY2xvdWRmb250LlByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgICAuLi4ocHJvcHMuZG9tYWluQ29uZmlnXG4gICAgICAgICAgPyB7XG4gICAgICAgICAgICAgIGRvbWFpbk5hbWVzOiBkb21haW5OYW1lcyxcbiAgICAgICAgICAgICAgY2VydGlmaWNhdGU6IHRoaXMuX2dldENlcnRpZmljYXRlKFxuICAgICAgICAgICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5jZXJ0aWZpY2F0ZUFybixcbiAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgIH1cbiAgICAgICAgICA6IHt9KSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIGNvbnN0IGhvc3RlZFpvbmUgPSByb3V0ZTUzLkhvc3RlZFpvbmUuZnJvbUxvb2t1cCh0aGlzLCBcIkhvc3RlZFpvbmVcIiwge1xuICAgICAgICBkb21haW5OYW1lOiBwcm9wcy5kb21haW5Db25maWcuZG9tYWluTmFtZSxcbiAgICAgIH0pO1xuICAgICAgY29uc3QgZG9tYWluQVJlY29yZCA9IG5ldyByb3V0ZTUzLkFSZWNvcmQodGhpcywgXCJEb21haW5BUmVjb3JkXCIsIHtcbiAgICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgICAgcmVjb3JkTmFtZTogdGhpcy5fZ2V0RnVsbERvbWFpbk5hbWUocHJvcHMuZG9tYWluQ29uZmlnKSxcbiAgICAgICAgdGFyZ2V0OiBjZGsuYXdzX3JvdXRlNTMuUmVjb3JkVGFyZ2V0LmZyb21BbGlhcyhcbiAgICAgICAgICBuZXcgY2RrLmF3c19yb3V0ZTUzX3RhcmdldHMuQ2xvdWRGcm9udFRhcmdldCh0aGlzLmRpc3RyaWJ1dGlvbiksXG4gICAgICAgICksXG4gICAgICB9KTtcbiAgICAgIGRvbWFpbkFSZWNvcmQubm9kZS5hZGREZXBlbmRlbmN5KHRoaXMuZGlzdHJpYnV0aW9uKTtcblxuICAgICAgaWYgKFxuICAgICAgICBwcm9wcy5kb21haW5Db25maWcuaW5jbHVkZVJvb3REb21haW4gJiZcbiAgICAgICAgcHJvcHMuZG9tYWluQ29uZmlnLnN1YmRvbWFpbk5hbWVcbiAgICAgICkge1xuICAgICAgICBuZXcgcm91dGU1My5BUmVjb3JkKHRoaXMsIFwiUm9vdERvbWFpbkFSZWNvcmRcIiwge1xuICAgICAgICAgIHpvbmU6IGhvc3RlZFpvbmUsXG4gICAgICAgICAgcmVjb3JkTmFtZTogcHJvcHMuZG9tYWluQ29uZmlnLmRvbWFpbk5hbWUsXG4gICAgICAgICAgdGFyZ2V0OiBjZGsuYXdzX3JvdXRlNTMuUmVjb3JkVGFyZ2V0LmZyb21BbGlhcyhcbiAgICAgICAgICAgIG5ldyBjZGsuYXdzX3JvdXRlNTNfdGFyZ2V0cy5DbG91ZEZyb250VGFyZ2V0KHRoaXMuZGlzdHJpYnV0aW9uKSxcbiAgICAgICAgICApLFxuICAgICAgICB9KS5ub2RlLmFkZERlcGVuZGVuY3kodGhpcy5kaXN0cmlidXRpb24pO1xuICAgICAgfVxuICAgIH1cblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwiY2xvdWRmcm9udC13ZWJzaXRlLXVybFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZSxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIkNsb3VkRnJvbnQgRGlzdHJpYnV0aW9uIERvbWFpbiBOYW1lXCIsXG4gICAgfSk7XG5cbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcInMzLXdlYnNpdGUtdXJsXCIsIHtcbiAgICAgIHZhbHVlOiB0aGlzLmJ1Y2tldC5idWNrZXRXZWJzaXRlVXJsLFxuICAgICAgZGVzY3JpcHRpb246IFwiUzMgQnVja2V0IFdlYnNpdGUgVVJMXCIsXG4gICAgfSk7XG5cbiAgICBpZiAocHJvcHMuZG9tYWluQ29uZmlnKSB7XG4gICAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcIndlYnNpdGUtdXJsXCIsIHtcbiAgICAgICAgdmFsdWU6IHRoaXMuZGlzdHJpYnV0aW9uLmRvbWFpbk5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBcIldlYnNpdGUgVVJMXCIsXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBpZiAocHJvcHMucHJldmlld0NvbmZpZykge1xuICAgICAgdGhpcy5wcmV2aWV3RW52aXJvbm1lbnQgPSBuZXcgUHJldmlld0Vudmlyb25tZW50KFxuICAgICAgICB0aGlzLFxuICAgICAgICBcIlByZXZpZXdFbnZpcm9ubWVudFwiLFxuICAgICAgICB7XG4gICAgICAgICAgLi4ucHJvcHMucHJldmlld0NvbmZpZyxcbiAgICAgICAgICBpbmRleEZpbGU6IHByb3BzLmluZGV4RmlsZSxcbiAgICAgICAgICBlcnJvckZpbGU6IHByb3BzLmVycm9yRmlsZSxcbiAgICAgICAgfSxcbiAgICAgICk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBfZ2V0RnVsbERvbWFpbk5hbWUoZG9tYWluQ29uZmlnOiBEb21haW5Db25maWcpOiBzdHJpbmcge1xuICAgIHJldHVybiBkb21haW5Db25maWcuc3ViZG9tYWluTmFtZVxuICAgICAgPyBgJHtkb21haW5Db25maWcuc3ViZG9tYWluTmFtZX0uJHtkb21haW5Db25maWcuZG9tYWluTmFtZX1gXG4gICAgICA6IGRvbWFpbkNvbmZpZy5kb21haW5OYW1lO1xuICB9XG5cbiAgcHJpdmF0ZSBfZ2V0Q2VydGlmaWNhdGUoYXJuOiBzdHJpbmcpOiBjZXJ0aWZpY2F0ZW1hbmFnZXIuSUNlcnRpZmljYXRlIHtcbiAgICByZXR1cm4gY2VydGlmaWNhdGVtYW5hZ2VyLkNlcnRpZmljYXRlLmZyb21DZXJ0aWZpY2F0ZUFybihcbiAgICAgIHRoaXMsXG4gICAgICBgd2Vic2l0ZS1jZXJ0YCxcbiAgICAgIGFybixcbiAgICApO1xuICB9XG59XG5cbmV4cG9ydCBjbGFzcyBQcmV2aWV3RW52aXJvbm1lbnQgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0czogczMuQnVja2V0W107XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb25zOiBjbG91ZGZvbnQuRGlzdHJpYnV0aW9uW107XG4gIHB1YmxpYyByZWFkb25seSBsZWFzZVRhYmxlOiBkeW5hbW9kYi5UYWJsZTtcbiAgcHVibGljIHJlYWRvbmx5IGFwaTogYXBpZ2F0ZXdheS5SZXN0QXBpO1xuICBwdWJsaWMgcmVhZG9ubHkgY2xhaW1FbmRwb2ludDogc3RyaW5nO1xuICBwdWJsaWMgcmVhZG9ubHkgaGVhcnRiZWF0RW5kcG9pbnQ6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IHJlbGVhc2VFbmRwb2ludDogc3RyaW5nO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBQcmV2aWV3RW52aXJvbm1lbnRQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBjb25zdCBidWNrZXRDb3VudCA9IHByb3BzLmJ1Y2tldENvdW50ID8/IDI7XG4gICAgaWYgKGJ1Y2tldENvdW50IDwgMSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiYnVja2V0Q291bnQgbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMVwiKTtcbiAgICB9XG5cbiAgICBjb25zdCBpbmRleEZpbGUgPSBwcm9wcy5pbmRleEZpbGU7XG4gICAgY29uc3QgZXJyb3JGaWxlID0gcHJvcHMuZXJyb3JGaWxlO1xuICAgIGNvbnN0IGNyZWF0ZURpc3RyaWJ1dGlvbnMgPSBwcm9wcy5jcmVhdGVEaXN0cmlidXRpb25zID8/IHRydWU7XG4gICAgY29uc3QgbWF4TGVhc2VIb3VycyA9IHByb3BzLm1heExlYXNlSG91cnMgPz8gMjQ7XG4gICAgY29uc3QgbWF4TGVhc2VNcyA9IGNkay5EdXJhdGlvbi5ob3VycyhtYXhMZWFzZUhvdXJzKS50b01pbGxpc2Vjb25kcygpO1xuXG4gICAgdGhpcy5idWNrZXRzID0gQXJyYXkuZnJvbSh7IGxlbmd0aDogYnVja2V0Q291bnQgfSwgKF8sIHNsb3RJZCkgPT4ge1xuICAgICAgY29uc3QgYnVja2V0TmFtZSA9IGAke3Byb3BzLmJ1Y2tldFByZWZpeH0tJHtzbG90SWR9YDtcbiAgICAgIHJldHVybiBuZXcgczMuQnVja2V0KHRoaXMsIGBQcmV2aWV3QnVja2V0JHtzbG90SWR9YCwge1xuICAgICAgICBidWNrZXROYW1lLFxuICAgICAgICB3ZWJzaXRlSW5kZXhEb2N1bWVudDogaW5kZXhGaWxlLFxuICAgICAgICB3ZWJzaXRlRXJyb3JEb2N1bWVudDogZXJyb3JGaWxlLFxuICAgICAgICBwdWJsaWNSZWFkQWNjZXNzOiB0cnVlLFxuICAgICAgICByZW1vdmFsUG9saWN5OiBjZGsuUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgICBhdXRvRGVsZXRlT2JqZWN0czogdHJ1ZSxcbiAgICAgICAgYmxvY2tQdWJsaWNBY2Nlc3M6IHMzLkJsb2NrUHVibGljQWNjZXNzLkJMT0NLX0FDTFNfT05MWSxcbiAgICAgICAgYWNjZXNzQ29udHJvbDogczMuQnVja2V0QWNjZXNzQ29udHJvbC5CVUNLRVRfT1dORVJfRlVMTF9DT05UUk9MLFxuICAgICAgICBlbmNyeXB0aW9uOiBzMy5CdWNrZXRFbmNyeXB0aW9uLlMzX01BTkFHRUQsXG4gICAgICB9KTtcbiAgICB9KTtcblxuICAgIHRoaXMuZGlzdHJpYnV0aW9ucyA9IGNyZWF0ZURpc3RyaWJ1dGlvbnNcbiAgICAgID8gdGhpcy5idWNrZXRzLm1hcCgoYnVja2V0LCBzbG90SWQpID0+IHtcbiAgICAgICAgICBjb25zdCBvYWkgPSBuZXcgY2xvdWRmb250Lk9yaWdpbkFjY2Vzc0lkZW50aXR5KFxuICAgICAgICAgICAgdGhpcyxcbiAgICAgICAgICAgIGBQcmV2aWV3T0FJJHtzbG90SWR9YCxcbiAgICAgICAgICApO1xuICAgICAgICAgIGJ1Y2tldC5hZGRUb1Jlc291cmNlUG9saWN5KFxuICAgICAgICAgICAgbmV3IGlhbS5Qb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgICAgICBhY3Rpb25zOiBbXCJzMzpHZXRPYmplY3RcIl0sXG4gICAgICAgICAgICAgIHJlc291cmNlczogW2J1Y2tldC5hcm5Gb3JPYmplY3RzKFwiKlwiKV0sXG4gICAgICAgICAgICAgIHByaW5jaXBhbHM6IFtcbiAgICAgICAgICAgICAgICBuZXcgaWFtLkNhbm9uaWNhbFVzZXJQcmluY2lwYWwoXG4gICAgICAgICAgICAgICAgICBvYWkuY2xvdWRGcm9udE9yaWdpbkFjY2Vzc0lkZW50aXR5UzNDYW5vbmljYWxVc2VySWQsXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgIH0pLFxuICAgICAgICAgICk7XG4gICAgICAgICAgcmV0dXJuIG5ldyBjbG91ZGZvbnQuRGlzdHJpYnV0aW9uKFxuICAgICAgICAgICAgdGhpcyxcbiAgICAgICAgICAgIGBQcmV2aWV3RGlzdHJpYnV0aW9uJHtzbG90SWR9YCxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgZGVmYXVsdEJlaGF2aW9yOiB7XG4gICAgICAgICAgICAgICAgb3JpZ2luOiBuZXcgb3JpZ2lucy5TM1N0YXRpY1dlYnNpdGVPcmlnaW4oYnVja2V0KSxcbiAgICAgICAgICAgICAgICB2aWV3ZXJQcm90b2NvbFBvbGljeTpcbiAgICAgICAgICAgICAgICAgIGNsb3VkZm9udC5WaWV3ZXJQcm90b2NvbFBvbGljeS5SRURJUkVDVF9UT19IVFRQUyxcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgcHJpY2VDbGFzczogY2xvdWRmb250LlByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICApO1xuICAgICAgICB9KVxuICAgICAgOiBbXTtcblxuICAgIHRoaXMubGVhc2VUYWJsZSA9IG5ldyBkeW5hbW9kYi5UYWJsZSh0aGlzLCBcIlByZXZpZXdMZWFzZXNcIiwge1xuICAgICAgcGFydGl0aW9uS2V5OiB7XG4gICAgICAgIG5hbWU6IFwic2xvdElkXCIsXG4gICAgICAgIHR5cGU6IGR5bmFtb2RiLkF0dHJpYnV0ZVR5cGUuU1RSSU5HLFxuICAgICAgfSxcbiAgICAgIGJpbGxpbmdNb2RlOiBkeW5hbW9kYi5CaWxsaW5nTW9kZS5QQVlfUEVSX1JFUVVFU1QsXG4gICAgICByZW1vdmFsUG9saWN5OiBjZGsuUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgdGltZVRvTGl2ZUF0dHJpYnV0ZTogXCJ0dGxFcG9jaFNlY29uZHNcIixcbiAgICB9KTtcbiAgICB0aGlzLmxlYXNlVGFibGUuYWRkR2xvYmFsU2Vjb25kYXJ5SW5kZXgoe1xuICAgICAgaW5kZXhOYW1lOiBcIlJlcG9QcktleUluZGV4XCIsXG4gICAgICBwYXJ0aXRpb25LZXk6IHtcbiAgICAgICAgbmFtZTogXCJyZXBvUHJLZXlcIixcbiAgICAgICAgdHlwZTogZHluYW1vZGIuQXR0cmlidXRlVHlwZS5TVFJJTkcsXG4gICAgICB9LFxuICAgICAgcHJvamVjdGlvblR5cGU6IGR5bmFtb2RiLlByb2plY3Rpb25UeXBlLkFMTCxcbiAgICB9KTtcblxuICAgIGNvbnN0IHNsb3REZWZpbml0aW9ucyA9IHRoaXMuYnVja2V0cy5tYXAoKGJ1Y2tldCwgc2xvdElkKSA9PiAoe1xuICAgICAgc2xvdElkLFxuICAgICAgYnVja2V0TmFtZTogYnVja2V0LmJ1Y2tldE5hbWUsXG4gICAgICBwcmV2aWV3VXJsOiB0aGlzLmRpc3RyaWJ1dGlvbnNbc2xvdElkXVxuICAgICAgICA/IGBodHRwczovLyR7dGhpcy5kaXN0cmlidXRpb25zW3Nsb3RJZF0uZGlzdHJpYnV0aW9uRG9tYWluTmFtZX1gXG4gICAgICAgIDogYnVja2V0LmJ1Y2tldFdlYnNpdGVVcmwsXG4gICAgfSkpO1xuXG4gICAgY29uc3QgbGVhc2VBcGlIYW5kbGVyID0gbmV3IGxhbWJkYS5GdW5jdGlvbihcbiAgICAgIHRoaXMsXG4gICAgICBcIlByZXZpZXdMZWFzZUFwaUhhbmRsZXJcIixcbiAgICAgIHtcbiAgICAgICAgcnVudGltZTogbGFtYmRhLlJ1bnRpbWUuTk9ERUpTXzIwX1gsXG4gICAgICAgIGhhbmRsZXI6IFwiaW5kZXguaGFuZGxlclwiLFxuICAgICAgICB0aW1lb3V0OiBjZGsuRHVyYXRpb24uc2Vjb25kcygxNSksXG4gICAgICAgIGNvZGU6IGxhbWJkYS5Db2RlLmZyb21Bc3NldChwYXRoLmpvaW4oX19kaXJuYW1lLCBcIi4uXCIsIFwibGFtYmRhXCIpKSxcbiAgICAgICAgZW52aXJvbm1lbnQ6IHtcbiAgICAgICAgICBUQUJMRV9OQU1FOiB0aGlzLmxlYXNlVGFibGUudGFibGVOYW1lLFxuICAgICAgICAgIFNMT1RfREVGSU5JVElPTlM6IEpTT04uc3RyaW5naWZ5KHNsb3REZWZpbml0aW9ucyksXG4gICAgICAgICAgTUFYX0xFQVNFX01TOiBTdHJpbmcobWF4TGVhc2VNcyksXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICk7XG5cbiAgICB0aGlzLmxlYXNlVGFibGUuZ3JhbnRSZWFkV3JpdGVEYXRhKGxlYXNlQXBpSGFuZGxlcik7XG5cbiAgICB0aGlzLmFwaSA9IG5ldyBhcGlnYXRld2F5LlJlc3RBcGkodGhpcywgXCJQcmV2aWV3TGVhc2VBcGlcIiwge1xuICAgICAgcmVzdEFwaU5hbWU6IGAke2Nkay5OYW1lcy51bmlxdWVJZCh0aGlzKX0tcHJldmlldy1sZWFzZS1hcGlgLFxuICAgICAgZGVzY3JpcHRpb246IFwiQVBJIGZvciBjbGFpbWluZy9yZWxlYXNpbmcgcHJldmlldyBzbG90c1wiLFxuICAgIH0pO1xuXG4gICAgY29uc3QgY2xhaW1SZXNvdXJjZSA9IHRoaXMuYXBpLnJvb3QuYWRkUmVzb3VyY2UoXCJjbGFpbVwiKTtcbiAgICBjbGFpbVJlc291cmNlLmFkZE1ldGhvZChcbiAgICAgIFwiUE9TVFwiLFxuICAgICAgbmV3IGFwaWdhdGV3YXkuTGFtYmRhSW50ZWdyYXRpb24obGVhc2VBcGlIYW5kbGVyKSxcbiAgICApO1xuICAgIGNvbnN0IGhlYXJ0YmVhdFJlc291cmNlID0gdGhpcy5hcGkucm9vdC5hZGRSZXNvdXJjZShcImhlYXJ0YmVhdFwiKTtcbiAgICBoZWFydGJlYXRSZXNvdXJjZS5hZGRNZXRob2QoXG4gICAgICBcIlBPU1RcIixcbiAgICAgIG5ldyBhcGlnYXRld2F5LkxhbWJkYUludGVncmF0aW9uKGxlYXNlQXBpSGFuZGxlciksXG4gICAgKTtcbiAgICBjb25zdCByZWxlYXNlUmVzb3VyY2UgPSB0aGlzLmFwaS5yb290LmFkZFJlc291cmNlKFwicmVsZWFzZVwiKTtcbiAgICByZWxlYXNlUmVzb3VyY2UuYWRkTWV0aG9kKFxuICAgICAgXCJQT1NUXCIsXG4gICAgICBuZXcgYXBpZ2F0ZXdheS5MYW1iZGFJbnRlZ3JhdGlvbihsZWFzZUFwaUhhbmRsZXIpLFxuICAgICk7XG5cbiAgICB0aGlzLmNsYWltRW5kcG9pbnQgPSBgJHt0aGlzLmFwaS51cmx9Y2xhaW1gO1xuICAgIHRoaXMuaGVhcnRiZWF0RW5kcG9pbnQgPSBgJHt0aGlzLmFwaS51cmx9aGVhcnRiZWF0YDtcbiAgICB0aGlzLnJlbGVhc2VFbmRwb2ludCA9IGAke3RoaXMuYXBpLnVybH1yZWxlYXNlYDtcblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwicHJldmlldy1jbGFpbS1lbmRwb2ludFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5jbGFpbUVuZHBvaW50LFxuICAgICAgZGVzY3JpcHRpb246IFwiUE9TVCBlbmRwb2ludCB1c2VkIHRvIGNsYWltIGEgcHJldmlldyBzbG90XCIsXG4gICAgfSk7XG4gICAgbmV3IGNkay5DZm5PdXRwdXQodGhpcywgXCJwcmV2aWV3LWhlYXJ0YmVhdC1lbmRwb2ludFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5oZWFydGJlYXRFbmRwb2ludCxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIlBPU1QgZW5kcG9pbnQgdXNlZCB0byByZWZyZXNoIGEgcHJldmlldyBzbG90IGxlYXNlXCIsXG4gICAgfSk7XG4gICAgbmV3IGNkay5DZm5PdXRwdXQodGhpcywgXCJwcmV2aWV3LXJlbGVhc2UtZW5kcG9pbnRcIiwge1xuICAgICAgdmFsdWU6IHRoaXMucmVsZWFzZUVuZHBvaW50LFxuICAgICAgZGVzY3JpcHRpb246IFwiUE9TVCBlbmRwb2ludCB1c2VkIHRvIHJlbGVhc2UgYSBwcmV2aWV3IHNsb3RcIixcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBncmFudERlcGxveW1lbnRBY2Nlc3MoZ3JhbnRlZTogaWFtLklHcmFudGFibGUpOiB2b2lkIHtcbiAgICB0aGlzLmJ1Y2tldHMuZm9yRWFjaCgoYnVja2V0KSA9PiBidWNrZXQuZ3JhbnRSZWFkV3JpdGUoZ3JhbnRlZSkpO1xuICAgIHRoaXMuZGlzdHJpYnV0aW9ucy5mb3JFYWNoKChkaXN0cmlidXRpb24pID0+IHtcbiAgICAgIGdyYW50ZWUuZ3JhbnRQcmluY2lwYWwuYWRkVG9QcmluY2lwYWxQb2xpY3koXG4gICAgICAgIG5ldyBpYW0uUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbXCJjbG91ZGZyb250OkNyZWF0ZUludmFsaWRhdGlvblwiXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFtkaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uQXJuXSxcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH0pO1xuICB9XG59XG4iXX0=
package/lib/index.ts CHANGED
@@ -6,6 +6,10 @@ import * as certificatemanager from "aws-cdk-lib/aws-certificatemanager";
6
6
  import * as cdk from "aws-cdk-lib";
7
7
  import * as iam from "aws-cdk-lib/aws-iam";
8
8
  import * as route53 from "aws-cdk-lib/aws-route53";
9
+ import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
10
+ import * as lambda from "aws-cdk-lib/aws-lambda";
11
+ import * as apigateway from "aws-cdk-lib/aws-apigateway";
12
+ import * as path from "path";
9
13
 
10
14
  export interface DomainConfig {
11
15
  /** The root domain name (e.g., example.com).
@@ -16,6 +20,11 @@ export interface DomainConfig {
16
20
  subdomainName: string;
17
21
  /** The ARN of the SSL certificate to use for the domain. */
18
22
  certificateArn: string;
23
+ /**
24
+ * If true, creates an additional Route 53 record for the root domain pointing to the CloudFront distribution.
25
+ * @default false
26
+ */
27
+ includeRootDomain?: boolean;
19
28
  }
20
29
 
21
30
  export interface WebsiteProps {
@@ -33,11 +42,43 @@ export interface WebsiteProps {
33
42
 
34
43
  /** Optional path to a custom 404 page. If not specified, the error file will be used. */
35
44
  notFoundResponsePagePath?: string;
45
+
46
+ /** Optional configuration for pull request preview environments. */
47
+ previewConfig?: PreviewConfig;
48
+ }
49
+
50
+ export interface PreviewConfig {
51
+ /** Prefix used to name preview buckets. Buckets are created as `${prefix}-0`, `${prefix}-1`, ... */
52
+ bucketPrefix: string;
53
+
54
+ /** Number of preview buckets to create.
55
+ * @default 2
56
+ */
57
+ bucketCount?: number;
58
+
59
+ /** If true, creates one CloudFront distribution per preview bucket.
60
+ * @default true
61
+ */
62
+ createDistributions?: boolean;
63
+
64
+ /** Maximum lease lifetime in hours before a slot is considered expired.
65
+ * @default 24
66
+ */
67
+ maxLeaseHours?: number;
68
+ }
69
+
70
+ export interface PreviewEnvironmentProps extends PreviewConfig {
71
+ /** Index document for preview buckets. */
72
+ indexFile: string;
73
+
74
+ /** Error document for preview buckets. */
75
+ errorFile: string;
36
76
  }
37
77
 
38
78
  export class Website extends Construct {
39
79
  public readonly bucket: s3.Bucket;
40
80
  public readonly distribution: cloudfont.Distribution;
81
+ public readonly previewEnvironment?: PreviewEnvironment;
41
82
 
42
83
  constructor(scope: Construct, id: string, props: WebsiteProps) {
43
84
  super(scope, id);
@@ -52,7 +93,7 @@ export class Website extends Construct {
52
93
  });
53
94
  const oai = new cloudfont.OriginAccessIdentity(
54
95
  this,
55
- `${props.bucketName}-OAI`
96
+ `${props.bucketName}-OAI`,
56
97
  );
57
98
  this.bucket.addToResourcePolicy(
58
99
  new iam.PolicyStatement({
@@ -60,11 +101,21 @@ export class Website extends Construct {
60
101
  resources: [this.bucket.arnForObjects("*")],
61
102
  principals: [
62
103
  new iam.CanonicalUserPrincipal(
63
- oai.cloudFrontOriginAccessIdentityS3CanonicalUserId
104
+ oai.cloudFrontOriginAccessIdentityS3CanonicalUserId,
64
105
  ),
65
106
  ],
66
- })
107
+ }),
67
108
  );
109
+ const domainNames: string[] = [];
110
+ if (props.domainConfig) {
111
+ domainNames.push(this._getFullDomainName(props.domainConfig));
112
+ if (
113
+ props.domainConfig.includeRootDomain &&
114
+ props.domainConfig.subdomainName
115
+ ) {
116
+ domainNames.push(props.domainConfig.domainName);
117
+ }
118
+ }
68
119
 
69
120
  this.distribution = new cloudfont.Distribution(
70
121
  this,
@@ -86,13 +137,13 @@ export class Website extends Construct {
86
137
  priceClass: cloudfont.PriceClass.PRICE_CLASS_100,
87
138
  ...(props.domainConfig
88
139
  ? {
89
- domainNames: [this._getFullDomainName(props.domainConfig)],
140
+ domainNames: domainNames,
90
141
  certificate: this._getCertificate(
91
- props.domainConfig.certificateArn
142
+ props.domainConfig.certificateArn,
92
143
  ),
93
144
  }
94
145
  : {}),
95
- }
146
+ },
96
147
  );
97
148
 
98
149
  if (props.domainConfig) {
@@ -103,10 +154,23 @@ export class Website extends Construct {
103
154
  zone: hostedZone,
104
155
  recordName: this._getFullDomainName(props.domainConfig),
105
156
  target: cdk.aws_route53.RecordTarget.fromAlias(
106
- new cdk.aws_route53_targets.CloudFrontTarget(this.distribution)
157
+ new cdk.aws_route53_targets.CloudFrontTarget(this.distribution),
107
158
  ),
108
159
  });
109
160
  domainARecord.node.addDependency(this.distribution);
161
+
162
+ if (
163
+ props.domainConfig.includeRootDomain &&
164
+ props.domainConfig.subdomainName
165
+ ) {
166
+ new route53.ARecord(this, "RootDomainARecord", {
167
+ zone: hostedZone,
168
+ recordName: props.domainConfig.domainName,
169
+ target: cdk.aws_route53.RecordTarget.fromAlias(
170
+ new cdk.aws_route53_targets.CloudFrontTarget(this.distribution),
171
+ ),
172
+ }).node.addDependency(this.distribution);
173
+ }
110
174
  }
111
175
 
112
176
  new cdk.CfnOutput(this, "cloudfront-website-url", {
@@ -125,6 +189,18 @@ export class Website extends Construct {
125
189
  description: "Website URL",
126
190
  });
127
191
  }
192
+
193
+ if (props.previewConfig) {
194
+ this.previewEnvironment = new PreviewEnvironment(
195
+ this,
196
+ "PreviewEnvironment",
197
+ {
198
+ ...props.previewConfig,
199
+ indexFile: props.indexFile,
200
+ errorFile: props.errorFile,
201
+ },
202
+ );
203
+ }
128
204
  }
129
205
 
130
206
  private _getFullDomainName(domainConfig: DomainConfig): string {
@@ -137,7 +213,173 @@ export class Website extends Construct {
137
213
  return certificatemanager.Certificate.fromCertificateArn(
138
214
  this,
139
215
  `website-cert`,
140
- arn
216
+ arn,
217
+ );
218
+ }
219
+ }
220
+
221
+ export class PreviewEnvironment extends Construct {
222
+ public readonly buckets: s3.Bucket[];
223
+ public readonly distributions: cloudfont.Distribution[];
224
+ public readonly leaseTable: dynamodb.Table;
225
+ public readonly api: apigateway.RestApi;
226
+ public readonly claimEndpoint: string;
227
+ public readonly heartbeatEndpoint: string;
228
+ public readonly releaseEndpoint: string;
229
+
230
+ constructor(scope: Construct, id: string, props: PreviewEnvironmentProps) {
231
+ super(scope, id);
232
+
233
+ const bucketCount = props.bucketCount ?? 2;
234
+ if (bucketCount < 1) {
235
+ throw new Error("bucketCount must be greater than or equal to 1");
236
+ }
237
+
238
+ const indexFile = props.indexFile;
239
+ const errorFile = props.errorFile;
240
+ const createDistributions = props.createDistributions ?? true;
241
+ const maxLeaseHours = props.maxLeaseHours ?? 24;
242
+ const maxLeaseMs = cdk.Duration.hours(maxLeaseHours).toMilliseconds();
243
+
244
+ this.buckets = Array.from({ length: bucketCount }, (_, slotId) => {
245
+ const bucketName = `${props.bucketPrefix}-${slotId}`;
246
+ return new s3.Bucket(this, `PreviewBucket${slotId}`, {
247
+ bucketName,
248
+ websiteIndexDocument: indexFile,
249
+ websiteErrorDocument: errorFile,
250
+ publicReadAccess: true,
251
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
252
+ autoDeleteObjects: true,
253
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS_ONLY,
254
+ accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
255
+ encryption: s3.BucketEncryption.S3_MANAGED,
256
+ });
257
+ });
258
+
259
+ this.distributions = createDistributions
260
+ ? this.buckets.map((bucket, slotId) => {
261
+ const oai = new cloudfont.OriginAccessIdentity(
262
+ this,
263
+ `PreviewOAI${slotId}`,
264
+ );
265
+ bucket.addToResourcePolicy(
266
+ new iam.PolicyStatement({
267
+ actions: ["s3:GetObject"],
268
+ resources: [bucket.arnForObjects("*")],
269
+ principals: [
270
+ new iam.CanonicalUserPrincipal(
271
+ oai.cloudFrontOriginAccessIdentityS3CanonicalUserId,
272
+ ),
273
+ ],
274
+ }),
275
+ );
276
+ return new cloudfont.Distribution(
277
+ this,
278
+ `PreviewDistribution${slotId}`,
279
+ {
280
+ defaultBehavior: {
281
+ origin: new origins.S3StaticWebsiteOrigin(bucket),
282
+ viewerProtocolPolicy:
283
+ cloudfont.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
284
+ },
285
+ priceClass: cloudfont.PriceClass.PRICE_CLASS_100,
286
+ },
287
+ );
288
+ })
289
+ : [];
290
+
291
+ this.leaseTable = new dynamodb.Table(this, "PreviewLeases", {
292
+ partitionKey: {
293
+ name: "slotId",
294
+ type: dynamodb.AttributeType.STRING,
295
+ },
296
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
297
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
298
+ timeToLiveAttribute: "ttlEpochSeconds",
299
+ });
300
+ this.leaseTable.addGlobalSecondaryIndex({
301
+ indexName: "RepoPrKeyIndex",
302
+ partitionKey: {
303
+ name: "repoPrKey",
304
+ type: dynamodb.AttributeType.STRING,
305
+ },
306
+ projectionType: dynamodb.ProjectionType.ALL,
307
+ });
308
+
309
+ const slotDefinitions = this.buckets.map((bucket, slotId) => ({
310
+ slotId,
311
+ bucketName: bucket.bucketName,
312
+ previewUrl: this.distributions[slotId]
313
+ ? `https://${this.distributions[slotId].distributionDomainName}`
314
+ : bucket.bucketWebsiteUrl,
315
+ }));
316
+
317
+ const leaseApiHandler = new lambda.Function(
318
+ this,
319
+ "PreviewLeaseApiHandler",
320
+ {
321
+ runtime: lambda.Runtime.NODEJS_20_X,
322
+ handler: "index.handler",
323
+ timeout: cdk.Duration.seconds(15),
324
+ code: lambda.Code.fromAsset(path.join(__dirname, "..", "lambda")),
325
+ environment: {
326
+ TABLE_NAME: this.leaseTable.tableName,
327
+ SLOT_DEFINITIONS: JSON.stringify(slotDefinitions),
328
+ MAX_LEASE_MS: String(maxLeaseMs),
329
+ },
330
+ },
141
331
  );
332
+
333
+ this.leaseTable.grantReadWriteData(leaseApiHandler);
334
+
335
+ this.api = new apigateway.RestApi(this, "PreviewLeaseApi", {
336
+ restApiName: `${cdk.Names.uniqueId(this)}-preview-lease-api`,
337
+ description: "API for claiming/releasing preview slots",
338
+ });
339
+
340
+ const claimResource = this.api.root.addResource("claim");
341
+ claimResource.addMethod(
342
+ "POST",
343
+ new apigateway.LambdaIntegration(leaseApiHandler),
344
+ );
345
+ const heartbeatResource = this.api.root.addResource("heartbeat");
346
+ heartbeatResource.addMethod(
347
+ "POST",
348
+ new apigateway.LambdaIntegration(leaseApiHandler),
349
+ );
350
+ const releaseResource = this.api.root.addResource("release");
351
+ releaseResource.addMethod(
352
+ "POST",
353
+ new apigateway.LambdaIntegration(leaseApiHandler),
354
+ );
355
+
356
+ this.claimEndpoint = `${this.api.url}claim`;
357
+ this.heartbeatEndpoint = `${this.api.url}heartbeat`;
358
+ this.releaseEndpoint = `${this.api.url}release`;
359
+
360
+ new cdk.CfnOutput(this, "preview-claim-endpoint", {
361
+ value: this.claimEndpoint,
362
+ description: "POST endpoint used to claim a preview slot",
363
+ });
364
+ new cdk.CfnOutput(this, "preview-heartbeat-endpoint", {
365
+ value: this.heartbeatEndpoint,
366
+ description: "POST endpoint used to refresh a preview slot lease",
367
+ });
368
+ new cdk.CfnOutput(this, "preview-release-endpoint", {
369
+ value: this.releaseEndpoint,
370
+ description: "POST endpoint used to release a preview slot",
371
+ });
372
+ }
373
+
374
+ public grantDeploymentAccess(grantee: iam.IGrantable): void {
375
+ this.buckets.forEach((bucket) => bucket.grantReadWrite(grantee));
376
+ this.distributions.forEach((distribution) => {
377
+ grantee.grantPrincipal.addToPrincipalPolicy(
378
+ new iam.PolicyStatement({
379
+ actions: ["cloudfront:CreateInvalidation"],
380
+ resources: [distribution.distributionArn],
381
+ }),
382
+ );
383
+ });
142
384
  }
143
385
  }