@cdklabs/cdk-appmod-catalog-blueprints 1.0.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/.jsii +8644 -0
- package/LICENSE +202 -0
- package/README.md +212 -0
- package/lib/document-processing/agentic-document-processing.d.ts +16 -0
- package/lib/document-processing/agentic-document-processing.js +90 -0
- package/lib/document-processing/base-document-processing.d.ts +189 -0
- package/lib/document-processing/base-document-processing.js +509 -0
- package/lib/document-processing/bedrock-document-processing.d.ts +167 -0
- package/lib/document-processing/bedrock-document-processing.js +297 -0
- package/lib/document-processing/index.d.ts +3 -0
- package/lib/document-processing/index.js +20 -0
- package/lib/document-processing/resources/default-bedrock-invoke/index.py +63 -0
- package/lib/document-processing/resources/default-bedrock-invoke/requirements.txt +4 -0
- package/lib/document-processing/resources/default-doc-retrieval-lambda/index.mjs +92 -0
- package/lib/document-processing/resources/default-doc-retrieval-lambda/package.json +10 -0
- package/lib/document-processing/resources/default-error-handler/index.js +46 -0
- package/lib/document-processing/resources/default-error-handler/package.json +4 -0
- package/lib/document-processing/resources/default-image-processor/classifier.mjs +665 -0
- package/lib/document-processing/resources/default-image-processor/extractors.mjs +465 -0
- package/lib/document-processing/resources/default-image-processor/index.mjs +143 -0
- package/lib/document-processing/resources/default-image-processor/package-lock.json +12 -0
- package/lib/document-processing/resources/default-image-processor/package.json +4 -0
- package/lib/document-processing/resources/default-image-validator/index.mjs +76 -0
- package/lib/document-processing/resources/default-image-validator/package-lock.json +154 -0
- package/lib/document-processing/resources/default-image-validator/package.json +7 -0
- package/lib/document-processing/resources/default-pdf-processor/index.js +46 -0
- package/lib/document-processing/resources/default-pdf-validator/index.js +36 -0
- package/lib/document-processing/resources/default-sqs-consumer/index.py +111 -0
- package/lib/document-processing/resources/default-sqs-consumer/requirements.txt +4 -0
- package/lib/document-processing/resources/default-sqs-consumer/sample_payload.json +20 -0
- package/lib/document-processing/resources/default-sqs-consumer/sample_payload_multi.json +24 -0
- package/lib/document-processing/resources/default-strands-agent/index.py +111 -0
- package/lib/document-processing/resources/default-strands-agent/requirements.txt +6 -0
- package/lib/document-processing/tests/agentic-document-processing-nag.test.d.ts +1 -0
- package/lib/document-processing/tests/agentic-document-processing-nag.test.js +107 -0
- package/lib/document-processing/tests/agentic-document-processing.test.d.ts +1 -0
- package/lib/document-processing/tests/agentic-document-processing.test.js +125 -0
- package/lib/document-processing/tests/bedrock-document-processing-nag.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +101 -0
- package/lib/document-processing/tests/bedrock-document-processing.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing.test.js +79 -0
- package/lib/framework/custom-resource/default-runtimes.d.ts +21 -0
- package/lib/framework/custom-resource/default-runtimes.js +34 -0
- package/lib/framework/custom-resource/index.d.ts +1 -0
- package/lib/framework/custom-resource/index.js +18 -0
- package/lib/framework/foundation/access-log.d.ts +69 -0
- package/lib/framework/foundation/access-log.js +121 -0
- package/lib/framework/foundation/eventbridge-broker.d.ts +18 -0
- package/lib/framework/foundation/eventbridge-broker.js +42 -0
- package/lib/framework/foundation/index.d.ts +3 -0
- package/lib/framework/foundation/index.js +20 -0
- package/lib/framework/foundation/network.d.ts +19 -0
- package/lib/framework/foundation/network.js +83 -0
- package/lib/framework/index.d.ts +2 -0
- package/lib/framework/index.js +19 -0
- package/lib/framework/quickstart/base-quickstart.d.ts +30 -0
- package/lib/framework/quickstart/base-quickstart.js +30 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +21 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/utilities/cdk-nag-config.d.ts +42 -0
- package/lib/utilities/cdk-nag-config.js +194 -0
- package/lib/utilities/data-loader-lambda/index.py +282 -0
- package/lib/utilities/data-loader-lambda/requirements.txt +3 -0
- package/lib/utilities/data-loader.d.ts +173 -0
- package/lib/utilities/data-loader.js +447 -0
- package/lib/utilities/index.d.ts +3 -0
- package/lib/utilities/index.js +20 -0
- package/lib/utilities/lambda-iam-utils.d.ts +145 -0
- package/lib/utilities/lambda-iam-utils.js +235 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.d.ts +42 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.js +53 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.ts +88 -0
- package/lib/utilities/observability/bedrock-observability.d.ts +18 -0
- package/lib/utilities/observability/bedrock-observability.js +131 -0
- package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.d.ts +6 -0
- package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +22 -0
- package/lib/utilities/observability/index.d.ts +6 -0
- package/lib/utilities/observability/index.js +25 -0
- package/lib/utilities/observability/lambda-observability-property-injector.d.ts +8 -0
- package/lib/utilities/observability/lambda-observability-property-injector.js +43 -0
- package/lib/utilities/observability/log-group-data-protection-props.d.ts +19 -0
- package/lib/utilities/observability/log-group-data-protection-props.js +5 -0
- package/lib/utilities/observability/observability.d.ts +83 -0
- package/lib/utilities/observability/observability.js +278 -0
- package/lib/utilities/observability/observable.d.ts +32 -0
- package/lib/utilities/observability/observable.js +3 -0
- package/lib/utilities/observability/powertools-config.d.ts +3 -0
- package/lib/utilities/observability/powertools-config.js +25 -0
- package/lib/utilities/observability/resources/bedrock-manage-logging-configuration/index.py +27 -0
- package/lib/utilities/observability/state-machine-observability-property-injector.d.ts +8 -0
- package/lib/utilities/observability/state-machine-observability-property-injector.js +49 -0
- package/lib/utilities/tests/data-loader-nag.test.d.ts +1 -0
- package/lib/utilities/tests/data-loader-nag.test.js +432 -0
- package/lib/utilities/tests/data-loader.test.d.ts +1 -0
- package/lib/utilities/tests/data-loader.test.js +284 -0
- package/lib/webapp/frontend-construct.d.ts +136 -0
- package/lib/webapp/frontend-construct.js +253 -0
- package/lib/webapp/index.d.ts +1 -0
- package/lib/webapp/index.js +18 -0
- package/lib/webapp/tests/frontend-construct-nag.test.d.ts +1 -0
- package/lib/webapp/tests/frontend-construct-nag.test.js +266 -0
- package/lib/webapp/tests/frontend-construct.test.d.ts +1 -0
- package/lib/webapp/tests/frontend-construct.test.js +385 -0
- package/package.json +183 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
6
|
+
const assertions_1 = require("aws-cdk-lib/assertions");
|
|
7
|
+
const aws_ec2_1 = require("aws-cdk-lib/aws-ec2");
|
|
8
|
+
const aws_rds_1 = require("aws-cdk-lib/aws-rds");
|
|
9
|
+
const aws_secretsmanager_1 = require("aws-cdk-lib/aws-secretsmanager");
|
|
10
|
+
const data_loader_1 = require("../data-loader");
|
|
11
|
+
describe('DataLoader', () => {
|
|
12
|
+
let app;
|
|
13
|
+
let stack;
|
|
14
|
+
let vpc;
|
|
15
|
+
let cluster;
|
|
16
|
+
let secret;
|
|
17
|
+
let securityGroup;
|
|
18
|
+
let template;
|
|
19
|
+
// Use beforeAll instead of beforeEach to avoid recreating infrastructure
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
app = new aws_cdk_lib_1.App();
|
|
22
|
+
stack = new aws_cdk_lib_1.Stack(app, 'TestStack');
|
|
23
|
+
// Create VPC
|
|
24
|
+
vpc = new aws_ec2_1.Vpc(stack, 'TestVpc');
|
|
25
|
+
// Create security group for database access
|
|
26
|
+
securityGroup = new aws_ec2_1.SecurityGroup(stack, 'DatabaseSecurityGroup', {
|
|
27
|
+
vpc: vpc,
|
|
28
|
+
description: 'Security group for database access',
|
|
29
|
+
});
|
|
30
|
+
// Create database secret
|
|
31
|
+
secret = new aws_secretsmanager_1.Secret(stack, 'DatabaseSecret', {
|
|
32
|
+
generateSecretString: {
|
|
33
|
+
secretStringTemplate: JSON.stringify({ username: 'testuser' }),
|
|
34
|
+
generateStringKey: 'password',
|
|
35
|
+
excludeCharacters: '"@/\\',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// Create Aurora cluster
|
|
39
|
+
cluster = new aws_rds_1.DatabaseCluster(stack, 'TestCluster', {
|
|
40
|
+
engine: aws_rds_1.DatabaseClusterEngine.auroraPostgres({
|
|
41
|
+
version: aws_rds_1.AuroraPostgresEngineVersion.VER_13_7,
|
|
42
|
+
}),
|
|
43
|
+
credentials: {
|
|
44
|
+
username: 'testuser',
|
|
45
|
+
password: secret.secretValueFromJson('password'),
|
|
46
|
+
},
|
|
47
|
+
vpc,
|
|
48
|
+
writer: aws_rds_1.ClusterInstance.provisioned('writer', {
|
|
49
|
+
instanceType: aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T3, aws_ec2_1.InstanceSize.MEDIUM),
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
// Create all DataLoader instances first
|
|
53
|
+
new data_loader_1.DataLoader(stack, 'BasicDataLoader', {
|
|
54
|
+
databaseConfig: {
|
|
55
|
+
engine: data_loader_1.DatabaseEngine.POSTGRESQL,
|
|
56
|
+
cluster: cluster,
|
|
57
|
+
secret: secret,
|
|
58
|
+
databaseName: 'testdb',
|
|
59
|
+
vpc: vpc,
|
|
60
|
+
securityGroup: securityGroup,
|
|
61
|
+
},
|
|
62
|
+
fileInputs: [
|
|
63
|
+
{
|
|
64
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
65
|
+
fileType: data_loader_1.FileType.SQL,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
new data_loader_1.DataLoader(stack, 'MultiFileDataLoader', {
|
|
70
|
+
databaseConfig: {
|
|
71
|
+
engine: data_loader_1.DatabaseEngine.MYSQL,
|
|
72
|
+
cluster: cluster,
|
|
73
|
+
secret: secret,
|
|
74
|
+
databaseName: 'testdb',
|
|
75
|
+
vpc: vpc,
|
|
76
|
+
securityGroup: securityGroup,
|
|
77
|
+
},
|
|
78
|
+
fileInputs: [
|
|
79
|
+
{
|
|
80
|
+
filePath: 's3://test-bucket/schema.sql',
|
|
81
|
+
fileType: data_loader_1.FileType.SQL,
|
|
82
|
+
executionOrder: 1,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
filePath: 's3://test-bucket/data.sql',
|
|
86
|
+
fileType: data_loader_1.FileType.MYSQLDUMP,
|
|
87
|
+
executionOrder: 2,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
new data_loader_1.DataLoader(stack, 'CustomDataLoader', {
|
|
92
|
+
databaseConfig: {
|
|
93
|
+
engine: data_loader_1.DatabaseEngine.POSTGRESQL,
|
|
94
|
+
cluster: cluster,
|
|
95
|
+
secret: secret,
|
|
96
|
+
databaseName: 'testdb',
|
|
97
|
+
vpc: vpc,
|
|
98
|
+
securityGroup: securityGroup,
|
|
99
|
+
},
|
|
100
|
+
fileInputs: [
|
|
101
|
+
{
|
|
102
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
103
|
+
fileType: data_loader_1.FileType.SQL,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.RETAIN,
|
|
107
|
+
memorySize: 2048,
|
|
108
|
+
});
|
|
109
|
+
// Generate template once after all constructs are created
|
|
110
|
+
template = assertions_1.Template.fromStack(stack);
|
|
111
|
+
});
|
|
112
|
+
describe('Basic functionality', () => {
|
|
113
|
+
test('creates DataLoader construct with minimal configuration', () => {
|
|
114
|
+
expect(stack.node.findChild('BasicDataLoader')).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
test('creates expected AWS resources', () => {
|
|
117
|
+
template.hasResourceProperties('AWS::S3::Bucket', {
|
|
118
|
+
BucketEncryption: {
|
|
119
|
+
ServerSideEncryptionConfiguration: [
|
|
120
|
+
{
|
|
121
|
+
ServerSideEncryptionByDefault: {
|
|
122
|
+
SSEAlgorithm: 'AES256',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
template.hasResourceProperties('AWS::StepFunctions::StateMachine', {});
|
|
129
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
130
|
+
Runtime: 'python3.11',
|
|
131
|
+
Handler: 'index.handler',
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
test('supports multiple file inputs with execution order', () => {
|
|
135
|
+
expect(stack.node.findChild('MultiFileDataLoader')).toBeDefined();
|
|
136
|
+
});
|
|
137
|
+
test('supports custom configuration options', () => {
|
|
138
|
+
expect(stack.node.findChild('CustomDataLoader')).toBeDefined();
|
|
139
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
140
|
+
MemorySize: 2048,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('Validation', () => {
|
|
145
|
+
test('throws error when databaseConfig is missing', () => {
|
|
146
|
+
expect(() => {
|
|
147
|
+
new data_loader_1.DataLoader(stack, 'InvalidConfigDataLoader', {
|
|
148
|
+
databaseConfig: undefined,
|
|
149
|
+
fileInputs: [
|
|
150
|
+
{
|
|
151
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
152
|
+
fileType: data_loader_1.FileType.SQL,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
}).toThrow('databaseConfig is required');
|
|
157
|
+
});
|
|
158
|
+
test('throws error when both cluster and instance are missing', () => {
|
|
159
|
+
expect(() => {
|
|
160
|
+
new data_loader_1.DataLoader(stack, 'NoClusterDataLoader', {
|
|
161
|
+
databaseConfig: {
|
|
162
|
+
engine: data_loader_1.DatabaseEngine.POSTGRESQL,
|
|
163
|
+
secret: secret,
|
|
164
|
+
databaseName: 'testdb',
|
|
165
|
+
vpc: vpc,
|
|
166
|
+
securityGroup: securityGroup,
|
|
167
|
+
},
|
|
168
|
+
fileInputs: [
|
|
169
|
+
{
|
|
170
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
171
|
+
fileType: data_loader_1.FileType.SQL,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
}).toThrow('Either cluster or instance must be provided in databaseConfig');
|
|
176
|
+
});
|
|
177
|
+
test('throws error when fileInputs is empty', () => {
|
|
178
|
+
expect(() => {
|
|
179
|
+
new data_loader_1.DataLoader(stack, 'EmptyInputsDataLoader', {
|
|
180
|
+
databaseConfig: {
|
|
181
|
+
engine: data_loader_1.DatabaseEngine.POSTGRESQL,
|
|
182
|
+
cluster: cluster,
|
|
183
|
+
secret: secret,
|
|
184
|
+
databaseName: 'testdb',
|
|
185
|
+
vpc: vpc,
|
|
186
|
+
securityGroup: securityGroup,
|
|
187
|
+
},
|
|
188
|
+
fileInputs: [],
|
|
189
|
+
});
|
|
190
|
+
}).toThrow('At least one file input is required');
|
|
191
|
+
});
|
|
192
|
+
test('throws error when MySQL engine is used with PostgreSQL dump', () => {
|
|
193
|
+
expect(() => {
|
|
194
|
+
new data_loader_1.DataLoader(stack, 'MySQLPgDumpDataLoader', {
|
|
195
|
+
databaseConfig: {
|
|
196
|
+
engine: data_loader_1.DatabaseEngine.MYSQL,
|
|
197
|
+
cluster: cluster,
|
|
198
|
+
secret: secret,
|
|
199
|
+
databaseName: 'testdb',
|
|
200
|
+
vpc: vpc,
|
|
201
|
+
securityGroup: securityGroup,
|
|
202
|
+
},
|
|
203
|
+
fileInputs: [
|
|
204
|
+
{
|
|
205
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
206
|
+
fileType: data_loader_1.FileType.PGDUMP,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
}).toThrow('PostgreSQL dump files cannot be used with MySQL databases');
|
|
211
|
+
});
|
|
212
|
+
test('throws error when PostgreSQL engine is used with MySQL dump', () => {
|
|
213
|
+
expect(() => {
|
|
214
|
+
new data_loader_1.DataLoader(stack, 'PostgreSQLMySQLDumpDataLoader', {
|
|
215
|
+
databaseConfig: {
|
|
216
|
+
engine: data_loader_1.DatabaseEngine.POSTGRESQL,
|
|
217
|
+
cluster: cluster,
|
|
218
|
+
secret: secret,
|
|
219
|
+
databaseName: 'testdb',
|
|
220
|
+
vpc: vpc,
|
|
221
|
+
securityGroup: securityGroup,
|
|
222
|
+
},
|
|
223
|
+
fileInputs: [
|
|
224
|
+
{
|
|
225
|
+
filePath: 's3://test-bucket/test-data.sql',
|
|
226
|
+
fileType: data_loader_1.FileType.MYSQLDUMP,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
}).toThrow('MySQL dump files cannot be used with PostgreSQL databases');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe('Removal Policy', () => {
|
|
234
|
+
test('applies custom removal policy to resources', () => {
|
|
235
|
+
expect(stack.node.findChild('CustomDataLoader')).toBeDefined();
|
|
236
|
+
template.hasResource('AWS::S3::Bucket', {
|
|
237
|
+
DeletionPolicy: 'Retain',
|
|
238
|
+
UpdateReplacePolicy: 'Retain',
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
test('uses default DESTROY removal policy when not specified', () => {
|
|
242
|
+
expect(stack.node.findChild('BasicDataLoader')).toBeDefined();
|
|
243
|
+
template.hasResource('AWS::S3::Bucket', {
|
|
244
|
+
DeletionPolicy: 'Delete',
|
|
245
|
+
UpdateReplacePolicy: 'Delete',
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('Security', () => {
|
|
250
|
+
test('creates resources with proper security configurations', () => {
|
|
251
|
+
template.hasResourceProperties('AWS::S3::Bucket', {
|
|
252
|
+
PublicAccessBlockConfiguration: {
|
|
253
|
+
BlockPublicAcls: true,
|
|
254
|
+
BlockPublicPolicy: true,
|
|
255
|
+
IgnorePublicAcls: true,
|
|
256
|
+
RestrictPublicBuckets: true,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
260
|
+
Handler: 'index.handler',
|
|
261
|
+
Runtime: 'python3.11',
|
|
262
|
+
});
|
|
263
|
+
const lambdaFunctions = template.findResources('AWS::Lambda::Function');
|
|
264
|
+
const hasVpcConfig = Object.values(lambdaFunctions).some((fn) => fn.Properties?.VpcConfig?.SubnetIds);
|
|
265
|
+
expect(hasVpcConfig).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
test('grants appropriate IAM permissions', () => {
|
|
268
|
+
template.hasResourceProperties('AWS::IAM::Role', {
|
|
269
|
+
AssumeRolePolicyDocument: {
|
|
270
|
+
Statement: [
|
|
271
|
+
{
|
|
272
|
+
Action: 'sts:AssumeRole',
|
|
273
|
+
Effect: 'Allow',
|
|
274
|
+
Principal: {
|
|
275
|
+
Service: 'lambda.amazonaws.com',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1sb2FkZXIudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3VzZS1jYXNlcy91dGlsaXRpZXMvdGVzdHMvZGF0YS1sb2FkZXIudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEscUVBQXFFO0FBQ3JFLHNDQUFzQzs7QUFFdEMsNkNBQXdEO0FBQ3hELHVEQUFrRDtBQUNsRCxpREFBb0c7QUFDcEcsaURBQTJIO0FBQzNILHVFQUF3RDtBQUN4RCxnREFBc0U7QUFFdEUsUUFBUSxDQUFDLFlBQVksRUFBRSxHQUFHLEVBQUU7SUFDMUIsSUFBSSxHQUFRLENBQUM7SUFDYixJQUFJLEtBQVksQ0FBQztJQUNqQixJQUFJLEdBQVEsQ0FBQztJQUNiLElBQUksT0FBd0IsQ0FBQztJQUM3QixJQUFJLE1BQWMsQ0FBQztJQUNuQixJQUFJLGFBQTRCLENBQUM7SUFDakMsSUFBSSxRQUFrQixDQUFDO0lBRXZCLHlFQUF5RTtJQUN6RSxTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsR0FBRyxHQUFHLElBQUksaUJBQUcsRUFBRSxDQUFDO1FBQ2hCLEtBQUssR0FBRyxJQUFJLG1CQUFLLENBQUMsR0FBRyxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBRXBDLGFBQWE7UUFDYixHQUFHLEdBQUcsSUFBSSxhQUFHLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBRWhDLDRDQUE0QztRQUM1QyxhQUFhLEdBQUcsSUFBSSx1QkFBYSxDQUFDLEtBQUssRUFBRSx1QkFBdUIsRUFBRTtZQUNoRSxHQUFHLEVBQUUsR0FBRztZQUNSLFdBQVcsRUFBRSxvQ0FBb0M7U0FDbEQsQ0FBQyxDQUFDO1FBRUgseUJBQXlCO1FBQ3pCLE1BQU0sR0FBRyxJQUFJLDJCQUFNLENBQUMsS0FBSyxFQUFFLGdCQUFnQixFQUFFO1lBQzNDLG9CQUFvQixFQUFFO2dCQUNwQixvQkFBb0IsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsUUFBUSxFQUFF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QUFJO29CQUN0QixxQkFBcUIsRUFBRSxJQUFJO2lCQUM1QjthQUNGLENBQUMsQ0FBQztZQUVILFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyx1QkFBdUIsRUFBRTtnQkFDdEQsT0FBTyxFQUFFLGVBQWU7Z0JBQ3hCLE9BQU8sRUFBRSxZQUFZO2FBQ3RCLENBQUMsQ0FBQztZQUVILE1BQU0sZUFBZSxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsdUJBQXVCLENBQUMsQ0FBQztZQUN4RSxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQU8sRUFBRSxFQUFFLENBQ25FLEVBQUUsQ0FBQyxVQUFVLEVBQUUsU0FBUyxFQUFFLFNBQVMsQ0FDcEMsQ0FBQztZQUNGLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsb0NBQW9DLEVBQUUsR0FBRyxFQUFFO1lBQzlDLFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyxnQkFBZ0IsRUFBRTtnQkFDL0Msd0JBQXdCLEVBQUU7b0JBQ3hCLFNBQVMsRUFBRTt3QkFDVDs0QkFDRSxNQUFNLEVBQUUsZ0JBQWdCOzRCQUN4QixNQUFNLEVBQUUsT0FBTzs0QkFDZixTQUFTLEVBQUU7Z0NBQ1QsT0FBTyxFQUFFLHNCQUFzQjs2QkFDaEM7eUJBQ0Y7cUJBQ0Y7aUJBQ0Y7YUFDRixDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgQW1hem9uLmNvbSwgSW5jLiBvciBpdHMgYWZmaWxpYXRlcy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IEFwcCwgUmVtb3ZhbFBvbGljeSwgU3RhY2sgfSBmcm9tICdhd3MtY2RrLWxpYic7XG5pbXBvcnQgeyBUZW1wbGF0ZSB9IGZyb20gJ2F3cy1jZGstbGliL2Fzc2VydGlvbnMnO1xuaW1wb3J0IHsgSW5zdGFuY2VDbGFzcywgSW5zdGFuY2VTaXplLCBJbnN0YW5jZVR5cGUsIFZwYywgU2VjdXJpdHlHcm91cCB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1lYzInO1xuaW1wb3J0IHsgRGF0YWJhc2VDbHVzdGVyLCBEYXRhYmFzZUNsdXN0ZXJFbmdpbmUsIEF1cm9yYVBvc3RncmVzRW5naW5lVmVyc2lvbiwgQ2x1c3Rlckluc3RhbmNlIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLXJkcyc7XG5pbXBvcnQgeyBTZWNyZXQgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3Mtc2VjcmV0c21hbmFnZXInO1xuaW1wb3J0IHsgRGF0YUxvYWRlciwgRGF0YWJhc2VFbmdpbmUsIEZpbGVUeXBlIH0gZnJvbSAnLi4vZGF0YS1sb2FkZXInO1xuXG5kZXNjcmliZSgnRGF0YUxvYWRlcicsICgpID0+IHtcbiAgbGV0IGFwcDogQXBwO1xuICBsZXQgc3RhY2s6IFN0YWNrO1xuICBsZXQgdnBjOiBWcGM7XG4gIGxldCBjbHVzdGVyOiBEYXRhYmFzZUNsdXN0ZXI7XG4gIGxldCBzZWNyZXQ6IFNlY3JldDtcbiAgbGV0IHNlY3VyaXR5R3JvdXA6IFNlY3VyaXR5R3JvdXA7XG4gIGxldCB0ZW1wbGF0ZTogVGVtcGxhdGU7XG5cbiAgLy8gVXNlIGJlZm9yZUFsbCBpbnN0ZWFkIG9mIGJlZm9yZUVhY2ggdG8gYXZvaWQgcmVjcmVhdGluZyBpbmZyYXN0cnVjdHVyZVxuICBiZWZvcmVBbGwoKCkgPT4ge1xuICAgIGFwcCA9IG5ldyBBcHAoKTtcbiAgICBzdGFjayA9IG5ldyBTdGFjayhhcHAsICdUZXN0U3RhY2snKTtcblxuICAgIC8vIENyZWF0ZSBWUENcbiAgICB2cGMgPSBuZXcgVnBjKHN0YWNrLCAnVGVzdFZwYycpO1xuXG4gICAgLy8gQ3JlYXRlIHNlY3VyaXR5IGdyb3VwIGZvciBkYXRhYmFzZSBhY2Nlc3NcbiAgICBzZWN1cml0eUdyb3VwID0gbmV3IFNlY3VyaXR5R3JvdXAoc3RhY2ssICdEYXRhYmFzZVNlY3VyaXR5R3JvdXAnLCB7XG4gICAgICB2cGM6IHZwYyxcbiAgICAgIGRlc2NyaXB0aW9uOiAnU2VjdXJpdHkgZ3JvdXAgZm9yIGRhdGFiYXNlIGFjY2VzcycsXG4gICAgfSk7XG5cbiAgICAvLyBDcmVhdGUgZGF0YWJhc2Ugc2VjcmV0XG4gICAgc2VjcmV0ID0gbmV3IFNlY3JldChzdGFjaywgJ0RhdGFiYXNlU2VjcmV0Jywge1xuICAgICAgZ2VuZXJhdGVTZWNyZXRTdHJpbmc6IHtcbiAgICAgICAgc2VjcmV0U3RyaW5nVGVtcGxhdGU6IEpTT04uc3RyaW5naWZ5KHsgdXNlcm5hbWU6ICd0ZXN0dXNlcicgfSksXG4gICAgICAgIGdlbmVyYXRlU3RyaW5nS2V5OiAncGFzc3dvcmQnLFxuICAgICAgICBleGNsdWRlQ2hhcmFjdGVyczogJ1wiQC9cXFxcJyxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICAvLyBDcmVhdGUgQXVyb3JhIGNsdXN0ZXJcbiAgICBjbHVzdGVyID0gbmV3IERhdGFiYXNlQ2x1c3RlcihzdGFjaywgJ1Rlc3RDbHVzdGVyJywge1xuICAgICAgZW5naW5lOiBEYXRhYmFzZUNsdXN0ZXJFbmdpbmUuYXVyb3JhUG9zdGdyZXMoe1xuICAgICAgICB2ZXJzaW9uOiBBdXJvcmFQb3N0Z3Jlc0VuZ2luZVZlcnNpb24uVkVSXzEzXzcsXG4gICAgICB9KSxcbiAgICAgIGNyZWRlbnRpYWxzOiB7XG4gICAgICAgIHVzZXJuYW1lOiAndGVzdHVzZXInLFxuICAgICAgICBwYXNzd29yZDogc2VjcmV0LnNlY3JldFZhbHVlRnJvbUpzb24oJ3Bhc3N3b3JkJyksXG4gICAgICB9LFxuICAgICAgdnBjLFxuICAgICAgd3JpdGVyOiBDbHVzdGVySW5zdGFuY2UucHJvdmlzaW9uZWQoJ3dyaXRlcicsIHtcbiAgICAgICAgaW5zdGFuY2VUeXBlOiBJbnN0YW5jZVR5cGUub2YoSW5zdGFuY2VDbGFzcy5UMywgSW5zdGFuY2VTaXplLk1FRElVTSksXG4gICAgICB9KSxcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBhbGwgRGF0YUxvYWRlciBpbnN0YW5jZXMgZmlyc3RcbiAgICBuZXcgRGF0YUxvYWRlcihzdGFjaywgJ0Jhc2ljRGF0YUxvYWRlcicsIHtcbiAgICAgIGRhdGFiYXNlQ29uZmlnOiB7XG4gICAgICAgIGVuZ2luZTogRGF0YWJhc2VFbmdpbmUuUE9TVEdSRVNRTCxcbiAgICAgICAgY2x1c3RlcjogY2x1c3RlcixcbiAgICAgICAgc2VjcmV0OiBzZWNyZXQsXG4gICAgICAgIGRhdGFiYXNlTmFtZTogJ3Rlc3RkYicsXG4gICAgICAgIHZwYzogdnBjLFxuICAgICAgICBzZWN1cml0eUdyb3VwOiBzZWN1cml0eUdyb3VwLFxuICAgICAgfSxcbiAgICAgIGZpbGVJbnB1dHM6IFtcbiAgICAgICAge1xuICAgICAgICAgIGZpbGVQYXRoOiAnczM6Ly90ZXN0LWJ1Y2tldC90ZXN0LWRhdGEuc3FsJyxcbiAgICAgICAgICBmaWxlVHlwZTogRmlsZVR5cGUuU1FMLFxuICAgICAgICB9LFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIG5ldyBEYXRhTG9hZGVyKHN0YWNrLCAnTXVsdGlGaWxlRGF0YUxvYWRlcicsIHtcbiAgICAgIGRhdGFiYXNlQ29uZmlnOiB7XG4gICAgICAgIGVuZ2luZTogRGF0YWJhc2VFbmdpbmUuTVlTUUwsXG4gICAgICAgIGNsdXN0ZXI6IGNsdXN0ZXIsXG4gICAgICAgIHNlY3JldDogc2VjcmV0LFxuICAgICAgICBkYXRhYmFzZU5hbWU6ICd0ZXN0ZGInLFxuICAgICAgICB2cGM6IHZwYyxcbiAgICAgICAgc2VjdXJpdHlHcm91cDogc2VjdXJpdHlHcm91cCxcbiAgICAgIH0sXG4gICAgICBmaWxlSW5wdXRzOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBmaWxlUGF0aDogJ3MzOi8vdGVzdC1idWNrZXQvc2NoZW1hLnNxbCcsXG4gICAgICAgICAgZmlsZVR5cGU6IEZpbGVUeXBlLlNRTCxcbiAgICAgICAgICBleGVjdXRpb25PcmRlcjogMSxcbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIGZpbGVQYXRoOiAnczM6Ly90ZXN0LWJ1Y2tldC9kYXRhLnNxbCcsXG4gICAgICAgICAgZmlsZVR5cGU6IEZpbGVUeXBlLk1ZU1FMRFVNUCxcbiAgICAgICAgICBleGVjdXRpb25PcmRlcjogMixcbiAgICAgICAgfSxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICBuZXcgRGF0YUxvYWRlcihzdGFjaywgJ0N1c3RvbURhdGFMb2FkZXInLCB7XG4gICAgICBkYXRhYmFzZUNvbmZpZzoge1xuICAgICAgICBlbmdpbmU6IERhdGFiYXNlRW5naW5lLlBPU1RHUkVTUUwsXG4gICAgICAgIGNsdXN0ZXI6IGNsdXN0ZXIsXG4gICAgICAgIHNlY3JldDogc2VjcmV0LFxuICAgICAgICBkYXRhYmFzZU5hbWU6ICd0ZXN0ZGInLFxuICAgICAgICB2cGM6IHZwYyxcbiAgICAgICAgc2VjdXJpdHlHcm91cDogc2VjdXJpdHlHcm91cCxcbiAgICAgIH0sXG4gICAgICBmaWxlSW5wdXRzOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBmaWxlUGF0aDogJ3MzOi8vdGVzdC1idWNrZXQvdGVzdC1kYXRhLnNxbCcsXG4gICAgICAgICAgZmlsZVR5cGU6IEZpbGVUeXBlLlNRTCxcbiAgICAgICAgfSxcbiAgICAgIF0sXG4gICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LlJFVEFJTixcbiAgICAgIG1lbW9yeVNpemU6IDIwNDgsXG4gICAgfSk7XG5cbiAgICAvLyBHZW5lcmF0ZSB0ZW1wbGF0ZSBvbmNlIGFmdGVyIGFsbCBjb25zdHJ1Y3RzIGFyZSBjcmVhdGVkXG4gICAgdGVtcGxhdGUgPSBUZW1wbGF0ZS5mcm9tU3RhY2soc3RhY2spO1xuICB9KTtcblxuICBkZXNjcmliZSgnQmFzaWMgZnVuY3Rpb25hbGl0eScsICgpID0+IHtcblxuICAgIHRlc3QoJ2NyZWF0ZXMgRGF0YUxvYWRlciBjb25zdHJ1Y3Qgd2l0aCBtaW5pbWFsIGNvbmZpZ3VyYXRpb24nLCAoKSA9PiB7XG4gICAgICBleHBlY3Qoc3RhY2subm9kZS5maW5kQ2hpbGQoJ0Jhc2ljRGF0YUxvYWRlcicpKS50b0JlRGVmaW5lZCgpO1xuICAgIH0pO1xuXG4gICAgdGVzdCgnY3JlYXRlcyBleHBlY3RlZCBBV1MgcmVzb3VyY2VzJywgKCkgPT4ge1xuICAgICAgdGVtcGxhdGUuaGFzUmVzb3VyY2VQcm9wZXJ0aWVzKCdBV1M6OlMzOjpCdWNrZXQnLCB7XG4gICAgICAgIEJ1Y2tldEVuY3J5cHRpb246IHtcbiAgICAgICAgICBTZXJ2ZXJTaWRlRW5jcnlwdGlvbkNvbmZpZ3VyYXRpb246IFtcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgU2VydmVyU2lkZUVuY3J5cHRpb25CeURlZmF1bHQ6IHtcbiAgICAgICAgICAgICAgICBTU0VBbGdvcml0aG06ICdBRVMyNTYnLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICBdLFxuICAgICAgICB9LFxuICAgICAgfSk7XG5cbiAgICAgIHRlbXBsYXRlLmhhc1Jlc291cmNlUHJvcGVydGllcygnQVdTOjpTdGVwRnVuY3Rpb25zOjpTdGF0ZU1hY2hpbmUnLCB7fSk7XG4gICAgICB0ZW1wbGF0ZS5oYXNSZXNvdXJjZVByb3BlcnRpZXMoJ0FXUzo6TGFtYmRhOjpGdW5jdGlvbicsIHtcbiAgICAgICAgUnVudGltZTogJ3B5dGhvbjMuMTEnLFxuICAgICAgICBIYW5kbGVyOiAnaW5kZXguaGFuZGxlcicsXG4gICAgICB9KTtcbiAgICB9KTtcblxuICAgIHRlc3QoJ3N1cHBvcnRzIG11bHRpcGxlIGZpbGUgaW5wdXRzIHdpdGggZXhlY3V0aW9uIG9yZGVyJywgKCkgPT4ge1xuICAgICAgZXhwZWN0KHN0YWNrLm5vZGUuZmluZENoaWxkKCdNdWx0aUZpbGVEYXRhTG9hZGVyJykpLnRvQmVEZWZpbmVkKCk7XG4gICAgfSk7XG5cbiAgICB0ZXN0KCdzdXBwb3J0cyBjdXN0b20gY29uZmlndXJhdGlvbiBvcHRpb25zJywgKCkgPT4ge1xuICAgICAgZXhwZWN0KHN0YWNrLm5vZGUuZmluZENoaWxkKCdDdXN0b21EYXRhTG9hZGVyJykpLnRvQmVEZWZpbmVkKCk7XG4gICAgICB0ZW1wbGF0ZS5oYXNSZXNvdXJjZVByb3BlcnRpZXMoJ0FXUzo6TGFtYmRhOjpGdW5jdGlvbicsIHtcbiAgICAgICAgTWVtb3J5U2l6ZTogMjA0OCxcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9KTtcblxuICBkZXNjcmliZSgnVmFsaWRhdGlvbicsICgpID0+IHtcbiAgICB0ZXN0KCd0aHJvd3MgZXJyb3Igd2hlbiBkYXRhYmFzZUNvbmZpZyBpcyBtaXNzaW5nJywgKCkgPT4ge1xuICAgICAgZXhwZWN0KCgpID0+IHtcbiAgICAgICAgbmV3IERhdGFMb2FkZXIoc3RhY2ssICdJbnZhbGlkQ29uZmlnRGF0YUxvYWRlcicsIHtcbiAgICAgICAgICBkYXRhYmFzZUNvbmZpZzogdW5kZWZpbmVkIGFzIGFueSxcbiAgICAgICAgICBmaWxlSW5wdXRzOiBbXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIGZpbGVQYXRoOiAnczM6Ly90ZXN0LWJ1Y2tldC90ZXN0LWRhdGEuc3FsJyxcbiAgICAgICAgICAgICAgZmlsZVR5cGU6IEZpbGVUeXBlLlNRTCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgXSxcbiAgICAgICAgfSk7XG4gICAgICB9KS50b1Rocm93KCdkYXRhYmFzZUNvbmZpZyBpcyByZXF1aXJlZCcpO1xuICAgIH0pO1xuXG4gICAgdGVzdCgndGhyb3dzIGVycm9yIHdoZW4gYm90aCBjbHVzdGVyIGFuZCBpbnN0YW5jZSBhcmUgbWlzc2luZycsICgpID0+IHtcbiAgICAgIGV4cGVjdCgoKSA9PiB7XG4gICAgICAgIG5ldyBEYXRhTG9hZGVyKHN0YWNrLCAnTm9DbHVzdGVyRGF0YUxvYWRlcicsIHtcbiAgICAgICAgICBkYXRhYmFzZUNvbmZpZzoge1xuICAgICAgICAgICAgZW5naW5lOiBEYXRhYmFzZUVuZ2luZS5QT1NUR1JFU1FMLFxuICAgICAgICAgICAgc2VjcmV0OiBzZWNyZXQsXG4gICAgICAgICAgICBkYXRhYmFzZU5hbWU6ICd0ZXN0ZGInLFxuICAgICAgICAgICAgdnBjOiB2cGMsXG4gICAgICAgICAgICBzZWN1cml0eUdyb3VwOiBzZWN1cml0eUdyb3VwLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgZmlsZUlucHV0czogW1xuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBmaWxlUGF0aDogJ3MzOi8vdGVzdC1idWNrZXQvdGVzdC1kYXRhLnNxbCcsXG4gICAgICAgICAgICAgIGZpbGVUeXBlOiBGaWxlVHlwZS5TUUwsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIF0sXG4gICAgICAgIH0pO1xuICAgICAgfSkudG9UaHJvdygnRWl0aGVyIGNsdXN0ZXIgb3IgaW5zdGFuY2UgbXVzdCBiZSBwcm92aWRlZCBpbiBkYXRhYmFzZUNvbmZpZycpO1xuICAgIH0pO1xuXG4gICAgdGVzdCgndGhyb3dzIGVycm9yIHdoZW4gZmlsZUlucHV0cyBpcyBlbXB0eScsICgpID0+IHtcbiAgICAgIGV4cGVjdCgoKSA9PiB7XG4gICAgICAgIG5ldyBEYXRhTG9hZGVyKHN0YWNrLCAnRW1wdHlJbnB1dHNEYXRhTG9hZGVyJywge1xuICAgICAgICAgIGRhdGFiYXNlQ29uZmlnOiB7XG4gICAgICAgICAgICBlbmdpbmU6IERhdGFiYXNlRW5naW5lLlBPU1RHUkVTUUwsXG4gICAgICAgICAgICBjbHVzdGVyOiBjbHVzdGVyLFxuICAgICAgICAgICAgc2VjcmV0OiBzZWNyZXQsXG4gICAgICAgICAgICBkYXRhYmFzZU5hbWU6ICd0ZXN0ZGInLFxuICAgICAgICAgICAgdnBjOiB2cGMsXG4gICAgICAgICAgICBzZWN1cml0eUdyb3VwOiBzZWN1cml0eUdyb3VwLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgZmlsZUlucHV0czogW10sXG4gICAgICAgIH0pO1xuICAgICAgfSkudG9UaHJvdygnQXQgbGVhc3Qgb25lIGZpbGUgaW5wdXQgaXMgcmVxdWlyZWQnKTtcbiAgICB9KTtcblxuICAgIHRlc3QoJ3Rocm93cyBlcnJvciB3aGVuIE15U1FMIGVuZ2luZSBpcyB1c2VkIHdpdGggUG9zdGdyZVNRTCBkdW1wJywgKCkgPT4ge1xuICAgICAgZXhwZWN0KCgpID0+IHtcbiAgICAgICAgbmV3IERhdGFMb2FkZXIoc3RhY2ssICdNeVNRTFBnRHVtcERhdGFMb2FkZXInLCB7XG4gICAgICAgICAgZGF0YWJhc2VDb25maWc6IHtcbiAgICAgICAgICAgIGVuZ2luZTogRGF0YWJhc2VFbmdpbmUuTVlTUUwsXG4gICAgICAgICAgICBjbHVzdGVyOiBjbHVzdGVyLFxuICAgICAgICAgICAgc2VjcmV0OiBzZWNyZXQsXG4gICAgICAgICAgICBkYXRhYmFzZU5hbWU6ICd0ZXN0ZGInLFxuICAgICAgICAgICAgdnBjOiB2cGMsXG4gICAgICAgICAgICBzZWN1cml0eUdyb3VwOiBzZWN1cml0eUdyb3VwLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgZmlsZUlucHV0czogW1xuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBmaWxlUGF0aDogJ3MzOi8vdGVzdC1idWNrZXQvdGVzdC1kYXRhLnNxbCcsXG4gICAgICAgICAgICAgIGZpbGVUeXBlOiBGaWxlVHlwZS5QR0RVTVAsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIF0sXG4gICAgICAgIH0pO1xuICAgICAgfSkudG9UaHJvdygnUG9zdGdyZVNRTCBkdW1wIGZpbGVzIGNhbm5vdCBiZSB1c2VkIHdpdGggTXlTUUwgZGF0YWJhc2VzJyk7XG4gICAgfSk7XG5cbiAgICB0ZXN0KCd0aHJvd3MgZXJyb3Igd2hlbiBQb3N0Z3JlU1FMIGVuZ2luZSBpcyB1c2VkIHdpdGggTXlTUUwgZHVtcCcsICgpID0+IHtcbiAgICAgIGV4cGVjdCgoKSA9PiB7XG4gICAgICAgIG5ldyBEYXRhTG9hZGVyKHN0YWNrLCAnUG9zdGdyZVNRTE15U1FMRHVtcERhdGFMb2FkZXInLCB7XG4gICAgICAgICAgZGF0YWJhc2VDb25maWc6IHtcbiAgICAgICAgICAgIGVuZ2luZTogRGF0YWJhc2VFbmdpbmUuUE9TVEdSRVNRTCxcbiAgICAgICAgICAgIGNsdXN0ZXI6IGNsdXN0ZXIsXG4gICAgICAgICAgICBzZWNyZXQ6IHNlY3JldCxcbiAgICAgICAgICAgIGRhdGFiYXNlTmFtZTogJ3Rlc3RkYicsXG4gICAgICAgICAgICB2cGM6IHZwYyxcbiAgICAgICAgICAgIHNlY3VyaXR5R3JvdXA6IHNlY3VyaXR5R3JvdXAsXG4gICAgICAgICAgfSxcbiAgICAgICAgICBmaWxlSW5wdXRzOiBbXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIGZpbGVQYXRoOiAnczM6Ly90ZXN0LWJ1Y2tldC90ZXN0LWRhdGEuc3FsJyxcbiAgICAgICAgICAgICAgZmlsZVR5cGU6IEZpbGVUeXBlLk1ZU1FMRFVNUCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgXSxcbiAgICAgICAgfSk7XG4gICAgICB9KS50b1Rocm93KCdNeVNRTCBkdW1wIGZpbGVzIGNhbm5vdCBiZSB1c2VkIHdpdGggUG9zdGdyZVNRTCBkYXRhYmFzZXMnKTtcbiAgICB9KTtcbiAgfSk7XG5cbiAgZGVzY3JpYmUoJ1JlbW92YWwgUG9saWN5JywgKCkgPT4ge1xuICAgIHRlc3QoJ2FwcGxpZXMgY3VzdG9tIHJlbW92YWwgcG9saWN5IHRvIHJlc291cmNlcycsICgpID0+IHtcbiAgICAgIGV4cGVjdChzdGFjay5ub2RlLmZpbmRDaGlsZCgnQ3VzdG9tRGF0YUxvYWRlcicpKS50b0JlRGVmaW5lZCgpO1xuXG4gICAgICB0ZW1wbGF0ZS5oYXNSZXNvdXJjZSgnQVdTOjpTMzo6QnVja2V0Jywge1xuICAgICAgICBEZWxldGlvblBvbGljeTogJ1JldGFpbicsXG4gICAgICAgIFVwZGF0ZVJlcGxhY2VQb2xpY3k6ICdSZXRhaW4nLFxuICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICB0ZXN0KCd1c2VzIGRlZmF1bHQgREVTVFJPWSByZW1vdmFsIHBvbGljeSB3aGVuIG5vdCBzcGVjaWZpZWQnLCAoKSA9PiB7XG4gICAgICBleHBlY3Qoc3RhY2subm9kZS5maW5kQ2hpbGQoJ0Jhc2ljRGF0YUxvYWRlcicpKS50b0JlRGVmaW5lZCgpO1xuXG4gICAgICB0ZW1wbGF0ZS5oYXNSZXNvdXJjZSgnQVdTOjpTMzo6QnVja2V0Jywge1xuICAgICAgICBEZWxldGlvblBvbGljeTogJ0RlbGV0ZScsXG4gICAgICAgIFVwZGF0ZVJlcGxhY2VQb2xpY3k6ICdEZWxldGUnLFxuICAgICAgfSk7XG4gICAgfSk7XG4gIH0pO1xuXG4gIGRlc2NyaWJlKCdTZWN1cml0eScsICgpID0+IHtcbiAgICB0ZXN0KCdjcmVhdGVzIHJlc291cmNlcyB3aXRoIHByb3BlciBzZWN1cml0eSBjb25maWd1cmF0aW9ucycsICgpID0+IHtcbiAgICAgIHRlbXBsYXRlLmhhc1Jlc291cmNlUHJvcGVydGllcygnQVdTOjpTMzo6QnVja2V0Jywge1xuICAgICAgICBQdWJsaWNBY2Nlc3NCbG9ja0NvbmZpZ3VyYXRpb246IHtcbiAgICAgICAgICBCbG9ja1B1YmxpY0FjbHM6IHRydWUsXG4gICAgICAgICAgQmxvY2tQdWJsaWNQb2xpY3k6IHRydWUsXG4gICAgICAgICAgSWdub3JlUHVibGljQWNsczogdHJ1ZSxcbiAgICAgICAgICBSZXN0cmljdFB1YmxpY0J1Y2tldHM6IHRydWUsXG4gICAgICAgIH0sXG4gICAgICB9KTtcblxuICAgICAgdGVtcGxhdGUuaGFzUmVzb3VyY2VQcm9wZXJ0aWVzKCdBV1M6OkxhbWJkYTo6RnVuY3Rpb24nLCB7XG4gICAgICAgIEhhbmRsZXI6ICdpbmRleC5oYW5kbGVyJyxcbiAgICAgICAgUnVudGltZTogJ3B5dGhvbjMuMTEnLFxuICAgICAgfSk7XG5cbiAgICAgIGNvbnN0IGxhbWJkYUZ1bmN0aW9ucyA9IHRlbXBsYXRlLmZpbmRSZXNvdXJjZXMoJ0FXUzo6TGFtYmRhOjpGdW5jdGlvbicpO1xuICAgICAgY29uc3QgaGFzVnBjQ29uZmlnID0gT2JqZWN0LnZhbHVlcyhsYW1iZGFGdW5jdGlvbnMpLnNvbWUoKGZuOiBhbnkpID0+XG4gICAgICAgIGZuLlByb3BlcnRpZXM/LlZwY0NvbmZpZz8uU3VibmV0SWRzLFxuICAgICAgKTtcbiAgICAgIGV4cGVjdChoYXNWcGNDb25maWcpLnRvQmUodHJ1ZSk7XG4gICAgfSk7XG5cbiAgICB0ZXN0KCdncmFudHMgYXBwcm9wcmlhdGUgSUFNIHBlcm1pc3Npb25zJywgKCkgPT4ge1xuICAgICAgdGVtcGxhdGUuaGFzUmVzb3VyY2VQcm9wZXJ0aWVzKCdBV1M6OklBTTo6Um9sZScsIHtcbiAgICAgICAgQXNzdW1lUm9sZVBvbGljeURvY3VtZW50OiB7XG4gICAgICAgICAgU3RhdGVtZW50OiBbXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIEFjdGlvbjogJ3N0czpBc3N1bWVSb2xlJyxcbiAgICAgICAgICAgICAgRWZmZWN0OiAnQWxsb3cnLFxuICAgICAgICAgICAgICBQcmluY2lwYWw6IHtcbiAgICAgICAgICAgICAgICBTZXJ2aWNlOiAnbGFtYmRhLmFtYXpvbmF3cy5jb20nLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICBdLFxuICAgICAgICB9LFxuICAgICAgfSk7XG4gICAgfSk7XG4gIH0pO1xufSk7XG4iXX0=
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
2
|
+
import { ICertificate } from 'aws-cdk-lib/aws-certificatemanager';
|
|
3
|
+
import { Distribution, ErrorResponse, PriceClass } from 'aws-cdk-lib/aws-cloudfront';
|
|
4
|
+
import { IHostedZone } from 'aws-cdk-lib/aws-route53';
|
|
5
|
+
import { Bucket } from 'aws-cdk-lib/aws-s3';
|
|
6
|
+
import { Asset } from 'aws-cdk-lib/aws-s3-assets';
|
|
7
|
+
import { BucketDeployment } from 'aws-cdk-lib/aws-s3-deployment';
|
|
8
|
+
import { Construct } from 'constructs';
|
|
9
|
+
/**
|
|
10
|
+
* Default CloudFront error responses for Single Page Applications
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_SPA_ERROR_RESPONSES: ErrorResponse[];
|
|
13
|
+
/**
|
|
14
|
+
* Custom domain configuration for the frontend
|
|
15
|
+
*/
|
|
16
|
+
export interface CustomDomainConfig {
|
|
17
|
+
/** Domain name for the frontend (e.g., 'app.example.com') */
|
|
18
|
+
readonly domainName: string;
|
|
19
|
+
/** SSL certificate for the domain (required when domainName is provided) */
|
|
20
|
+
readonly certificate: ICertificate;
|
|
21
|
+
/** Optional hosted zone for automatic DNS record creation */
|
|
22
|
+
readonly hostedZone?: IHostedZone;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Additional CloudFront distribution properties
|
|
26
|
+
*/
|
|
27
|
+
export interface AdditionalDistributionProps {
|
|
28
|
+
/** Optional comment for the distribution */
|
|
29
|
+
readonly comment?: string;
|
|
30
|
+
/** Optional enabled flag for the distribution */
|
|
31
|
+
readonly enabled?: boolean;
|
|
32
|
+
/** Optional price class for the distribution */
|
|
33
|
+
readonly priceClass?: PriceClass;
|
|
34
|
+
/** Optional web ACL ID for the distribution */
|
|
35
|
+
readonly webAclId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Properties for the Frontend construct
|
|
39
|
+
*/
|
|
40
|
+
export interface FrontendProps {
|
|
41
|
+
/** Base directory of the frontend source code */
|
|
42
|
+
readonly sourceDirectory: string;
|
|
43
|
+
/** Directory where build artifacts are located after build command completes (defaults to '{sourceDirectory}/build') */
|
|
44
|
+
readonly buildOutputDirectory?: string;
|
|
45
|
+
/** Optional build command (defaults to 'npm run build') */
|
|
46
|
+
readonly buildCommand?: string;
|
|
47
|
+
/** Optional custom domain configuration */
|
|
48
|
+
readonly customDomain?: CustomDomainConfig;
|
|
49
|
+
/** Optional CloudFront error responses (defaults to SPA-friendly responses) */
|
|
50
|
+
readonly errorResponses?: ErrorResponse[];
|
|
51
|
+
/** Optional additional CloudFront distribution properties */
|
|
52
|
+
readonly distributionProps?: AdditionalDistributionProps;
|
|
53
|
+
/** Optional flag to skip the build process (useful for pre-built artifacts) */
|
|
54
|
+
readonly skipBuild?: boolean;
|
|
55
|
+
/** Optional removal policy for all resources (defaults to DESTROY) */
|
|
56
|
+
readonly removalPolicy?: RemovalPolicy;
|
|
57
|
+
/**
|
|
58
|
+
* Enable logging and tracing for all supporting resource
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
readonly enableObservability?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Frontend construct that deploys a frontend application to S3 and CloudFront
|
|
65
|
+
*
|
|
66
|
+
* This construct provides a complete solution for hosting static frontend applications
|
|
67
|
+
* with the following features:
|
|
68
|
+
* - S3 bucket for hosting static assets with security best practices
|
|
69
|
+
* - CloudFront distribution for global content delivery
|
|
70
|
+
* - Optional custom domain with SSL certificate
|
|
71
|
+
* - Automatic build process execution
|
|
72
|
+
* - SPA-friendly error handling by default
|
|
73
|
+
* - Security configurations
|
|
74
|
+
*/
|
|
75
|
+
export declare class Frontend extends Construct {
|
|
76
|
+
/** The S3 bucket hosting the frontend assets */
|
|
77
|
+
readonly bucket: Bucket;
|
|
78
|
+
/** The CloudFront distribution */
|
|
79
|
+
readonly distribution: Distribution;
|
|
80
|
+
/** The bucket deployment that uploads the frontend assets */
|
|
81
|
+
readonly bucketDeployment: BucketDeployment;
|
|
82
|
+
/** The custom domain name (if configured) */
|
|
83
|
+
readonly domainName?: string;
|
|
84
|
+
/** The Asset containing the frontend source code */
|
|
85
|
+
readonly asset?: Asset;
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new Frontend
|
|
88
|
+
* @param scope The construct scope
|
|
89
|
+
* @param id The construct ID
|
|
90
|
+
* @param props The frontend properties
|
|
91
|
+
*/
|
|
92
|
+
constructor(scope: Construct, id: string, props: FrontendProps);
|
|
93
|
+
/**
|
|
94
|
+
* Validates the construct properties
|
|
95
|
+
* @param props The frontend properties
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
private _validateProps;
|
|
99
|
+
/**
|
|
100
|
+
* Creates an Asset for the frontend source code with bundling
|
|
101
|
+
* @param props The frontend properties
|
|
102
|
+
* @returns The Asset containing the built frontend
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
private _createAsset;
|
|
106
|
+
/**
|
|
107
|
+
* Creates the CloudFront distribution
|
|
108
|
+
* @param props The frontend properties
|
|
109
|
+
* @param removalPolicy The removal policy to apply
|
|
110
|
+
* @returns The CloudFront distribution
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
private _createDistribution;
|
|
114
|
+
/**
|
|
115
|
+
* Sets up custom domain with Route53 record
|
|
116
|
+
* @param customDomain The custom domain configuration
|
|
117
|
+
* @param removalPolicy The removal policy to apply
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
private _setupCustomDomain;
|
|
121
|
+
/**
|
|
122
|
+
* Gets the URL of the frontend application
|
|
123
|
+
* @returns The frontend URL
|
|
124
|
+
*/
|
|
125
|
+
url(): string;
|
|
126
|
+
/**
|
|
127
|
+
* Gets the CloudFront distribution domain name
|
|
128
|
+
* @returns The CloudFront domain name
|
|
129
|
+
*/
|
|
130
|
+
distributionDomainName(): string;
|
|
131
|
+
/**
|
|
132
|
+
* Gets the S3 bucket name
|
|
133
|
+
* @returns The S3 bucket name
|
|
134
|
+
*/
|
|
135
|
+
bucketName(): string;
|
|
136
|
+
}
|