carlin 1.19.11 → 1.19.13
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/dist/cli.js +232 -0
- package/dist/config.js +10 -0
- package/dist/deploy/addDefaults.cloudFormation.js +138 -0
- package/dist/deploy/baseStack/command.js +9 -0
- package/dist/deploy/baseStack/config.js +30 -0
- package/dist/deploy/baseStack/deployBaseStack.js +59 -0
- package/dist/deploy/baseStack/getBaseStackResource.js +26 -0
- package/dist/deploy/baseStack/getBucket.template.js +44 -0
- package/dist/deploy/baseStack/getLambdaImageBuilder.template.js +186 -0
- package/dist/deploy/baseStack/getLambdaLayerBuilder.template.js +140 -0
- package/dist/deploy/baseStack/getVpc.template.js +169 -0
- package/dist/deploy/cicd/cicd.template.js +922 -0
- package/dist/deploy/cicd/command.js +27 -0
- package/dist/deploy/cicd/command.options.js +71 -0
- package/dist/deploy/cicd/config.js +8 -0
- package/dist/deploy/cicd/deployCicd.js +93 -0
- package/dist/deploy/cicd/ecsTaskReportCommand.js +51 -0
- package/dist/deploy/cicd/getCicdStackName.js +11 -0
- package/dist/deploy/cicd/getTriggerPipelineObjectKey.js +11 -0
- package/dist/deploy/cicd/lambdas/cicdApiV1.handler.js +124 -0
- package/dist/deploy/cicd/lambdas/ecsTaskReport.handler.js +126 -0
- package/dist/deploy/cicd/lambdas/executeTasks.js +67 -0
- package/dist/deploy/cicd/lambdas/getProcessEnvVariable.js +10 -0
- package/dist/deploy/cicd/lambdas/githubWebhooksApiV1.handler.js +146 -0
- package/dist/deploy/cicd/lambdas/imageUpdaterSchedule.handler.js +44 -0
- package/dist/deploy/cicd/lambdas/index.js +13 -0
- package/dist/deploy/cicd/lambdas/pipelines.handler.js +134 -0
- package/dist/deploy/cicd/lambdas/putApprovalResultManualTask.js +52 -0
- package/dist/deploy/cicd/lambdas/shConditionalCommands.js +30 -0
- package/dist/deploy/cicd/pipelines.js +76 -0
- package/dist/deploy/cicd/readSSHKey.js +10 -0
- package/dist/deploy/cloudFormation.core.js +300 -0
- package/dist/deploy/cloudFormation.js +182 -0
- package/dist/deploy/command.js +185 -0
- package/dist/deploy/lambda/buildLambdaSingleFile.js +30 -0
- package/dist/deploy/lambda/deployLambdaCode.js +41 -0
- package/dist/deploy/lambda/deployLambdaLayers.js +34 -0
- package/dist/deploy/lambda/uploadCodeToECR.js +53 -0
- package/dist/deploy/lambda/uploadCodeToS3.js +31 -0
- package/dist/deploy/lambdaLayer/command.js +48 -0
- package/dist/deploy/lambdaLayer/deployLambdaLayer.js +140 -0
- package/dist/deploy/readDockerfile.js +18 -0
- package/dist/deploy/s3.js +165 -0
- package/dist/deploy/stackName.js +78 -0
- package/dist/deploy/staticApp/command.js +79 -0
- package/dist/deploy/staticApp/deployStaticApp.js +64 -0
- package/dist/deploy/staticApp/findDefaultBuildFolder.js +27 -0
- package/dist/deploy/staticApp/getOriginShieldRegion.js +30 -0
- package/dist/deploy/staticApp/getStaticAppBucket.js +19 -0
- package/dist/deploy/staticApp/invalidateCloudFront.js +42 -0
- package/dist/deploy/staticApp/removeOldVersions.js +43 -0
- package/dist/deploy/staticApp/staticApp.template.js +303 -0
- package/dist/deploy/staticApp/uploadBuiltAppToS3.js +27 -0
- package/dist/deploy/utils.js +29 -0
- package/dist/generateEnv/generateEnv.js +46 -0
- package/dist/generateEnv/generateEnvCommand.js +9 -0
- package/dist/index.js +5 -0
- package/dist/utils/addGroupToOptions.js +11 -0
- package/dist/utils/cloudFormationTemplate.js +132 -0
- package/dist/utils/codeBuild.js +50 -0
- package/dist/utils/environmentVariables.js +11 -0
- package/dist/utils/exec.js +22 -0
- package/dist/utils/formatCode.js +12 -0
- package/dist/utils/getAwsAccountId.js +10 -0
- package/dist/utils/getCurrentBranch.js +33 -0
- package/dist/utils/getEnvironment.js +6 -0
- package/dist/utils/getIamPath.js +6 -0
- package/dist/utils/getProjectName.js +35 -0
- package/dist/utils/index.js +17 -0
- package/dist/utils/packageJson.js +25 -0
- package/dist/utils/readCloudFormationTemplate.js +33 -0
- package/dist/utils/readObjectFile.js +46 -0
- package/package.json +3 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deployStaticAppCommand = exports.options = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/* eslint-disable no-param-reassign */
|
|
6
|
+
const config_1 = require("../../config");
|
|
7
|
+
const utils_1 = require("../../utils");
|
|
8
|
+
const findDefaultBuildFolder_1 = require("./findDefaultBuildFolder");
|
|
9
|
+
const deployStaticApp_1 = require("./deployStaticApp");
|
|
10
|
+
const cloudFormation_1 = require("../cloudFormation");
|
|
11
|
+
const aws_sdk_1 = tslib_1.__importDefault(require("aws-sdk"));
|
|
12
|
+
exports.options = {
|
|
13
|
+
acm: {
|
|
14
|
+
describe: 'The ARN of the certificate or the name of the exported variable whose value is the ARN of the certificate that will be associated to CloudFront.',
|
|
15
|
+
type: 'string',
|
|
16
|
+
},
|
|
17
|
+
aliases: {
|
|
18
|
+
describe: 'The aliases that will be associated with the CloudFront. See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html',
|
|
19
|
+
implies: ['acm'],
|
|
20
|
+
type: 'array',
|
|
21
|
+
},
|
|
22
|
+
'build-folder': {
|
|
23
|
+
describe: `The folder that will be uploaded. If not provided, it'll search for the folders "${findDefaultBuildFolder_1.defaultBuildFolders.join(', ')}."`,
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
cloudfront: {
|
|
27
|
+
default: false,
|
|
28
|
+
describe: 'A CloudFront resource is created along with S3 if this option is `true`.',
|
|
29
|
+
require: false,
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
},
|
|
32
|
+
'hosted-zone-name': {
|
|
33
|
+
required: false,
|
|
34
|
+
describe: `Is the name of a Route 53 hosted zone. If this value is provided, ${config_1.NAME} creates the subdomains defined on \`--aliases\` option. E.g. if you have a hosted zone named "sub.domain.com", the value provided may be "sub.domain.com".`,
|
|
35
|
+
type: 'string',
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* CloudFront triggers can be only in US East (N. Virginia) Region.
|
|
39
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers
|
|
40
|
+
*/
|
|
41
|
+
region: {
|
|
42
|
+
coerce: () => config_1.CLOUDFRONT_REGION,
|
|
43
|
+
default: config_1.CLOUDFRONT_REGION,
|
|
44
|
+
hidden: true,
|
|
45
|
+
type: 'string',
|
|
46
|
+
},
|
|
47
|
+
'skip-upload': {
|
|
48
|
+
default: false,
|
|
49
|
+
describe: 'Skip files upload to S3. Useful when wanting update only CloudFormation.',
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
},
|
|
52
|
+
spa: {
|
|
53
|
+
default: false,
|
|
54
|
+
describe: 'This option enables CloudFront to serve a single page application (SPA).',
|
|
55
|
+
require: false,
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
exports.deployStaticAppCommand = {
|
|
60
|
+
command: 'static-app',
|
|
61
|
+
describe: 'Deploy static app.',
|
|
62
|
+
builder: (yargs) => yargs
|
|
63
|
+
.options((0, utils_1.addGroupToOptions)(exports.options, 'Deploy Static App Options'))
|
|
64
|
+
/**
|
|
65
|
+
* CloudFront triggers can be only in US East (N. Virginia) Region.
|
|
66
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers
|
|
67
|
+
*/
|
|
68
|
+
.middleware(() => {
|
|
69
|
+
aws_sdk_1.default.config.region = config_1.CLOUDFRONT_REGION;
|
|
70
|
+
}),
|
|
71
|
+
handler: ({ destroy, ...rest }) => {
|
|
72
|
+
if (destroy) {
|
|
73
|
+
(0, cloudFormation_1.destroyCloudFormation)();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
(0, deployStaticApp_1.deployStaticApp)(rest);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deployStaticApp = void 0;
|
|
4
|
+
const cloudFormation_core_1 = require("../cloudFormation.core");
|
|
5
|
+
const getStaticAppBucket_1 = require("./getStaticAppBucket");
|
|
6
|
+
const staticApp_template_1 = require("./staticApp.template");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const invalidateCloudFront_1 = require("./invalidateCloudFront");
|
|
9
|
+
const removeOldVersions_1 = require("./removeOldVersions");
|
|
10
|
+
const uploadBuiltAppToS3_1 = require("./uploadBuiltAppToS3");
|
|
11
|
+
const logPrefix = 'static-app';
|
|
12
|
+
/**
|
|
13
|
+
* 1. Create the stack name that will be passed to CloudFormation.
|
|
14
|
+
* 1. Create a CloudFormation template based on the type of the deployment, and
|
|
15
|
+
* the options, for instance, only S3, SPA, with hosted zone...
|
|
16
|
+
* 1. Create AWS resources using the templated created.
|
|
17
|
+
* 1. Upload static files to the host bucket S3.
|
|
18
|
+
* 1. Remove old deployment versions. Keep only the 3 most recent ones.
|
|
19
|
+
*/
|
|
20
|
+
const deployStaticApp = async ({ acm, aliases, buildFolder, cloudfront, spa, hostedZoneName, region, skipUpload, }) => {
|
|
21
|
+
try {
|
|
22
|
+
const { stackName } = await (0, utils_1.handleDeployInitialization)({ logPrefix });
|
|
23
|
+
const params = { StackName: stackName };
|
|
24
|
+
const template = (0, staticApp_template_1.getStaticAppTemplate)({
|
|
25
|
+
acm,
|
|
26
|
+
aliases,
|
|
27
|
+
cloudfront,
|
|
28
|
+
spa,
|
|
29
|
+
hostedZoneName,
|
|
30
|
+
region,
|
|
31
|
+
});
|
|
32
|
+
const bucket = await (0, getStaticAppBucket_1.getStaticAppBucket)({ stackName });
|
|
33
|
+
/**
|
|
34
|
+
* Stack already exists. Upload files first after changing the files routes
|
|
35
|
+
* because of the version changing.
|
|
36
|
+
*/
|
|
37
|
+
if (bucket) {
|
|
38
|
+
if (!skipUpload) {
|
|
39
|
+
await (0, uploadBuiltAppToS3_1.uploadBuiltAppToS3)({ buildFolder, bucket, cloudfront });
|
|
40
|
+
}
|
|
41
|
+
const { Outputs } = await (0, cloudFormation_core_1.deploy)({ params, template });
|
|
42
|
+
await (0, invalidateCloudFront_1.invalidateCloudFront)({ outputs: Outputs });
|
|
43
|
+
if (!skipUpload) {
|
|
44
|
+
await (0, removeOldVersions_1.removeOldVersions)({ bucket });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
/**
|
|
49
|
+
* Stack doesn't exist. Deploy CloudFormation first, get the bucket name,
|
|
50
|
+
* and upload files to S3.
|
|
51
|
+
*/
|
|
52
|
+
await (0, cloudFormation_core_1.deploy)({ params, template });
|
|
53
|
+
const newBucket = await (0, getStaticAppBucket_1.getStaticAppBucket)({ stackName });
|
|
54
|
+
if (!newBucket) {
|
|
55
|
+
throw new Error(`Cannot find bucket at ${stackName}.`);
|
|
56
|
+
}
|
|
57
|
+
await (0, uploadBuiltAppToS3_1.uploadBuiltAppToS3)({ buildFolder, bucket: newBucket, cloudfront });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
(0, utils_1.handleDeployError)({ error, logPrefix });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
exports.deployStaticApp = deployStaticApp;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findDefaultBuildFolder = exports.defaultBuildFolders = void 0;
|
|
4
|
+
const s3_1 = require("../s3");
|
|
5
|
+
/**
|
|
6
|
+
* Fixes #20 https://github.com/ttoss/carlin/issues/20
|
|
7
|
+
*/
|
|
8
|
+
exports.defaultBuildFolders = ['build', 'out', 'storybook-static'];
|
|
9
|
+
const findDefaultBuildFolder = async () => {
|
|
10
|
+
/**
|
|
11
|
+
* Valid folders have at least one file inside.
|
|
12
|
+
*/
|
|
13
|
+
const validFolders = await Promise.all(exports.defaultBuildFolders.map(async (directory) => {
|
|
14
|
+
const allFiles = await (0, s3_1.getAllFilesInsideADirectory)({
|
|
15
|
+
directory,
|
|
16
|
+
});
|
|
17
|
+
return { directory, isValid: allFiles.length !== 0 };
|
|
18
|
+
}));
|
|
19
|
+
const validFolder = validFolders.reduce((acc, cur) => {
|
|
20
|
+
if (cur.isValid) {
|
|
21
|
+
return cur.directory;
|
|
22
|
+
}
|
|
23
|
+
return acc;
|
|
24
|
+
}, '');
|
|
25
|
+
return validFolder;
|
|
26
|
+
};
|
|
27
|
+
exports.findDefaultBuildFolder = findDefaultBuildFolder;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOriginShieldRegion = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/origin-shield.html#choose-origin-shield-region
|
|
6
|
+
* @param region
|
|
7
|
+
*/
|
|
8
|
+
const getOriginShieldRegion = (region) => {
|
|
9
|
+
switch (region) {
|
|
10
|
+
case 'us-west-1':
|
|
11
|
+
return 'us-west-2';
|
|
12
|
+
case 'af-south-1':
|
|
13
|
+
return 'eu-west-1';
|
|
14
|
+
case 'ap-east-1':
|
|
15
|
+
return 'ap-southeast-1';
|
|
16
|
+
case 'ca-central-1':
|
|
17
|
+
return 'us-east-1';
|
|
18
|
+
case 'eu-south-1':
|
|
19
|
+
return 'eu-central-1';
|
|
20
|
+
case 'eu-west-3':
|
|
21
|
+
return 'eu-west-2';
|
|
22
|
+
case 'eu-north-1':
|
|
23
|
+
return 'eu-west-2';
|
|
24
|
+
case 'me-south-1':
|
|
25
|
+
return 'ap-south-1';
|
|
26
|
+
default:
|
|
27
|
+
return region;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.getOriginShieldRegion = getOriginShieldRegion;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getStaticAppBucket = void 0;
|
|
4
|
+
const cloudFormation_core_1 = require("../cloudFormation.core");
|
|
5
|
+
const STATIC_APP_BUCKET_LOGICAL_ID = 'StaticBucket';
|
|
6
|
+
const getStaticAppBucket = async ({ stackName, }) => {
|
|
7
|
+
const params = {
|
|
8
|
+
LogicalResourceId: STATIC_APP_BUCKET_LOGICAL_ID,
|
|
9
|
+
StackName: stackName,
|
|
10
|
+
};
|
|
11
|
+
try {
|
|
12
|
+
const { StackResourceDetail } = await (0, cloudFormation_core_1.describeStackResource)(params);
|
|
13
|
+
return StackResourceDetail === null || StackResourceDetail === void 0 ? void 0 : StackResourceDetail.PhysicalResourceId;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
exports.getStaticAppBucket = getStaticAppBucket;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.invalidateCloudFront = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const aws_sdk_1 = require("aws-sdk");
|
|
6
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
7
|
+
const CLOUDFRONT_DISTRIBUTION_ID = 'CloudFrontDistributionId';
|
|
8
|
+
const logPrefix = 'static-app';
|
|
9
|
+
const invalidateCloudFront = async ({ outputs, }) => {
|
|
10
|
+
npmlog_1.default.info(logPrefix, 'Invalidating CloudFront...');
|
|
11
|
+
if (!outputs) {
|
|
12
|
+
npmlog_1.default.info(logPrefix, 'Invalidation: outputs do not exist.');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const cloudFrontDistributionIDOutput = outputs.find((output) => output.OutputKey === CLOUDFRONT_DISTRIBUTION_ID);
|
|
16
|
+
if (cloudFrontDistributionIDOutput === null || cloudFrontDistributionIDOutput === void 0 ? void 0 : cloudFrontDistributionIDOutput.OutputValue) {
|
|
17
|
+
const distributionId = cloudFrontDistributionIDOutput.OutputValue;
|
|
18
|
+
const params = {
|
|
19
|
+
DistributionId: distributionId,
|
|
20
|
+
InvalidationBatch: {
|
|
21
|
+
CallerReference: new Date().toISOString(),
|
|
22
|
+
Paths: {
|
|
23
|
+
Items: ['/*'],
|
|
24
|
+
Quantity: 1,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const cloudFront = new aws_sdk_1.CloudFront();
|
|
29
|
+
try {
|
|
30
|
+
await cloudFront.createInvalidation(params).promise();
|
|
31
|
+
npmlog_1.default.info(logPrefix, `CloudFront Distribution ID ${distributionId} invalidated with success.`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
npmlog_1.default.error(logPrefix, `Error while trying to invalidate CloudFront distribution ${distributionId}.`);
|
|
35
|
+
npmlog_1.default.error(logPrefix, err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
npmlog_1.default.info(logPrefix, `Cannot invalidate because distribution does not exist.`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
exports.invalidateCloudFront = invalidateCloudFront;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeOldVersions = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const s3_1 = require("../s3");
|
|
6
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
7
|
+
const semver_1 = tslib_1.__importDefault(require("semver"));
|
|
8
|
+
const logPrefix = 'static-app';
|
|
9
|
+
/**
|
|
10
|
+
* When a static-app deployment is executed, the algorithm delete old versions
|
|
11
|
+
* if there are three newer versions, and keep these three. For instance, if
|
|
12
|
+
* the bucket has the versions/folders below:
|
|
13
|
+
*
|
|
14
|
+
* - `9.0.1/`
|
|
15
|
+
* - `9.0.2/`
|
|
16
|
+
* - `9.2.0/`
|
|
17
|
+
* - `9.3.0/`
|
|
18
|
+
* - `9.3.1/`
|
|
19
|
+
* - `10.0.0/` _<- created by the last deploy._
|
|
20
|
+
*
|
|
21
|
+
* The folders `9.0.1/`, `9.0.2/`, and `9.2.0/` will be delete after the
|
|
22
|
+
* deploy.
|
|
23
|
+
*/
|
|
24
|
+
const removeOldVersions = async ({ bucket }) => {
|
|
25
|
+
try {
|
|
26
|
+
npmlog_1.default.info(logPrefix, 'Removing old versions...');
|
|
27
|
+
const { CommonPrefixes = [] } = await s3_1.s3
|
|
28
|
+
.listObjectsV2({ Bucket: bucket, Delimiter: '/' })
|
|
29
|
+
.promise();
|
|
30
|
+
const versions = CommonPrefixes === null || CommonPrefixes === void 0 ? void 0 : CommonPrefixes.map(({ Prefix }) => Prefix === null || Prefix === void 0 ? void 0 : Prefix.replace('/', '')).filter((version) => semver_1.default.valid(version)).sort((a, b) => (semver_1.default.gt(a, b) ? -1 : 1));
|
|
31
|
+
/**
|
|
32
|
+
* Keep the 3 most recent versions.
|
|
33
|
+
*/
|
|
34
|
+
versions.shift();
|
|
35
|
+
versions.shift();
|
|
36
|
+
versions.shift();
|
|
37
|
+
await Promise.all(versions.map((version) => (0, s3_1.deleteS3Directory)({ bucket, directory: `${version}` })));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
npmlog_1.default.info(logPrefix, `Cannot remove older versions from "${bucket}" bucket.`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
exports.removeOldVersions = removeOldVersions;
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getStaticAppTemplate = exports.ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID = exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const getOriginShieldRegion_1 = require("./getOriginShieldRegion");
|
|
6
|
+
const PACKAGE_VERSION = (0, utils_1.getPackageVersion)();
|
|
7
|
+
const STATIC_APP_BUCKET_LOGICAL_ID = 'StaticBucket';
|
|
8
|
+
const CLOUDFRONT_DISTRIBUTION_ID = 'CloudFrontDistributionId';
|
|
9
|
+
exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID = 'CloudFrontDistribution';
|
|
10
|
+
exports.ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID = 'Route53RecordSetGroup';
|
|
11
|
+
/**
|
|
12
|
+
* Name: Managed-CachingDisabled
|
|
13
|
+
* ID: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
|
|
14
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
|
|
15
|
+
*/
|
|
16
|
+
const CACHE_POLICY_ID = '4135ea2d-6df8-44a3-9df3-4b5a84be39ad';
|
|
17
|
+
/**
|
|
18
|
+
* Name: Managed-CORS-S3Origin
|
|
19
|
+
* ID: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf
|
|
20
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
|
|
21
|
+
*/
|
|
22
|
+
const ORIGIN_REQUEST_POLICY_ID = '88a5eaf4-2fd4-4709-b370-b4c650ea3fcf';
|
|
23
|
+
/**
|
|
24
|
+
* Name: CORS-with-preflight-and-SecurityHeadersPolicy
|
|
25
|
+
* ID: eaab4381-ed33-4a86-88ca-d9558dc6cd63
|
|
26
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html
|
|
27
|
+
*/
|
|
28
|
+
const ORIGIN_RESPONSE_POLICY_ID = 'eaab4381-ed33-4a86-88ca-d9558dc6cd63';
|
|
29
|
+
const getBaseTemplate = ({ spa, }) => {
|
|
30
|
+
return {
|
|
31
|
+
AWSTemplateFormatVersion: '2010-09-09',
|
|
32
|
+
Resources: {
|
|
33
|
+
[STATIC_APP_BUCKET_LOGICAL_ID]: {
|
|
34
|
+
Type: 'AWS::S3::Bucket',
|
|
35
|
+
Properties: {
|
|
36
|
+
CorsConfiguration: {
|
|
37
|
+
CorsRules: [
|
|
38
|
+
{
|
|
39
|
+
AllowedHeaders: ['*'],
|
|
40
|
+
AllowedMethods: ['GET'],
|
|
41
|
+
AllowedOrigins: ['*'],
|
|
42
|
+
Id: 'OpenCors',
|
|
43
|
+
MaxAge: 600,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
WebsiteConfiguration: {
|
|
48
|
+
IndexDocument: `index.html`,
|
|
49
|
+
ErrorDocument: spa ? 'index.html' : '404/index.html',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
[`${STATIC_APP_BUCKET_LOGICAL_ID}S3BucketPolicy`]: {
|
|
54
|
+
Type: 'AWS::S3::BucketPolicy',
|
|
55
|
+
Properties: {
|
|
56
|
+
Bucket: { Ref: STATIC_APP_BUCKET_LOGICAL_ID },
|
|
57
|
+
PolicyDocument: {
|
|
58
|
+
Statement: [
|
|
59
|
+
{
|
|
60
|
+
Action: ['s3:GetObject'],
|
|
61
|
+
Effect: 'Allow',
|
|
62
|
+
Principal: '*',
|
|
63
|
+
Resource: {
|
|
64
|
+
'Fn::Join': [
|
|
65
|
+
'',
|
|
66
|
+
[
|
|
67
|
+
'arn:aws:s3:::',
|
|
68
|
+
{ Ref: STATIC_APP_BUCKET_LOGICAL_ID },
|
|
69
|
+
'/*',
|
|
70
|
+
],
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
Outputs: {
|
|
80
|
+
BucketWebsiteURL: {
|
|
81
|
+
Description: 'Bucket static app website URL',
|
|
82
|
+
Value: {
|
|
83
|
+
'Fn::GetAtt': [STATIC_APP_BUCKET_LOGICAL_ID, 'WebsiteURL'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const getCloudFrontTemplate = ({ acm, aliases = [], cloudfront, spa, hostedZoneName, region, }) => {
|
|
90
|
+
const template = { ...getBaseTemplate({ cloudfront, spa }) };
|
|
91
|
+
const cloudFrontResources = {
|
|
92
|
+
[exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID]: {
|
|
93
|
+
Type: 'AWS::CloudFront::Distribution',
|
|
94
|
+
Properties: {
|
|
95
|
+
DistributionConfig: {
|
|
96
|
+
Comment: {
|
|
97
|
+
'Fn::Sub': [
|
|
98
|
+
'CloudFront Distribution for ${Project} project.',
|
|
99
|
+
{ Project: { Ref: 'Project' } },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
CustomErrorResponses: [403, 404].map((errorCode) => {
|
|
103
|
+
if (spa) {
|
|
104
|
+
return {
|
|
105
|
+
ErrorCachingMinTTL: 60 * 60 * 24,
|
|
106
|
+
ErrorCode: errorCode,
|
|
107
|
+
ResponseCode: 200,
|
|
108
|
+
ResponsePagePath: '/index.html',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ErrorCachingMinTTL: 0,
|
|
113
|
+
ErrorCode: errorCode,
|
|
114
|
+
ResponseCode: 404,
|
|
115
|
+
ResponsePagePath: '/404',
|
|
116
|
+
};
|
|
117
|
+
}),
|
|
118
|
+
DefaultCacheBehavior: {
|
|
119
|
+
AllowedMethods: ['GET', 'HEAD', 'OPTIONS'],
|
|
120
|
+
Compress: true,
|
|
121
|
+
CachedMethods: ['GET', 'HEAD', 'OPTIONS'],
|
|
122
|
+
/**
|
|
123
|
+
* Caching OPTIONS. Related to OriginRequestPolicyId property.
|
|
124
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors
|
|
125
|
+
*/
|
|
126
|
+
OriginRequestPolicyId: ORIGIN_REQUEST_POLICY_ID,
|
|
127
|
+
/**
|
|
128
|
+
* CachePolicyId property:
|
|
129
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-cachepolicyid
|
|
130
|
+
*/
|
|
131
|
+
CachePolicyId: CACHE_POLICY_ID,
|
|
132
|
+
ResponseHeadersPolicyId: ORIGIN_RESPONSE_POLICY_ID,
|
|
133
|
+
TargetOriginId: { Ref: STATIC_APP_BUCKET_LOGICAL_ID },
|
|
134
|
+
ViewerProtocolPolicy: 'redirect-to-https',
|
|
135
|
+
},
|
|
136
|
+
DefaultRootObject: spa ? 'index.html' : undefined,
|
|
137
|
+
Enabled: true,
|
|
138
|
+
HttpVersion: 'http2',
|
|
139
|
+
Origins: [
|
|
140
|
+
{
|
|
141
|
+
CustomOriginConfig: {
|
|
142
|
+
OriginProtocolPolicy: 'http-only',
|
|
143
|
+
},
|
|
144
|
+
/**
|
|
145
|
+
* https://github.com/aws/aws-cdk/issues/1882#issuecomment-629141467
|
|
146
|
+
*/
|
|
147
|
+
DomainName: {
|
|
148
|
+
'Fn::Select': [
|
|
149
|
+
1,
|
|
150
|
+
{
|
|
151
|
+
'Fn::Split': [
|
|
152
|
+
'//',
|
|
153
|
+
{
|
|
154
|
+
'Fn::GetAtt': [
|
|
155
|
+
STATIC_APP_BUCKET_LOGICAL_ID,
|
|
156
|
+
'WebsiteURL',
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
Id: { Ref: STATIC_APP_BUCKET_LOGICAL_ID },
|
|
164
|
+
/**
|
|
165
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/origin-shield.html#choose-origin-shield-region
|
|
166
|
+
*/
|
|
167
|
+
...(region && {
|
|
168
|
+
OriginShield: {
|
|
169
|
+
Enabled: true,
|
|
170
|
+
OriginShieldRegion: (0, getOriginShieldRegion_1.getOriginShieldRegion)(region),
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
if (acm) {
|
|
180
|
+
const acmRegex = /^arn:aws:acm:[-a-z0-9]+:\d{12}:certificate\/[-a-z0-9]+$/;
|
|
181
|
+
const acmCertificateArn = acmRegex.test(acm)
|
|
182
|
+
? acm
|
|
183
|
+
: {
|
|
184
|
+
'Fn::ImportValue': acm,
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Add ACM to CloudFront template.
|
|
188
|
+
*/
|
|
189
|
+
cloudFrontResources.CloudFrontDistribution.Properties.DistributionConfig = {
|
|
190
|
+
...cloudFrontResources.CloudFrontDistribution.Properties
|
|
191
|
+
.DistributionConfig,
|
|
192
|
+
Aliases: aliases || { Ref: 'AWS::NoValue' },
|
|
193
|
+
ViewerCertificate: {
|
|
194
|
+
AcmCertificateArn: acmCertificateArn,
|
|
195
|
+
/**
|
|
196
|
+
* AWS CloudFront recommendation.
|
|
197
|
+
*/
|
|
198
|
+
MinimumProtocolVersion: 'TLSv1.2_2021',
|
|
199
|
+
SslSupportMethod: 'sni-only',
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Add aliases to Route 53 records.
|
|
205
|
+
*/
|
|
206
|
+
if (hostedZoneName && aliases) {
|
|
207
|
+
const recordSets = aliases.map((alias) => {
|
|
208
|
+
if (alias === hostedZoneName) {
|
|
209
|
+
return {
|
|
210
|
+
AliasTarget: {
|
|
211
|
+
DNSName: {
|
|
212
|
+
'Fn::GetAtt': `${exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`,
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
|
|
216
|
+
*/
|
|
217
|
+
HostedZoneId: 'Z2FDTNDATAQYW2',
|
|
218
|
+
},
|
|
219
|
+
Name: alias,
|
|
220
|
+
Type: 'A',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
Name: alias,
|
|
225
|
+
ResourceRecords: [
|
|
226
|
+
{
|
|
227
|
+
'Fn::GetAtt': `${exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
TTL: 60,
|
|
231
|
+
Type: 'CNAME',
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
const route53RecordSetGroupResources = {
|
|
235
|
+
[exports.ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID]: {
|
|
236
|
+
Type: 'AWS::Route53::RecordSetGroup',
|
|
237
|
+
DependsOn: [exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID],
|
|
238
|
+
Properties: {
|
|
239
|
+
// https://forums.aws.amazon.com/thread.jspa?threadID=103919
|
|
240
|
+
HostedZoneName: `${hostedZoneName}${hostedZoneName.endsWith('.') ? '' : '.'}`,
|
|
241
|
+
RecordSets: recordSets,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
template.Resources = {
|
|
246
|
+
...template.Resources,
|
|
247
|
+
...route53RecordSetGroupResources,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
template.Resources = { ...template.Resources, ...cloudFrontResources };
|
|
251
|
+
/**
|
|
252
|
+
* Add aliases output to template.
|
|
253
|
+
*/
|
|
254
|
+
const aliasesOutput = (aliases || []).reduce((acc, alias, index) => ({
|
|
255
|
+
...acc,
|
|
256
|
+
[`Alias${index}URL`]: {
|
|
257
|
+
Value: `https://${alias}`,
|
|
258
|
+
},
|
|
259
|
+
}), {});
|
|
260
|
+
/**
|
|
261
|
+
* Add CloudFront Distribution ID and CloudFront URL to template.
|
|
262
|
+
*/
|
|
263
|
+
template.Outputs = {
|
|
264
|
+
...template.Outputs,
|
|
265
|
+
...aliasesOutput,
|
|
266
|
+
CloudFrontURL: {
|
|
267
|
+
Value: {
|
|
268
|
+
'Fn::Join': [
|
|
269
|
+
'',
|
|
270
|
+
[
|
|
271
|
+
'https://',
|
|
272
|
+
{
|
|
273
|
+
'Fn::GetAtt': `${exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
[CLOUDFRONT_DISTRIBUTION_ID]: {
|
|
280
|
+
Value: {
|
|
281
|
+
Ref: exports.CLOUDFRONT_DISTRIBUTION_LOGICAL_ID,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
CurrentVersion: {
|
|
285
|
+
Value: PACKAGE_VERSION,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
return template;
|
|
289
|
+
};
|
|
290
|
+
const getStaticAppTemplate = ({ acm, aliases, cloudfront, spa, hostedZoneName, region, }) => {
|
|
291
|
+
if (cloudfront) {
|
|
292
|
+
return getCloudFrontTemplate({
|
|
293
|
+
acm,
|
|
294
|
+
aliases,
|
|
295
|
+
cloudfront,
|
|
296
|
+
spa,
|
|
297
|
+
hostedZoneName,
|
|
298
|
+
region,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return getBaseTemplate({ cloudfront, spa });
|
|
302
|
+
};
|
|
303
|
+
exports.getStaticAppTemplate = getStaticAppTemplate;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uploadBuiltAppToS3 = void 0;
|
|
4
|
+
const findDefaultBuildFolder_1 = require("./findDefaultBuildFolder");
|
|
5
|
+
const s3_1 = require("../s3");
|
|
6
|
+
const uploadBuiltAppToS3 = async ({ buildFolder: directory, bucket, }) => {
|
|
7
|
+
/**
|
|
8
|
+
* Only empty directory if the number of the files inside $directory.
|
|
9
|
+
* If the number of files is zero, uploadDirectoryToS3 will thrown.
|
|
10
|
+
*/
|
|
11
|
+
if (directory) {
|
|
12
|
+
const files = await (0, s3_1.getAllFilesInsideADirectory)({ directory });
|
|
13
|
+
if (files.length > 0) {
|
|
14
|
+
await (0, s3_1.emptyS3Directory)({ bucket });
|
|
15
|
+
}
|
|
16
|
+
await (0, s3_1.uploadDirectoryToS3)({ bucket, directory });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const defaultDirectory = await (0, findDefaultBuildFolder_1.findDefaultBuildFolder)();
|
|
20
|
+
if (defaultDirectory) {
|
|
21
|
+
await (0, s3_1.emptyS3Directory)({ bucket });
|
|
22
|
+
await (0, s3_1.uploadDirectoryToS3)({ bucket, directory: defaultDirectory });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`build-folder option wasn't provided and files weren't found in ${findDefaultBuildFolder_1.defaultBuildFolders.join(', ')} directories.`);
|
|
26
|
+
};
|
|
27
|
+
exports.uploadBuiltAppToS3 = uploadBuiltAppToS3;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleDeployInitialization = exports.handleDeployError = exports.deployErrorLogs = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const stackName_1 = require("./stackName");
|
|
6
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
7
|
+
const deployErrorLogs = ({ error, logPrefix, }) => {
|
|
8
|
+
npmlog_1.default.error(logPrefix, `An error occurred. Cannot deploy ${logPrefix}.`);
|
|
9
|
+
npmlog_1.default.error(logPrefix, 'Error message: %j', error.message);
|
|
10
|
+
};
|
|
11
|
+
exports.deployErrorLogs = deployErrorLogs;
|
|
12
|
+
const handleDeployError = ({ error, logPrefix, }) => {
|
|
13
|
+
(0, exports.deployErrorLogs)({ error, logPrefix });
|
|
14
|
+
process.exit(1);
|
|
15
|
+
};
|
|
16
|
+
exports.handleDeployError = handleDeployError;
|
|
17
|
+
/**
|
|
18
|
+
* @param param.stackName acts as a default stack name.
|
|
19
|
+
*/
|
|
20
|
+
const handleDeployInitialization = async ({ logPrefix, stackName: preDefinedStackName, }) => {
|
|
21
|
+
npmlog_1.default.info(logPrefix, `Starting deploy ${logPrefix}...`);
|
|
22
|
+
if (preDefinedStackName) {
|
|
23
|
+
(0, stackName_1.setPreDefinedStackName)(preDefinedStackName);
|
|
24
|
+
}
|
|
25
|
+
const stackName = await (0, stackName_1.getStackName)();
|
|
26
|
+
npmlog_1.default.info(logPrefix, `stackName: ${stackName}`);
|
|
27
|
+
return { stackName };
|
|
28
|
+
};
|
|
29
|
+
exports.handleDeployInitialization = handleDeployInitialization;
|