carlin 1.31.11 → 1.31.12

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.
Files changed (74) hide show
  1. package/dist/index.js +4471 -4
  2. package/package.json +10 -10
  3. package/dist/cli.js +0 -246
  4. package/dist/config.js +0 -11
  5. package/dist/deploy/addDefaults.cloudformation.js +0 -151
  6. package/dist/deploy/baseStack/command.js +0 -9
  7. package/dist/deploy/baseStack/config.js +0 -30
  8. package/dist/deploy/baseStack/deployBaseStack.js +0 -62
  9. package/dist/deploy/baseStack/getBaseStackResource.js +0 -27
  10. package/dist/deploy/baseStack/getBucketTemplate.js +0 -46
  11. package/dist/deploy/baseStack/getLambdaImageBuilderTemplate.js +0 -188
  12. package/dist/deploy/baseStack/getLambdaLayerBuilderTemplate.js +0 -142
  13. package/dist/deploy/baseStack/getVpcTemplate.js +0 -169
  14. package/dist/deploy/cicd/cicd.template.js +0 -938
  15. package/dist/deploy/cicd/command.js +0 -31
  16. package/dist/deploy/cicd/command.options.js +0 -79
  17. package/dist/deploy/cicd/config.js +0 -8
  18. package/dist/deploy/cicd/deployCicd.js +0 -121
  19. package/dist/deploy/cicd/ecsTaskReportCommand.js +0 -55
  20. package/dist/deploy/cicd/getCicdStackName.js +0 -11
  21. package/dist/deploy/cicd/getTriggerPipelineObjectKey.js +0 -11
  22. package/dist/deploy/cicd/lambdas/cicdApiV1.handler.js +0 -124
  23. package/dist/deploy/cicd/lambdas/ecsTaskReport.handler.js +0 -126
  24. package/dist/deploy/cicd/lambdas/executeTasks.js +0 -67
  25. package/dist/deploy/cicd/lambdas/getProcessEnvVariable.js +0 -10
  26. package/dist/deploy/cicd/lambdas/githubWebhooksApiV1.handler.js +0 -148
  27. package/dist/deploy/cicd/lambdas/imageUpdaterSchedule.handler.js +0 -44
  28. package/dist/deploy/cicd/lambdas/index.js +0 -13
  29. package/dist/deploy/cicd/lambdas/pipelines.handler.js +0 -160
  30. package/dist/deploy/cicd/lambdas/putApprovalResultManualTask.js +0 -51
  31. package/dist/deploy/cicd/lambdas/shConditionalCommands.js +0 -30
  32. package/dist/deploy/cicd/pipelines.js +0 -86
  33. package/dist/deploy/cicd/readSSHKey.js +0 -34
  34. package/dist/deploy/cloudformation.core.js +0 -379
  35. package/dist/deploy/cloudformation.js +0 -189
  36. package/dist/deploy/command.js +0 -205
  37. package/dist/deploy/lambda/buildLambdaSingleFile.js +0 -67
  38. package/dist/deploy/lambda/deployLambdaCode.js +0 -43
  39. package/dist/deploy/lambda/deployLambdaLayers.js +0 -36
  40. package/dist/deploy/lambda/uploadCodeToECR.js +0 -53
  41. package/dist/deploy/lambda/uploadCodeToS3.js +0 -33
  42. package/dist/deploy/lambdaLayer/command.js +0 -50
  43. package/dist/deploy/lambdaLayer/deployLambdaLayer.js +0 -139
  44. package/dist/deploy/lambdaLayer/getPackageLambdaLayerStackName.js +0 -21
  45. package/dist/deploy/readDockerfile.js +0 -40
  46. package/dist/deploy/s3.js +0 -210
  47. package/dist/deploy/stackName.js +0 -85
  48. package/dist/deploy/staticApp/command.js +0 -86
  49. package/dist/deploy/staticApp/deployStaticApp.js +0 -65
  50. package/dist/deploy/staticApp/findDefaultBuildFolder.js +0 -44
  51. package/dist/deploy/staticApp/getStaticAppBucket.js +0 -19
  52. package/dist/deploy/staticApp/invalidateCloudFront.js +0 -44
  53. package/dist/deploy/staticApp/removeOldVersions.js +0 -56
  54. package/dist/deploy/staticApp/staticApp.template.js +0 -371
  55. package/dist/deploy/staticApp/uploadBuiltAppToS3.js +0 -28
  56. package/dist/deploy/utils.js +0 -31
  57. package/dist/deploy/vercel/command.js +0 -31
  58. package/dist/deploy/vercel/deployVercel.js +0 -59
  59. package/dist/generateEnv/generateEnv.js +0 -64
  60. package/dist/generateEnv/generateEnvCommand.js +0 -29
  61. package/dist/utils/addGroupToOptions.js +0 -11
  62. package/dist/utils/cloudFormationTemplate.js +0 -142
  63. package/dist/utils/codeBuild.js +0 -52
  64. package/dist/utils/environmentVariables.js +0 -16
  65. package/dist/utils/exec.js +0 -26
  66. package/dist/utils/formatCode.js +0 -34
  67. package/dist/utils/getAwsAccountId.js +0 -10
  68. package/dist/utils/getCurrentBranch.js +0 -35
  69. package/dist/utils/getEnvironment.js +0 -8
  70. package/dist/utils/getIamPath.js +0 -6
  71. package/dist/utils/getProjectName.js +0 -35
  72. package/dist/utils/index.js +0 -31
  73. package/dist/utils/packageJson.js +0 -32
  74. package/dist/utils/spawn.js +0 -34
package/dist/index.js CHANGED
@@ -1,4 +1,4471 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const cli_1 = require("./cli");
4
- (0, cli_1.cli)().parse();
1
+ "use strict"; function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/config.ts
10
+ var NAME = "carlin";
11
+ var AWS_DEFAULT_REGION = "us-east-1";
12
+ var CLOUDFRONT_REGION = "us-east-1";
13
+ var NODE_RUNTIME = "nodejs20.x";
14
+
15
+ // src/utils/addGroupToOptions.ts
16
+ var addGroupToOptions = (options9, group) => {
17
+ Object.values(options9).forEach((option) => {
18
+ option.group = group;
19
+ });
20
+ return options9;
21
+ };
22
+
23
+ // src/utils/codeBuild.ts
24
+ var _awssdk = require('aws-sdk'); var _awssdk2 = _interopRequireDefault(_awssdk);
25
+ var _npmlog = require('npmlog'); var _npmlog2 = _interopRequireDefault(_npmlog);
26
+ var logPrefix = "codebuild";
27
+ var WAIT_TIME = 10 * 1e3;
28
+ var waitCodeBuildFinish = async ({
29
+ buildId,
30
+ name
31
+ }) => {
32
+ const codeBuild2 = new (0, _awssdk.CodeBuild)();
33
+ let result;
34
+ const checkIfBuildIsFinished = async () => {
35
+ const { builds } = await codeBuild2.batchGetBuilds({ ids: [buildId] }).promise();
36
+ return new Promise((resolve5, reject) => {
37
+ setTimeout(() => {
38
+ const executedBuild = _optionalChain([builds, 'optionalAccess', _ => _.find, 'call', _2 => _2(({ id }) => id === buildId)]);
39
+ _npmlog2.default.info(
40
+ logPrefix,
41
+ `Build status of ${name || buildId}: ${_optionalChain([executedBuild, 'optionalAccess', _3 => _3.buildStatus])}`
42
+ );
43
+ if (executedBuild && executedBuild.currentPhase === "COMPLETED") {
44
+ if (executedBuild.buildStatus === "SUCCEEDED") {
45
+ resolve5(executedBuild);
46
+ } else if (["FAILED", "FAILURE"].includes(executedBuild.buildStatus || "")) {
47
+ reject(new Error(`Cannot execute build ${buildId}.`));
48
+ }
49
+ }
50
+ resolve5(void 0);
51
+ }, WAIT_TIME);
52
+ });
53
+ };
54
+ while (!result) {
55
+ result = await checkIfBuildIsFinished();
56
+ }
57
+ return result;
58
+ };
59
+ var startCodeBuildBuild = async ({
60
+ projectName
61
+ }) => {
62
+ const codeBuild2 = new (0, _awssdk.CodeBuild)();
63
+ const { build } = await codeBuild2.startBuild({ projectName }).promise();
64
+ if (!build) {
65
+ throw new Error(`Cannot start ${projectName} build`);
66
+ }
67
+ return build;
68
+ };
69
+
70
+ // src/utils/cloudFormationTemplate.ts
71
+ var _jsyaml = require('js-yaml'); var _jsyaml2 = _interopRequireDefault(_jsyaml);
72
+
73
+ // src/utils/environmentVariables.ts
74
+ var cache = /* @__PURE__ */ new Map();
75
+ var getEnvVar = (key) => {
76
+ return cache.has(key) && cache.get(key) ? cache.get(key) : void 0;
77
+ };
78
+ var setEnvVar = (key, value) => {
79
+ if (!value) {
80
+ return cache.delete(key);
81
+ }
82
+ return cache.set(key, value);
83
+ };
84
+
85
+ // src/utils/exec.ts
86
+
87
+ _npmlog2.default.heading = "exec";
88
+
89
+ // src/utils/formatCode.ts
90
+ var _uglifyjs = require('uglify-js'); var UglifyJS = _interopRequireWildcard(_uglifyjs);
91
+ var _prettier = require('prettier'); var prettier = _interopRequireWildcard(_prettier);
92
+
93
+ // src/utils/getAwsAccountId.ts
94
+
95
+ var getAwsAccountId = async () => {
96
+ const sts = new (0, _awssdk.STS)();
97
+ const { Account } = await sts.getCallerIdentity().promise();
98
+ return Account;
99
+ };
100
+
101
+ // src/utils/getCurrentBranch.ts
102
+ var _simplegit = require('simple-git'); var _simplegit2 = _interopRequireDefault(_simplegit);
103
+ var BRANCH_UNDEFINED = "";
104
+ var getCurrentBranch = async () => {
105
+ try {
106
+ if (getEnvVar("BRANCH")) {
107
+ return getEnvVar("BRANCH");
108
+ }
109
+ const { current } = await _simplegit2.default.call(void 0, ).branch();
110
+ return current || BRANCH_UNDEFINED;
111
+ } catch (err) {
112
+ return BRANCH_UNDEFINED;
113
+ }
114
+ };
115
+
116
+ // src/utils/getEnvironment.ts
117
+ var getEnvironment = () => {
118
+ return getEnvVar("ENVIRONMENT");
119
+ };
120
+
121
+ // src/utils/getIamPath.ts
122
+ var getIamPath = () => `/${NAME}/`;
123
+
124
+ // src/utils/packageJson.ts
125
+ var _findupsync = require('findup-sync'); var _findupsync2 = _interopRequireDefault(_findupsync);
126
+ var _fs = require('fs'); var fs3 = _interopRequireWildcard(_fs); var fs6 = _interopRequireWildcard(_fs); var fs7 = _interopRequireWildcard(_fs); var fs10 = _interopRequireWildcard(_fs); var fs8 = _interopRequireWildcard(_fs); var fs11 = _interopRequireWildcard(_fs); var fs12 = _interopRequireWildcard(_fs);
127
+ var readPackageJson = () => {
128
+ const packageJsonDir = _findupsync2.default.call(void 0, "package.json");
129
+ if (!packageJsonDir) {
130
+ return {};
131
+ }
132
+ return JSON.parse(fs3.default.readFileSync(packageJsonDir).toString());
133
+ };
134
+ var getPackageJsonProperty = ({ property }) => {
135
+ try {
136
+ return readPackageJson()[property];
137
+ } catch (e) {
138
+ return "";
139
+ }
140
+ };
141
+ var getPackageName = () => {
142
+ return getPackageJsonProperty({ property: "name" });
143
+ };
144
+ var getPackageVersion = () => {
145
+ return getPackageJsonProperty({ property: "version" });
146
+ };
147
+
148
+ // src/utils/getProjectName.ts
149
+ var _changecase = require('change-case');
150
+ var getProjectName = () => {
151
+ if (getEnvVar("PROJECT")) {
152
+ return getEnvVar("PROJECT");
153
+ }
154
+ const name = getPackageName();
155
+ if (!name) {
156
+ return "";
157
+ }
158
+ try {
159
+ return _changecase.pascalCase.call(void 0, name.split(/[@/]/)[1]);
160
+ } catch (err) {
161
+ return _changecase.pascalCase.call(void 0, name);
162
+ }
163
+ };
164
+
165
+ // src/utils/spawn.ts
166
+ var _process = require('process');
167
+ var _child_process = require('child_process'); var _child_process2 = _interopRequireDefault(_child_process);
168
+
169
+ _npmlog2.default.heading = "exec";
170
+ var spawn = (cmd) => {
171
+ return new Promise((resolve5, reject) => {
172
+ const [cmdName, ...cmdArgs] = cmd.split(" ");
173
+ const child = _child_process2.default.spawn(cmdName, cmdArgs);
174
+ child.stdout.on("data", (data) => {
175
+ _process.stdout.write(data);
176
+ });
177
+ child.stderr.on("data", (data) => {
178
+ _process.stdout.write(data);
179
+ });
180
+ child.on("error", (error) => {
181
+ reject(error);
182
+ });
183
+ child.on("close", (code) => {
184
+ if (code === 0) {
185
+ resolve5({});
186
+ } else {
187
+ reject(code);
188
+ }
189
+ });
190
+ });
191
+ };
192
+
193
+ // src/cli.ts
194
+
195
+
196
+ // src/deploy/baseStack/config.ts
197
+
198
+ var pascalCaseName = _changecase.pascalCase.call(void 0, NAME);
199
+ var BASE_STACK_NAME = `${pascalCaseName}BaseStack`;
200
+ var BASE_STACK_BUCKET_TEMPLATES_FOLDER = "cloudformation-templates";
201
+ var BASE_STACK_BUCKET_LOGICAL_NAME = `${pascalCaseName}Bucket`;
202
+ var BASE_STACK_BUCKET_NAME_EXPORTED_NAME = `${pascalCaseName}BucketNameExportedName`;
203
+ var BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME = `${pascalCaseName}LambdaImageBuilder`;
204
+ var BASE_STACK_LAMBDA_IMAGE_BUILDER_EXPORTED_NAME = `${pascalCaseName}LambdaImageBuilderExportedName`;
205
+ var BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME = `${pascalCaseName}LambdaLayerBuilder`;
206
+ var BASE_STACK_VPC_ID_EXPORTED_NAME = `${pascalCaseName}VPCIDExportedName`;
207
+ var BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME = `${pascalCaseName}DefaultSecurityGroupExportedName`;
208
+ var BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet0ExportedName`;
209
+ var BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet1ExportedName`;
210
+ var BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet2ExportedName`;
211
+
212
+ // src/deploy/cloudformation.core.ts
213
+
214
+ var _path = require('path'); var path2 = _interopRequireWildcard(_path); var path6 = _interopRequireWildcard(_path); var path8 = _interopRequireWildcard(_path); var path7 = _interopRequireWildcard(_path); var path9 = _interopRequireWildcard(_path); var path10 = _interopRequireWildcard(_path);
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+ var _clientcloudformation = require('@aws-sdk/client-cloudformation');
227
+
228
+ // src/deploy/addDefaults.cloudformation.ts
229
+ var addDefaultsParametersAndTagsToParams = async (params) => {
230
+ const branchName = await getCurrentBranch();
231
+ const environment = await getEnvironment();
232
+ const packageName = await getPackageName();
233
+ const packageVersion = await getPackageVersion();
234
+ const projectName = await getProjectName();
235
+ return {
236
+ ...params,
237
+ Parameters: [
238
+ ...params.Parameters || [],
239
+ ...environment ? [{ ParameterKey: "Environment", ParameterValue: environment }] : [],
240
+ { ParameterKey: "Project", ParameterValue: projectName }
241
+ ],
242
+ Tags: [
243
+ ...params.Tags || [],
244
+ { Key: "Branch", Value: branchName },
245
+ ...environment ? [{ Key: "Environment", Value: environment }] : [],
246
+ { Key: "Package", Value: packageName },
247
+ { Key: "Project", Value: projectName },
248
+ { Key: "Version", Value: packageVersion }
249
+ ].filter(({ Value }) => {
250
+ return !!Value;
251
+ })
252
+ };
253
+ };
254
+ var addDefaultParametersToTemplate = async (template) => {
255
+ const [environment, projectName] = await Promise.all([
256
+ getEnvironment(),
257
+ getProjectName()
258
+ ]);
259
+ const newParameters = {
260
+ Project: { Default: projectName, Type: "String" }
261
+ };
262
+ if (environment) {
263
+ newParameters.Environment = { Default: environment, Type: "String" };
264
+ }
265
+ template.Parameters = { ...newParameters, ...template.Parameters };
266
+ };
267
+ var addLogGroupToResources = (template) => {
268
+ const { Resources } = template;
269
+ const resourcesEntries = Object.entries(Resources);
270
+ resourcesEntries.forEach(([key, resource]) => {
271
+ if (["AWS::Lambda::Function", "AWS::Serverless::Function"].includes(
272
+ resource.Type
273
+ )) {
274
+ const logGroup = resourcesEntries.find(([, resource2]) => {
275
+ const logGroupNameStr = JSON.stringify(
276
+ _optionalChain([resource2, 'access', _4 => _4.Properties, 'optionalAccess', _5 => _5.LogGroupName, 'optionalAccess', _6 => _6["Fn::Join"]]) || ""
277
+ );
278
+ return logGroupNameStr.includes(key);
279
+ });
280
+ if (!logGroup) {
281
+ Resources[`${key}LogsLogGroup`] = {
282
+ Type: "AWS::Logs::LogGroup",
283
+ DeletionPolicy: "Delete",
284
+ Properties: {
285
+ LogGroupName: { "Fn::Join": ["/", ["/aws/lambda", { Ref: key }]] }
286
+ }
287
+ };
288
+ }
289
+ }
290
+ });
291
+ };
292
+ var addEnvironmentsToLambdaResources = async (template) => {
293
+ const environment = getEnvironment();
294
+ const { Resources } = template;
295
+ const resourcesEntries = Object.entries(Resources);
296
+ resourcesEntries.forEach(([, resource]) => {
297
+ if (resource.Type === "AWS::Lambda::Function") {
298
+ const { Properties } = resource;
299
+ if ((Properties.Description || "").includes("Lambda@Edge")) {
300
+ return;
301
+ }
302
+ if (!environment) {
303
+ return;
304
+ }
305
+ if (!Properties.Environment) {
306
+ Properties.Environment = {};
307
+ }
308
+ if (!Properties.Environment.Variables) {
309
+ Properties.Environment.Variables = {};
310
+ }
311
+ Properties.Environment.Variables.ENVIRONMENT = environment;
312
+ }
313
+ });
314
+ };
315
+ var CRITICAL_RESOURCES_TYPES = [
316
+ "AWS::Cognito::UserPool",
317
+ "AWS::DynamoDB::Table"
318
+ ];
319
+ var addRetainToCriticalResources = async (template) => {
320
+ const environment = getEnvironment();
321
+ Object.entries(template.Resources).forEach(([, resource]) => {
322
+ if (CRITICAL_RESOURCES_TYPES.includes(resource.Type)) {
323
+ if (!resource.DeletionPolicy && environment) {
324
+ resource.DeletionPolicy = "Retain";
325
+ }
326
+ }
327
+ });
328
+ };
329
+ var addAppSyncApiOutputs = async (template) => {
330
+ Object.entries(template.Resources).forEach(([key, resource]) => {
331
+ if (resource.Type === "AWS::AppSync::GraphQLApi") {
332
+ template.Outputs = {
333
+ [key]: {
334
+ Description: `Automatically added by ${NAME}`,
335
+ Value: { "Fn::GetAtt": [key, "GraphQLUrl"] },
336
+ Export: {
337
+ Name: {
338
+ "Fn::Join": [":", [{ Ref: "AWS::StackName" }, "GraphQLApiUrl"]]
339
+ }
340
+ }
341
+ },
342
+ ...template.Outputs
343
+ };
344
+ }
345
+ });
346
+ };
347
+ var addDefaults = async ({
348
+ params,
349
+ template
350
+ }) => {
351
+ const newTemplate = JSON.parse(JSON.stringify(template));
352
+ await addDefaultParametersToTemplate(newTemplate);
353
+ await addLogGroupToResources(newTemplate);
354
+ await addEnvironmentsToLambdaResources(newTemplate);
355
+ await addAppSyncApiOutputs(newTemplate);
356
+ await addRetainToCriticalResources(newTemplate);
357
+ const response = {
358
+ params: await addDefaultsParametersAndTagsToParams(params),
359
+ template: newTemplate
360
+ };
361
+ return response;
362
+ };
363
+
364
+ // src/deploy/s3.ts
365
+
366
+ var _glob = require('glob');
367
+
368
+
369
+ var _mimetypes = require('mime-types'); var _mimetypes2 = _interopRequireDefault(_mimetypes);
370
+
371
+ var logPrefix2 = "s3";
372
+ var s3 = new (0, _awssdk.S3)({ apiVersion: "2006-03-01" });
373
+ var getBucketKeyUrl = ({
374
+ bucket,
375
+ key
376
+ }) => {
377
+ return `https://s3.amazonaws.com/${bucket}/${key}`;
378
+ };
379
+ var uploadFileToS3 = async ({
380
+ bucket,
381
+ contentType,
382
+ file,
383
+ filePath,
384
+ key
385
+ }) => {
386
+ if (!file && !filePath) {
387
+ throw new Error("file or filePath must be defined");
388
+ }
389
+ let params = {
390
+ Bucket: bucket,
391
+ Key: key.split(path2.default.sep).join("/")
392
+ };
393
+ if (file) {
394
+ params.ContentType = contentType;
395
+ params.Body = file;
396
+ } else if (filePath) {
397
+ const readFile = await fs3.default.promises.readFile(filePath);
398
+ params = {
399
+ ...params,
400
+ ContentType: contentType || _mimetypes2.default.contentType(path2.default.extname(filePath)) || void 0
401
+ };
402
+ params.Body = Buffer.from(readFile);
403
+ }
404
+ const { Bucket, Key, VersionId } = await s3.upload(params).promise();
405
+ return {
406
+ bucket: Bucket,
407
+ key: Key,
408
+ versionId: VersionId,
409
+ url: getBucketKeyUrl({ bucket: Bucket, key: Key })
410
+ };
411
+ };
412
+ var getAllFilesInsideADirectory = async ({
413
+ directory
414
+ }) => {
415
+ const allFilesAndDirectories = await _glob.glob.call(void 0, `${directory}/**/*`);
416
+ const allFiles = allFilesAndDirectories.filter((item) => {
417
+ return fs3.default.lstatSync(item).isFile();
418
+ });
419
+ return allFiles;
420
+ };
421
+ var copyRoot404To404Index = async ({ bucket }) => {
422
+ try {
423
+ const root404Exists = await s3.headObject({
424
+ Bucket: bucket,
425
+ Key: "404.html"
426
+ }).promise().catch(() => {
427
+ return false;
428
+ });
429
+ if (root404Exists) {
430
+ await s3.copyObject({
431
+ Bucket: bucket,
432
+ CopySource: `${bucket}/404.html`,
433
+ Key: "404/index.html"
434
+ }).promise();
435
+ }
436
+ } catch (err) {
437
+ _npmlog2.default.error(logPrefix2, `Cannot copy 404.html to 404/index.html`);
438
+ throw err;
439
+ }
440
+ };
441
+ var uploadDirectoryToS3 = async ({
442
+ bucket,
443
+ bucketKey = "",
444
+ directory
445
+ }) => {
446
+ _npmlog2.default.info(
447
+ logPrefix2,
448
+ `Uploading directory ${directory}/ to ${bucket}/${bucketKey}...`
449
+ );
450
+ const allFiles = await getAllFilesInsideADirectory({ directory });
451
+ if (allFiles.length === 0) {
452
+ throw new Error(`Directory ${directory}/ has no files.`);
453
+ }
454
+ const GROUP_MAX_LENGTH = 63;
455
+ const numberOfGroups = Math.ceil(allFiles.length / GROUP_MAX_LENGTH);
456
+ const aoaOfFiles = allFiles.reduce((acc, file, index) => {
457
+ const groupIndex = index % numberOfGroups;
458
+ if (!acc[groupIndex]) {
459
+ acc[groupIndex] = [];
460
+ }
461
+ acc[index % numberOfGroups].push(file);
462
+ return acc;
463
+ }, []);
464
+ for (const [index, groupOfFiles] of aoaOfFiles.entries()) {
465
+ _npmlog2.default.info(logPrefix2, `Uploading group ${index + 1}/${aoaOfFiles.length}...`);
466
+ await Promise.all(
467
+ groupOfFiles.map((file) => {
468
+ return uploadFileToS3({
469
+ bucket,
470
+ key: path2.default.join(bucketKey, path2.default.relative(directory, file)),
471
+ filePath: file
472
+ });
473
+ })
474
+ );
475
+ }
476
+ };
477
+ var emptyS3Directory = async ({
478
+ bucket,
479
+ directory = ""
480
+ }) => {
481
+ _npmlog2.default.info(logPrefix2, `${bucket}/${directory} will be empty`);
482
+ try {
483
+ const { Contents, IsTruncated } = await s3.listObjectsV2({
484
+ Bucket: bucket,
485
+ Prefix: directory
486
+ }).promise();
487
+ if (Contents && Contents.length > 0) {
488
+ const objectsPromises = Contents.filter(({ Key }) => {
489
+ return !!Key;
490
+ }).map(async ({ Key }) => {
491
+ const { Versions = [] } = await s3.listObjectVersions({
492
+ Bucket: bucket,
493
+ Prefix: Key
494
+ }).promise();
495
+ return {
496
+ Key,
497
+ Versions: Versions.map(({ VersionId }) => {
498
+ return VersionId || void 0;
499
+ })
500
+ };
501
+ });
502
+ const objects = await Promise.all(objectsPromises);
503
+ const objectsWithVersionsIds = objects.reduce((acc, { Key, Versions }) => {
504
+ const objectWithVersionsIds = Versions.map((VersionId) => {
505
+ return {
506
+ Key,
507
+ VersionId
508
+ };
509
+ });
510
+ return [...acc, ...objectWithVersionsIds];
511
+ }, []);
512
+ await s3.deleteObjects({
513
+ Bucket: bucket,
514
+ Delete: { Objects: objectsWithVersionsIds }
515
+ }).promise();
516
+ }
517
+ if (IsTruncated) {
518
+ await emptyS3Directory({ bucket, directory });
519
+ }
520
+ _npmlog2.default.info(logPrefix2, `${bucket}/${directory} is empty.`);
521
+ } catch (err) {
522
+ _npmlog2.default.error(logPrefix2, `Cannot empty ${bucket}/${directory}.`);
523
+ throw err;
524
+ }
525
+ };
526
+ var deleteS3Directory = async ({
527
+ bucket,
528
+ directory = ""
529
+ }) => {
530
+ try {
531
+ _npmlog2.default.info(logPrefix2, `${bucket}/${directory} is being deleted...`);
532
+ await emptyS3Directory({ bucket, directory });
533
+ await s3.deleteObject({ Bucket: bucket, Key: directory }).promise();
534
+ _npmlog2.default.info(logPrefix2, `${bucket}/${directory} was deleted.`);
535
+ } catch (error) {
536
+ _npmlog2.default.error(logPrefix2, `Cannot delete ${bucket}/${directory}.`);
537
+ throw error;
538
+ }
539
+ };
540
+
541
+ // src/deploy/baseStack/getBaseStackResource.ts
542
+ var getBaseStackOutput = async (outputKey) => {
543
+ const output = await getStackOutput({
544
+ stackName: BASE_STACK_NAME,
545
+ outputKey
546
+ });
547
+ return output.OutputValue;
548
+ };
549
+ var resourcesKeys = {
550
+ BASE_STACK_BUCKET_LOGICAL_NAME,
551
+ BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME,
552
+ BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME
553
+ };
554
+ var resources = {};
555
+ var getBaseStackResource = async (resource) => {
556
+ if (!resources[resource]) {
557
+ resources[resource] = await getBaseStackOutput(resourcesKeys[resource]);
558
+ }
559
+ return resources[resource];
560
+ };
561
+
562
+ // src/deploy/cloudformation.core.ts
563
+
564
+
565
+ var logPrefix3 = "cloudformation";
566
+ _npmlog2.default.addLevel("event", 1e4, { fg: "yellow" });
567
+ _npmlog2.default.addLevel("output", 1e4, { fg: "blue" });
568
+ var TEMPLATE_BODY_MAX_SIZE = 51200;
569
+ var isTemplateBodyGreaterThanMaxSize = (template) => {
570
+ return Buffer.byteLength(JSON.stringify(template), "utf8") >= TEMPLATE_BODY_MAX_SIZE;
571
+ };
572
+ var uploadTemplateToBaseStackBucket = async ({
573
+ stackName,
574
+ template
575
+ }) => {
576
+ const bucketName = await getBaseStackResource(
577
+ "BASE_STACK_BUCKET_LOGICAL_NAME"
578
+ );
579
+ const { url } = await uploadFileToS3({
580
+ bucket: bucketName,
581
+ contentType: "application/json",
582
+ key: `${BASE_STACK_BUCKET_TEMPLATES_FOLDER}/${stackName}.json`,
583
+ file: Buffer.from(JSON.stringify(template, null, 2))
584
+ });
585
+ return { url };
586
+ };
587
+ var cloudFormationClients = {};
588
+ var cloudformation = () => {
589
+ const cloudFormationClientConfig = {
590
+ apiVersion: "2010-05-15",
591
+ region: getEnvVar("REGION")
592
+ };
593
+ const key = JSON.stringify(cloudFormationClientConfig);
594
+ if (!cloudFormationClients[key]) {
595
+ cloudFormationClients[key] = new (0, _clientcloudformation.CloudFormationClient)({
596
+ apiVersion: "2010-05-15",
597
+ region: getEnvVar("REGION")
598
+ });
599
+ }
600
+ return cloudFormationClients[key];
601
+ };
602
+ var cloudFormationV2 = () => {
603
+ return new _awssdk2.default.CloudFormation({ apiVersion: "2010-05-15" });
604
+ };
605
+ var describeStacks = async ({
606
+ stackName
607
+ } = {}) => {
608
+ const { Stacks } = await cloudformation().send(
609
+ new (0, _clientcloudformation.DescribeStacksCommand)({ StackName: stackName })
610
+ );
611
+ return Stacks;
612
+ };
613
+ var describeStackResource = async (input) => {
614
+ return cloudformation().send(new (0, _clientcloudformation.DescribeStackResourceCommand)(input));
615
+ };
616
+ var doesStackExist = async ({ stackName }) => {
617
+ _npmlog2.default.info(logPrefix3, `Checking if stack ${stackName} already exists...`);
618
+ try {
619
+ await describeStacks({ stackName });
620
+ _npmlog2.default.info(logPrefix3, `Stack ${stackName} already exists.`);
621
+ return true;
622
+ } catch (error) {
623
+ if (error.Code === "ValidationError") {
624
+ _npmlog2.default.info(logPrefix3, `Stack ${stackName} does not exist.`);
625
+ return false;
626
+ }
627
+ throw error;
628
+ }
629
+ };
630
+ var describeStackEvents = async ({
631
+ stackName
632
+ }) => {
633
+ _npmlog2.default.error(logPrefix3, "Stack events:");
634
+ const { StackEvents } = await cloudformation().send(
635
+ new (0, _clientcloudformation.DescribeStackEventsCommand)({ StackName: stackName })
636
+ );
637
+ const events = (StackEvents || []).filter(({ Timestamp }) => {
638
+ return Date.now() - Number(Timestamp) < 10 * 60 * 1e3;
639
+ }).filter(({ ResourceStatusReason }) => {
640
+ return ResourceStatusReason;
641
+ }).reverse();
642
+ events.forEach(({ LogicalResourceId, ResourceStatusReason }) => {
643
+ return _npmlog2.default.event(LogicalResourceId, ResourceStatusReason);
644
+ });
645
+ return events;
646
+ };
647
+ var describeStack = async ({ stackName }) => {
648
+ const stacks = await describeStacks({ stackName });
649
+ if (!stacks) {
650
+ throw new Error(`Stack ${stackName} not found and cannot be described.`);
651
+ }
652
+ return stacks[0];
653
+ };
654
+ var getStackOutput = async ({
655
+ stackName,
656
+ outputKey
657
+ }) => {
658
+ const { Outputs = [] } = await describeStack({ stackName });
659
+ const output = _optionalChain([Outputs, 'optionalAccess', _7 => _7.find, 'call', _8 => _8(({ OutputKey }) => {
660
+ return OutputKey === outputKey;
661
+ })]);
662
+ if (!output) {
663
+ throw new Error(`Output ${outputKey} doesn't exist on ${stackName} stack`);
664
+ }
665
+ return output;
666
+ };
667
+ var saveEnvironmentOutput = async ({
668
+ outputs,
669
+ stackName
670
+ }) => {
671
+ const envFile = { stackName };
672
+ envFile.outputs = outputs.reduce((acc, output) => {
673
+ if (!output.OutputKey || !output) {
674
+ return acc;
675
+ }
676
+ return {
677
+ ...acc,
678
+ [output.OutputKey]: output
679
+ };
680
+ }, {});
681
+ const dotCarlinFolderPath = path2.join(process.cwd(), ".carlin");
682
+ if (!fs3.existsSync(dotCarlinFolderPath)) {
683
+ await fs3.promises.mkdir(dotCarlinFolderPath);
684
+ }
685
+ const filePath = path2.join(dotCarlinFolderPath, `${stackName}.json`);
686
+ await fs3.promises.writeFile(filePath, JSON.stringify(envFile, null, 2));
687
+ };
688
+ var printStackOutputsAfterDeploy = async ({
689
+ stackName
690
+ }) => {
691
+ const {
692
+ EnableTerminationProtection,
693
+ StackName,
694
+ Outputs = []
695
+ } = await describeStack({ stackName });
696
+ await saveEnvironmentOutput({ stackName, outputs: Outputs });
697
+ _npmlog2.default.output("Describe Stack");
698
+ _npmlog2.default.output("StackName", StackName);
699
+ _npmlog2.default.output("EnableTerminationProtection", EnableTerminationProtection);
700
+ Outputs.forEach(({ OutputKey, OutputValue, Description, ExportName }) => {
701
+ _npmlog2.default.output(
702
+ `${OutputKey}`,
703
+ [
704
+ "",
705
+ `OutputKey: ${OutputKey}`,
706
+ `OutputValue: ${OutputValue}`,
707
+ `Description: ${Description}`,
708
+ `ExportName: ${ExportName}`,
709
+ ""
710
+ ].join("\n")
711
+ );
712
+ });
713
+ };
714
+ var deleteStack = async ({ stackName }) => {
715
+ _npmlog2.default.info(logPrefix3, `Deleting stack ${stackName}...`);
716
+ await cloudformation().send(new (0, _clientcloudformation.DeleteStackCommand)({ StackName: stackName }));
717
+ try {
718
+ await cloudFormationV2().waitFor("stackDeleteComplete", { StackName: stackName }).promise();
719
+ } catch (err) {
720
+ _npmlog2.default.error(logPrefix3, `An error occurred when deleting stack ${stackName}.`);
721
+ await describeStackEvents({ stackName });
722
+ throw err;
723
+ }
724
+ _npmlog2.default.info(logPrefix3, `Stack ${stackName} deleted.`);
725
+ };
726
+ var createStack = async ({
727
+ params
728
+ }) => {
729
+ const { StackName: stackName = "" } = params;
730
+ _npmlog2.default.info(logPrefix3, `Creating stack ${stackName}...`);
731
+ await cloudformation().send(new (0, _clientcloudformation.CreateStackCommand)(params));
732
+ try {
733
+ await cloudFormationV2().waitFor("stackCreateComplete", { StackName: stackName }).promise();
734
+ } catch (err) {
735
+ _npmlog2.default.error(logPrefix3, `An error occurred when creating stack ${stackName}.`);
736
+ await describeStackEvents({ stackName });
737
+ await deleteStack({ stackName });
738
+ throw err;
739
+ }
740
+ _npmlog2.default.info(logPrefix3, `Stack ${stackName} was created.`);
741
+ };
742
+ var updateStack = async ({
743
+ params
744
+ }) => {
745
+ const { StackName: stackName = "" } = params;
746
+ _npmlog2.default.info(logPrefix3, `Updating stack ${stackName}...`);
747
+ try {
748
+ await cloudformation().send(new (0, _clientcloudformation.UpdateStackCommand)(params));
749
+ await cloudFormationV2().waitFor("stackUpdateComplete", { StackName: stackName }).promise();
750
+ } catch (error) {
751
+ if (error.message === "No updates are to be performed.") {
752
+ _npmlog2.default.info(logPrefix3, error.message);
753
+ return;
754
+ }
755
+ _npmlog2.default.error(logPrefix3, "An error occurred when updating stack.");
756
+ await describeStackEvents({ stackName });
757
+ throw error;
758
+ }
759
+ _npmlog2.default.info(logPrefix3, `Stack ${stackName} was updated.`);
760
+ };
761
+ var enableTerminationProtection = async ({
762
+ stackName
763
+ }) => {
764
+ _npmlog2.default.info(logPrefix3, `Enabling termination protection...`);
765
+ try {
766
+ await cloudformation().send(
767
+ new (0, _clientcloudformation.UpdateTerminationProtectionCommand)({
768
+ EnableTerminationProtection: true,
769
+ StackName: stackName
770
+ })
771
+ );
772
+ } catch (err) {
773
+ _npmlog2.default.error(
774
+ logPrefix3,
775
+ "An error occurred when enabling termination protection"
776
+ );
777
+ throw err;
778
+ }
779
+ };
780
+ var defaultTemplatePaths = ["ts", "js", "yaml", "yml", "json"].map(
781
+ (extension) => {
782
+ return `src/cloudformation.${extension}`;
783
+ }
784
+ );
785
+ var deploy = async ({
786
+ terminationProtection = false,
787
+ ...paramsAndTemplate
788
+ }) => {
789
+ const { params, template } = await addDefaults(paramsAndTemplate);
790
+ const stackName = params.StackName;
791
+ if (!stackName) {
792
+ throw new Error("StackName is required");
793
+ }
794
+ delete params.TemplateBody;
795
+ delete params.TemplateURL;
796
+ if (isTemplateBodyGreaterThanMaxSize(template)) {
797
+ const { url } = await uploadTemplateToBaseStackBucket({
798
+ stackName,
799
+ template
800
+ });
801
+ params.TemplateURL = url;
802
+ } else {
803
+ params.TemplateBody = JSON.stringify(template);
804
+ }
805
+ params.Capabilities = [
806
+ "CAPABILITY_AUTO_EXPAND",
807
+ "CAPABILITY_IAM",
808
+ "CAPABILITY_NAMED_IAM"
809
+ ];
810
+ if (await doesStackExist({ stackName })) {
811
+ await updateStack({ params });
812
+ } else {
813
+ await createStack({ params });
814
+ }
815
+ if (terminationProtection || !!getEnvironment()) {
816
+ await enableTerminationProtection({ stackName });
817
+ }
818
+ await printStackOutputsAfterDeploy({ stackName });
819
+ return describeStack({ stackName });
820
+ };
821
+ var canDestroyStack = async ({ stackName }) => {
822
+ const { EnableTerminationProtection } = await describeStack({ stackName });
823
+ if (EnableTerminationProtection) {
824
+ return false;
825
+ }
826
+ return true;
827
+ };
828
+ var validateTemplate = async ({
829
+ stackName,
830
+ template
831
+ }) => {
832
+ const validateTemplateCommandInput = {};
833
+ if (isTemplateBodyGreaterThanMaxSize(template)) {
834
+ const { url } = await uploadTemplateToBaseStackBucket({
835
+ stackName,
836
+ template
837
+ });
838
+ validateTemplateCommandInput.TemplateURL = url;
839
+ } else {
840
+ validateTemplateCommandInput.TemplateBody = JSON.stringify(template);
841
+ }
842
+ await cloudformation().send(
843
+ new (0, _clientcloudformation.ValidateTemplateCommand)(validateTemplateCommandInput)
844
+ );
845
+ };
846
+
847
+ // src/deploy/baseStack/getBucketTemplate.ts
848
+ var getBucketTemplate = () => {
849
+ return {
850
+ AWSTemplateFormatVersion: "2010-09-09",
851
+ Resources: {
852
+ [BASE_STACK_BUCKET_LOGICAL_NAME]: {
853
+ Type: "AWS::S3::Bucket",
854
+ DeletionPolicy: "Retain",
855
+ Properties: {
856
+ LifecycleConfiguration: {
857
+ Rules: [
858
+ {
859
+ ExpirationInDays: 1,
860
+ Prefix: BASE_STACK_BUCKET_TEMPLATES_FOLDER,
861
+ Status: "Enabled"
862
+ },
863
+ {
864
+ NoncurrentVersionExpirationInDays: 3,
865
+ Status: "Enabled"
866
+ }
867
+ ]
868
+ },
869
+ /**
870
+ * This is necessary because if we update Lambda code without change
871
+ * CloudFormation template, the Lambda will not be updated.
872
+ */
873
+ VersioningConfiguration: {
874
+ Status: "Enabled"
875
+ }
876
+ }
877
+ }
878
+ },
879
+ Outputs: {
880
+ [BASE_STACK_BUCKET_LOGICAL_NAME]: {
881
+ Value: { Ref: BASE_STACK_BUCKET_LOGICAL_NAME },
882
+ Export: {
883
+ Name: BASE_STACK_BUCKET_NAME_EXPORTED_NAME
884
+ }
885
+ }
886
+ }
887
+ };
888
+ };
889
+
890
+ // src/deploy/baseStack/getLambdaImageBuilderTemplate.ts
891
+
892
+ var getLambdaImageBuilderTemplate = () => {
893
+ const CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2 = "CodeBuildProjectLogsLogGroup";
894
+ const CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2 = "ImageCodeBuildProjectIAMRole";
895
+ return {
896
+ AWSTemplateFormatVersion: "2010-09-09",
897
+ Resources: {
898
+ [CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2]: {
899
+ Type: "AWS::Logs::LogGroup",
900
+ DeletionPolicy: "Delete",
901
+ Properties: {}
902
+ },
903
+ [CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2]: {
904
+ Type: "AWS::IAM::Role",
905
+ Properties: {
906
+ AssumeRolePolicyDocument: {
907
+ Version: "2012-10-17",
908
+ Statement: [
909
+ {
910
+ Effect: "Allow",
911
+ Principal: {
912
+ Service: "codebuild.amazonaws.com"
913
+ },
914
+ Action: "sts:AssumeRole"
915
+ }
916
+ ]
917
+ },
918
+ Path: getIamPath(),
919
+ Policies: [
920
+ {
921
+ PolicyName: `${CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2}Policy`,
922
+ PolicyDocument: {
923
+ Version: "2012-10-17",
924
+ Statement: [
925
+ {
926
+ Effect: "Allow",
927
+ Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
928
+ Resource: "*"
929
+ },
930
+ {
931
+ Effect: "Allow",
932
+ Action: ["ecr:GetAuthorizationToken"],
933
+ Resource: "*"
934
+ },
935
+ {
936
+ Effect: "Allow",
937
+ Action: [
938
+ "ecr:BatchCheckLayerAvailability",
939
+ "ecr:CompleteLayerUpload",
940
+ "ecr:InitiateLayerUpload",
941
+ "ecr:PutImage",
942
+ "ecr:UploadLayerPart"
943
+ ],
944
+ Resource: "*"
945
+ },
946
+ {
947
+ Effect: "Allow",
948
+ Action: "s3:GetObject",
949
+ Resource: [
950
+ {
951
+ "Fn::Sub": [
952
+ // eslint-disable-next-line no-template-curly-in-string
953
+ "arn:aws:s3:::${BucketName}/*",
954
+ {
955
+ BucketName: {
956
+ Ref: BASE_STACK_BUCKET_LOGICAL_NAME
957
+ }
958
+ }
959
+ ]
960
+ }
961
+ ]
962
+ }
963
+ ]
964
+ }
965
+ }
966
+ ]
967
+ }
968
+ },
969
+ [BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME]: {
970
+ Type: "AWS::CodeBuild::Project",
971
+ Properties: {
972
+ Artifacts: {
973
+ Type: "NO_ARTIFACTS"
974
+ },
975
+ Cache: {
976
+ Location: "LOCAL",
977
+ Modes: ["LOCAL_DOCKER_LAYER_CACHE"],
978
+ Type: "LOCAL"
979
+ },
980
+ Description: "Create Lambda image.",
981
+ Environment: {
982
+ ComputeType: "BUILD_GENERAL1_SMALL",
983
+ EnvironmentVariables: [
984
+ {
985
+ Name: "AWS_ACCOUNT_ID",
986
+ Value: { Ref: "AWS::AccountId" }
987
+ },
988
+ {
989
+ Name: "AWS_REGION",
990
+ Value: { Ref: "AWS::Region" }
991
+ },
992
+ {
993
+ Name: "IMAGE_TAG",
994
+ Value: "latest"
995
+ },
996
+ {
997
+ Name: "LAMBDA_EXTERNALS",
998
+ Value: ""
999
+ }
1000
+ ],
1001
+ Image: "aws/codebuild/standard:3.0",
1002
+ ImagePullCredentialsType: "CODEBUILD",
1003
+ PrivilegedMode: true,
1004
+ Type: "LINUX_CONTAINER"
1005
+ },
1006
+ LogsConfig: {
1007
+ CloudWatchLogs: {
1008
+ Status: "ENABLED",
1009
+ GroupName: { Ref: CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2 }
1010
+ }
1011
+ },
1012
+ ServiceRole: {
1013
+ "Fn::GetAtt": [CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2, "Arn"]
1014
+ },
1015
+ Source: {
1016
+ BuildSpec: _jsyaml2.default.dump({
1017
+ version: "0.2",
1018
+ phases: {
1019
+ install: {
1020
+ commands: [
1021
+ "echo install started on `date`",
1022
+ "npm init -y",
1023
+ /**
1024
+ * https://stackoverflow.com/a/51433146/8786986
1025
+ */
1026
+ "npm install --save --package-lock-only --no-package-lock $LAMBDA_EXTERNALS",
1027
+ "ls"
1028
+ ]
1029
+ },
1030
+ pre_build: {
1031
+ commands: [
1032
+ "echo pre_build started on `date`",
1033
+ "$(aws ecr get-login --no-include-email --region $AWS_REGION)"
1034
+ ]
1035
+ },
1036
+ build: {
1037
+ commands: [
1038
+ "echo build started on `date`",
1039
+ "echo Building the repository image...",
1040
+ 'echo "$DOCKERFILE" > Dockerfile',
1041
+ "docker build -t $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile .",
1042
+ "docker tag $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
1043
+ ]
1044
+ },
1045
+ post_build: {
1046
+ commands: [
1047
+ "echo post_build completed on `date`",
1048
+ "echo Pushing the repository image...",
1049
+ "docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
1050
+ ]
1051
+ }
1052
+ }
1053
+ }),
1054
+ Type: "NO_SOURCE"
1055
+ },
1056
+ TimeoutInMinutes: 60
1057
+ }
1058
+ }
1059
+ },
1060
+ Outputs: {
1061
+ [BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME]: {
1062
+ Value: { Ref: BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME },
1063
+ Export: {
1064
+ Name: BASE_STACK_LAMBDA_IMAGE_BUILDER_EXPORTED_NAME
1065
+ }
1066
+ }
1067
+ }
1068
+ };
1069
+ };
1070
+
1071
+ // src/deploy/baseStack/getLambdaLayerBuilderTemplate.ts
1072
+ var CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID = `${BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME}LogsLogGroup`;
1073
+ var CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID = `${BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME}Role`;
1074
+ var getBuildSpec = () => {
1075
+ return `
1076
+ version: 0.2
1077
+ phases:
1078
+ install:
1079
+ runtime-versions:
1080
+ nodejs: 12
1081
+ commands:
1082
+ - npm i --no-bin-links --no-optional --no-package-lock --no-save --no-shrinkwrap $PACKAGE_NAME
1083
+ - mkdir nodejs
1084
+ - mv node_modules nodejs/node_modules
1085
+ artifacts:
1086
+ files:
1087
+ - nodejs/**/*
1088
+ name: $PACKAGE_NAME.zip
1089
+ `.trim();
1090
+ };
1091
+ var getLambdaLayerBuilderTemplate = () => {
1092
+ return {
1093
+ Resources: {
1094
+ [CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID]: {
1095
+ Type: "AWS::IAM::Role",
1096
+ Properties: {
1097
+ AssumeRolePolicyDocument: {
1098
+ Version: "2012-10-17",
1099
+ Statement: [
1100
+ {
1101
+ Effect: "Allow",
1102
+ Principal: {
1103
+ Service: ["codebuild.amazonaws.com"]
1104
+ },
1105
+ Action: ["sts:AssumeRole"]
1106
+ }
1107
+ ]
1108
+ },
1109
+ Path: getIamPath(),
1110
+ Policies: [
1111
+ {
1112
+ PolicyName: `${CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID}Policy`,
1113
+ PolicyDocument: {
1114
+ Version: "2012-10-17",
1115
+ Statement: [
1116
+ {
1117
+ Effect: "Allow",
1118
+ Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
1119
+ Resource: "*"
1120
+ },
1121
+ {
1122
+ Effect: "Allow",
1123
+ Action: ["s3:*"],
1124
+ Resource: [
1125
+ {
1126
+ "Fn::Sub": [
1127
+ // eslint-disable-next-line no-template-curly-in-string
1128
+ "arn:aws:s3:::${BucketName}",
1129
+ {
1130
+ BucketName: {
1131
+ Ref: BASE_STACK_BUCKET_LOGICAL_NAME
1132
+ }
1133
+ }
1134
+ ]
1135
+ },
1136
+ {
1137
+ "Fn::Sub": [
1138
+ // eslint-disable-next-line no-template-curly-in-string
1139
+ "arn:aws:s3:::${BucketName}/*",
1140
+ {
1141
+ BucketName: {
1142
+ Ref: BASE_STACK_BUCKET_LOGICAL_NAME
1143
+ }
1144
+ }
1145
+ ]
1146
+ }
1147
+ ]
1148
+ }
1149
+ ]
1150
+ }
1151
+ }
1152
+ ]
1153
+ }
1154
+ },
1155
+ [CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID]: {
1156
+ Type: "AWS::Logs::LogGroup",
1157
+ DeletionPolicy: "Delete",
1158
+ Properties: {}
1159
+ },
1160
+ [BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME]: {
1161
+ Type: "AWS::CodeBuild::Project",
1162
+ Properties: {
1163
+ Artifacts: {
1164
+ Location: { Ref: BASE_STACK_BUCKET_LOGICAL_NAME },
1165
+ NamespaceType: "NONE",
1166
+ OverrideArtifactName: true,
1167
+ Packaging: "ZIP",
1168
+ Path: "lambda-layers/packages",
1169
+ Type: "S3"
1170
+ },
1171
+ Environment: {
1172
+ ComputeType: "BUILD_GENERAL1_SMALL",
1173
+ Image: "aws/codebuild/standard:3.0",
1174
+ Type: "LINUX_CONTAINER"
1175
+ },
1176
+ LogsConfig: {
1177
+ CloudWatchLogs: {
1178
+ GroupName: {
1179
+ Ref: `${CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID}`
1180
+ },
1181
+ Status: "ENABLED"
1182
+ }
1183
+ },
1184
+ ServiceRole: {
1185
+ "Fn::GetAtt": `${CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID}.Arn`
1186
+ },
1187
+ Source: {
1188
+ BuildSpec: getBuildSpec(),
1189
+ Type: "NO_SOURCE"
1190
+ }
1191
+ }
1192
+ }
1193
+ },
1194
+ Outputs: {
1195
+ [BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME]: {
1196
+ Value: { Ref: BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME }
1197
+ }
1198
+ }
1199
+ };
1200
+ };
1201
+
1202
+ // src/deploy/baseStack/getVpcTemplate.ts
1203
+
1204
+ var getVpcTemplate = () => {
1205
+ const vpcName = `${_changecase.pascalCase.call(void 0, NAME)}VPC`;
1206
+ const EC2_INTERNET_GATEWAY_LOGICAL_ID = "EC2InternetGateway";
1207
+ const EC2_ROUTE_TABLE_LOGICAL_ID = "EC2RouteTable";
1208
+ const EC2_VPC_LOGICAL_ID = "EC2VCP";
1209
+ const template = {
1210
+ AWSTemplateFormatVersion: "2010-09-09",
1211
+ Mappings: {
1212
+ CidrMappings: {
1213
+ VPC: {
1214
+ CIDR: "10.0.0.0/16"
1215
+ }
1216
+ }
1217
+ },
1218
+ Resources: {
1219
+ [EC2_VPC_LOGICAL_ID]: {
1220
+ Type: "AWS::EC2::VPC",
1221
+ Properties: {
1222
+ CidrBlock: {
1223
+ "Fn::FindInMap": ["CidrMappings", "VPC", "CIDR"]
1224
+ },
1225
+ EnableDnsHostnames: true,
1226
+ EnableDnsSupport: true,
1227
+ Tags: [
1228
+ {
1229
+ Key: "Name",
1230
+ Value: vpcName
1231
+ }
1232
+ ]
1233
+ }
1234
+ },
1235
+ [EC2_INTERNET_GATEWAY_LOGICAL_ID]: {
1236
+ Type: "AWS::EC2::InternetGateway",
1237
+ Properties: {}
1238
+ },
1239
+ EC2VPCGatewayAttachment: {
1240
+ Type: "AWS::EC2::VPCGatewayAttachment",
1241
+ Properties: {
1242
+ InternetGatewayId: {
1243
+ Ref: EC2_INTERNET_GATEWAY_LOGICAL_ID
1244
+ },
1245
+ VpcId: {
1246
+ Ref: EC2_VPC_LOGICAL_ID
1247
+ }
1248
+ }
1249
+ },
1250
+ [EC2_ROUTE_TABLE_LOGICAL_ID]: {
1251
+ Type: "AWS::EC2::RouteTable",
1252
+ Properties: {
1253
+ Tags: [
1254
+ {
1255
+ Key: "Name",
1256
+ Value: {
1257
+ "Fn::Join": [" ", [vpcName, "-", EC2_ROUTE_TABLE_LOGICAL_ID]]
1258
+ }
1259
+ }
1260
+ ],
1261
+ VpcId: {
1262
+ Ref: EC2_VPC_LOGICAL_ID
1263
+ }
1264
+ }
1265
+ },
1266
+ EC2Route: {
1267
+ Type: "AWS::EC2::Route",
1268
+ Properties: {
1269
+ DestinationCidrBlock: "0.0.0.0/0",
1270
+ GatewayId: {
1271
+ Ref: EC2_INTERNET_GATEWAY_LOGICAL_ID
1272
+ },
1273
+ RouteTableId: {
1274
+ Ref: EC2_ROUTE_TABLE_LOGICAL_ID
1275
+ }
1276
+ }
1277
+ }
1278
+ },
1279
+ Outputs: {
1280
+ VPCId: {
1281
+ Value: {
1282
+ Ref: EC2_VPC_LOGICAL_ID
1283
+ },
1284
+ Export: {
1285
+ Name: BASE_STACK_VPC_ID_EXPORTED_NAME
1286
+ }
1287
+ },
1288
+ VPCDefaultSecurityGroup: {
1289
+ Value: {
1290
+ "Fn::GetAtt": [EC2_VPC_LOGICAL_ID, "DefaultSecurityGroup"]
1291
+ },
1292
+ Export: {
1293
+ Name: BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME
1294
+ }
1295
+ }
1296
+ }
1297
+ };
1298
+ [
1299
+ BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME,
1300
+ BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME,
1301
+ BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME
1302
+ ].forEach((publicSubnetExportedName, index) => {
1303
+ const publicSubnetLogicalId = `PublicSubnet${index}EC2Subnet`;
1304
+ const publicSubnetCidrMappings = `PublicSubnet${index}`;
1305
+ template.Mappings.CidrMappings[publicSubnetCidrMappings] = {
1306
+ CIDR: `10.0.${index}.0/24`
1307
+ };
1308
+ template.Resources[publicSubnetLogicalId] = {
1309
+ Type: "AWS::EC2::Subnet",
1310
+ Properties: {
1311
+ AvailabilityZone: {
1312
+ "Fn::Select": [
1313
+ index,
1314
+ {
1315
+ "Fn::GetAZs": {
1316
+ Ref: "AWS::Region"
1317
+ }
1318
+ }
1319
+ ]
1320
+ },
1321
+ CidrBlock: {
1322
+ "Fn::FindInMap": ["CidrMappings", publicSubnetCidrMappings, "CIDR"]
1323
+ },
1324
+ MapPublicIpOnLaunch: true,
1325
+ Tags: [
1326
+ {
1327
+ Key: "Name",
1328
+ Value: {
1329
+ "Fn::Join": [
1330
+ " ",
1331
+ [EC2_VPC_LOGICAL_ID, "-", publicSubnetLogicalId]
1332
+ ]
1333
+ }
1334
+ }
1335
+ ],
1336
+ VpcId: {
1337
+ Ref: EC2_VPC_LOGICAL_ID
1338
+ }
1339
+ }
1340
+ };
1341
+ template.Resources[`PublicSubnet${index}EC2SubnetRouteTableAssociation`] = {
1342
+ Type: "AWS::EC2::SubnetRouteTableAssociation",
1343
+ Properties: {
1344
+ RouteTableId: {
1345
+ Ref: EC2_ROUTE_TABLE_LOGICAL_ID
1346
+ },
1347
+ SubnetId: {
1348
+ Ref: publicSubnetLogicalId
1349
+ }
1350
+ }
1351
+ };
1352
+ if (!template.Outputs) {
1353
+ template.Outputs = {};
1354
+ }
1355
+ template.Outputs[publicSubnetLogicalId] = {
1356
+ Value: {
1357
+ Ref: publicSubnetLogicalId
1358
+ },
1359
+ Export: {
1360
+ Name: publicSubnetExportedName
1361
+ }
1362
+ };
1363
+ });
1364
+ return template;
1365
+ };
1366
+
1367
+ // src/deploy/stackName.ts
1368
+
1369
+ var setPreDefinedStackName = (stackName) => {
1370
+ setEnvVar("STACK_NAME", stackName);
1371
+ };
1372
+ var STACK_NAME_MAX_LENGTH = 100;
1373
+ var limitStackName = (stackName) => {
1374
+ return `${stackName}`.substring(0, STACK_NAME_MAX_LENGTH);
1375
+ };
1376
+ var getStackName = async () => {
1377
+ if (getEnvVar("STACK_NAME")) {
1378
+ return getEnvVar("STACK_NAME");
1379
+ }
1380
+ const [currentBranch, environment, packageName] = await Promise.all([
1381
+ getCurrentBranch(),
1382
+ getEnvironment(),
1383
+ getPackageName()
1384
+ ]);
1385
+ const firstName = packageName ? _changecase.pascalCase.call(void 0, packageName) : `Stack-${Math.round(Math.random() * 1e5)}`;
1386
+ const secondName = (() => {
1387
+ if (environment) {
1388
+ return environment;
1389
+ }
1390
+ if (currentBranch) {
1391
+ return _changecase.paramCase.call(void 0, currentBranch);
1392
+ }
1393
+ return void 0;
1394
+ })();
1395
+ const name = [firstName, secondName].filter((word) => {
1396
+ return !!word;
1397
+ }).join("-");
1398
+ return limitStackName(name);
1399
+ };
1400
+
1401
+ // src/deploy/utils.ts
1402
+
1403
+ var deployErrorLogs = ({
1404
+ error,
1405
+ logPrefix: logPrefix22
1406
+ }) => {
1407
+ _npmlog2.default.error(logPrefix22, `An error occurred. Cannot deploy ${logPrefix22}.`);
1408
+ _npmlog2.default.error(logPrefix22, "Error message: %j", _optionalChain([error, 'optionalAccess', _9 => _9.message]));
1409
+ };
1410
+ var handleDeployError = ({
1411
+ error,
1412
+ logPrefix: logPrefix22
1413
+ }) => {
1414
+ deployErrorLogs({ error, logPrefix: logPrefix22 });
1415
+ process.exit(1);
1416
+ };
1417
+ var handleDeployInitialization = async ({
1418
+ logPrefix: logPrefix22,
1419
+ stackName: preDefinedStackName
1420
+ }) => {
1421
+ _npmlog2.default.info(logPrefix22, `Starting deploy ${logPrefix22}...`);
1422
+ if (preDefinedStackName) {
1423
+ setPreDefinedStackName(preDefinedStackName);
1424
+ }
1425
+ const stackName = await getStackName();
1426
+ _npmlog2.default.info(logPrefix22, `stackName: ${stackName}`);
1427
+ return { stackName };
1428
+ };
1429
+
1430
+ // src/deploy/baseStack/deployBaseStack.ts
1431
+ var _deepmerge = require('deepmerge'); var _deepmerge2 = _interopRequireDefault(_deepmerge);
1432
+ var logPrefix4 = "base-stack";
1433
+ var baseStackTemplate = _deepmerge2.default.all([
1434
+ getBucketTemplate(),
1435
+ getLambdaImageBuilderTemplate(),
1436
+ getLambdaLayerBuilderTemplate(),
1437
+ getVpcTemplate()
1438
+ ]);
1439
+ var deployBaseStack = async () => {
1440
+ try {
1441
+ const { stackName } = await handleDeployInitialization({
1442
+ logPrefix: logPrefix4,
1443
+ stackName: BASE_STACK_NAME
1444
+ });
1445
+ await deploy({
1446
+ template: baseStackTemplate,
1447
+ params: { StackName: stackName },
1448
+ terminationProtection: true
1449
+ });
1450
+ } catch (error) {
1451
+ handleDeployError({ error, logPrefix: logPrefix4 });
1452
+ }
1453
+ };
1454
+
1455
+ // src/deploy/baseStack/command.ts
1456
+ var deployBaseStackCommand = {
1457
+ command: "base-stack",
1458
+ describe: "Create base resources.",
1459
+ handler: deployBaseStack
1460
+ };
1461
+
1462
+ // src/deploy/cicd/deployCicd.ts
1463
+
1464
+
1465
+
1466
+ // src/deploy/cicd/command.options.ts
1467
+
1468
+ var _helpers = require('yargs/helpers');
1469
+
1470
+ // src/deploy/cicd/config.ts
1471
+ var ECS_TASK_DEFAULT_CPU = "2048";
1472
+ var ECS_TASK_DEFAULT_MEMORY = "4096";
1473
+ var PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME = `PipelineRunECSTasksStage`;
1474
+ var PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME = `PipelineRunECSTasksApproval`;
1475
+
1476
+ // src/deploy/cicd/pipelines.ts
1477
+ var pipelines = ["pr", "main", "tag"];
1478
+
1479
+ // src/deploy/cicd/command.options.ts
1480
+ var _yargs = require('yargs'); var _yargs2 = _interopRequireDefault(_yargs);
1481
+ var options = {
1482
+ cpu: {
1483
+ type: "string"
1484
+ },
1485
+ memory: {
1486
+ type: "string"
1487
+ },
1488
+ pipelines: {
1489
+ choices: pipelines,
1490
+ coerce: (values) => {
1491
+ return values.map((value) => {
1492
+ return _changecase.camelCase.call(void 0, value);
1493
+ });
1494
+ },
1495
+ default: [],
1496
+ description: "Pipelines that will be implemented with the CICD stack.",
1497
+ type: "array"
1498
+ },
1499
+ "update-repository": {
1500
+ alias: ["ur"],
1501
+ description: "Determine if the repository image will be updated.",
1502
+ default: true,
1503
+ type: "boolean"
1504
+ },
1505
+ "ssh-key": {
1506
+ demandOption: true,
1507
+ type: "string"
1508
+ },
1509
+ "ssh-url": {
1510
+ demandOption: true,
1511
+ type: "string"
1512
+ },
1513
+ "slack-webhook-url": {
1514
+ type: "string"
1515
+ },
1516
+ /**
1517
+ * This option has the format:
1518
+ *
1519
+ * ```ts
1520
+ * Array<{
1521
+ * name: string,
1522
+ * value: string,
1523
+ * }>
1524
+ * ```
1525
+ */
1526
+ "task-environment": {
1527
+ alias: ["te"],
1528
+ default: [],
1529
+ describe: "A list of environment variables that will be passed to the ECS container task.",
1530
+ type: "array"
1531
+ }
1532
+ };
1533
+ var getCicdConfig = () => {
1534
+ const { parsed } = _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).config();
1535
+ if (!parsed) {
1536
+ return false;
1537
+ }
1538
+ const { argv } = parsed;
1539
+ const config = Object.keys(options).reduce((acc, key) => {
1540
+ const value = argv[key];
1541
+ if (value) {
1542
+ acc[key] = value;
1543
+ }
1544
+ return acc;
1545
+ }, {});
1546
+ return config;
1547
+ };
1548
+
1549
+ // src/deploy/cicd/getTriggerPipelineObjectKey.ts
1550
+ var getTriggerPipelinesObjectKey = ({
1551
+ prefix,
1552
+ pipeline
1553
+ }) => {
1554
+ return `${prefix}/${pipeline}.zip`;
1555
+ };
1556
+
1557
+ // src/deploy/cicd/cicd.template.ts
1558
+
1559
+
1560
+ var API_LOGICAL_ID = "ApiV1ServerlessApi";
1561
+ var CODE_BUILD_PROJECT_LOGS_LOGICAL_ID = "RepositoryImageCodeBuildProjectLogsLogGroup";
1562
+ var CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID = "RepositoryImageCodeBuildProjectIAMRole";
1563
+ var ECR_REPOSITORY_LOGICAL_ID = "RepositoryECRRepository";
1564
+ var FUNCTION_IAM_ROLE_LOGICAL_ID = "ApiV1ServerlessFunctionIAMRole";
1565
+ var ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID = "EcsTaskReportHandler";
1566
+ var PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME = "REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME";
1567
+ var REPOSITORY_ECS_TASK_CONTAINER_NAME = "RepositoryECSTaskContainerName";
1568
+ var REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID = "RepositoryECSTaskDefinition";
1569
+ var REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID = "RepositoryImageCodeBuildProject";
1570
+ var REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID = "RepositoryTasksECSCluster";
1571
+ var REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID = "RepositoryTasksECSClusterLogsLogGroup";
1572
+ var REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID = "RepositoryTasksECSTaskDefinitionExecutionRoleIAMRole";
1573
+ var REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID = "RepositoryTasksECSTaskDefinitionTaskRoleIAMRole";
1574
+ var PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID = "PipelinesArtifactStoreS3Bucket";
1575
+ var PIPELINES_ROLE_LOGICAL_ID = "PipelinesMainIAMRole";
1576
+ var PIPELINES_MAIN_LOGICAL_ID = "PipelinesMainCodePipeline";
1577
+ var PIPELINES_TAG_LOGICAL_ID = "PipelinesTagCodePipeline";
1578
+ var PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID = "PipelinesHandlerLambdaFunction";
1579
+ var IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID = "ImageUpdaterScheduleServerlessFunction";
1580
+ var getRepositoryImageBuilder = () => {
1581
+ const nodeRuntimeNumber = NODE_RUNTIME.replace("nodejs", "").replace(
1582
+ ".x",
1583
+ ""
1584
+ );
1585
+ return {
1586
+ Type: "AWS::CodeBuild::Project",
1587
+ Properties: {
1588
+ Artifacts: {
1589
+ Type: "NO_ARTIFACTS"
1590
+ },
1591
+ Cache: {
1592
+ Location: "LOCAL",
1593
+ Modes: ["LOCAL_DOCKER_LAYER_CACHE"],
1594
+ Type: "LOCAL"
1595
+ },
1596
+ Description: "Create repository image.",
1597
+ Environment: {
1598
+ ComputeType: "BUILD_GENERAL1_SMALL",
1599
+ EnvironmentVariables: [
1600
+ {
1601
+ Name: "AWS_ACCOUNT_ID",
1602
+ Value: { Ref: "AWS::AccountId" }
1603
+ },
1604
+ {
1605
+ Name: "AWS_REGION",
1606
+ Value: { Ref: "AWS::Region" }
1607
+ },
1608
+ {
1609
+ Name: "DOCKERFILE",
1610
+ Value: {
1611
+ "Fn::Sub": [
1612
+ "FROM public.ecr.aws/ubuntu/ubuntu:20.04_stable",
1613
+ // https://stackoverflow.com/a/59693182/8786986
1614
+ "ENV DEBIAN_FRONTEND noninteractive",
1615
+ // Make sure apt is up to date
1616
+ "RUN apt-get update --fix-missing",
1617
+ "RUN apt-get install -y curl",
1618
+ "RUN apt-get install -y git",
1619
+ "RUN apt-get install -y jq",
1620
+ // Install Node.js
1621
+ `RUN curl -fsSL https://deb.nodesource.com/setup_${nodeRuntimeNumber}.x | bash -`,
1622
+ "RUN apt-get install -y nodejs",
1623
+ // Clean cache
1624
+ "RUN apt-get clean",
1625
+ // Install Yarn
1626
+ "RUN npm install -g yarn",
1627
+ // Install carlin CLI
1628
+ "RUN yarn global add carlin",
1629
+ // Configure git
1630
+ "RUN git config --global user.name carlin",
1631
+ "RUN git config --global user.email carlin@ttoss.dev",
1632
+ "RUN mkdir /root/.ssh/",
1633
+ "COPY ./id_rsa /root/.ssh/id_rsa",
1634
+ "RUN chmod 600 /root/.ssh/id_rsa",
1635
+ // Make sure your domain is accepted
1636
+ "RUN touch /root/.ssh/known_hosts",
1637
+ "RUN ssh-keyscan github.com >> /root/.ssh/known_hosts",
1638
+ // Copy repository
1639
+ "COPY . /home",
1640
+ // Go to repository directory
1641
+ "WORKDIR /home/repository",
1642
+ // Set Yarn cache
1643
+ "RUN mkdir -p /home/yarn-cache",
1644
+ "RUN yarn config set cache-folder /home/yarn-cache",
1645
+ "RUN yarn install",
1646
+ // Used in case of yarn.lock is modified.
1647
+ "RUN git checkout -- yarn.lock"
1648
+ ].join("\n")
1649
+ }
1650
+ },
1651
+ {
1652
+ Name: "IMAGE_TAG",
1653
+ Value: "latest"
1654
+ },
1655
+ {
1656
+ Name: "REPOSITORY_ECR_REPOSITORY",
1657
+ Value: { Ref: ECR_REPOSITORY_LOGICAL_ID }
1658
+ },
1659
+ {
1660
+ Name: "SSH_KEY",
1661
+ Value: { Ref: "SSHKey" }
1662
+ },
1663
+ {
1664
+ Name: "SSH_URL",
1665
+ Value: { Ref: "SSHUrl" }
1666
+ }
1667
+ ],
1668
+ Image: "aws/codebuild/standard:3.0",
1669
+ ImagePullCredentialsType: "CODEBUILD",
1670
+ /**
1671
+ * Enables running the Docker daemon inside a Docker container. Set to
1672
+ * true only if the build project is used to build Docker images.
1673
+ * Otherwise, a build that attempts to interact with the Docker daemon
1674
+ * fails. The default setting is false."
1675
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-environment.html#cfn-codebuild-project-environment-privilegedmode
1676
+ */
1677
+ PrivilegedMode: true,
1678
+ Type: "LINUX_CONTAINER"
1679
+ },
1680
+ LogsConfig: {
1681
+ CloudWatchLogs: {
1682
+ Status: "ENABLED",
1683
+ GroupName: { Ref: CODE_BUILD_PROJECT_LOGS_LOGICAL_ID }
1684
+ }
1685
+ },
1686
+ ServiceRole: {
1687
+ "Fn::GetAtt": [CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID, "Arn"]
1688
+ },
1689
+ Source: {
1690
+ BuildSpec: _jsyaml2.default.dump({
1691
+ version: "0.2",
1692
+ phases: {
1693
+ install: {
1694
+ commands: [
1695
+ "echo install started on `date`",
1696
+ `echo "$SSH_KEY" > ~/.ssh/id_rsa`,
1697
+ "chmod 600 ~/.ssh/id_rsa",
1698
+ "rm -rf repository",
1699
+ "git clone $SSH_URL repository",
1700
+ "cd repository",
1701
+ "ls"
1702
+ ]
1703
+ },
1704
+ pre_build: {
1705
+ commands: ["echo pre_build started on `date`"]
1706
+ },
1707
+ build: {
1708
+ commands: [
1709
+ "echo build started on `date`",
1710
+ "$(aws ecr get-login --no-include-email --region $AWS_REGION)",
1711
+ "echo Building the repository image...",
1712
+ "cd ../",
1713
+ "cp ~/.ssh/id_rsa .",
1714
+ 'echo "$DOCKERFILE" > Dockerfile',
1715
+ "cat Dockerfile",
1716
+ "docker build -t $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile .",
1717
+ "docker tag $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG",
1718
+ "echo Pushing the repository image...",
1719
+ "docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
1720
+ ]
1721
+ },
1722
+ post_build: {
1723
+ commands: ["echo post_build completed on `date`"]
1724
+ }
1725
+ }
1726
+ }),
1727
+ Type: "NO_SOURCE"
1728
+ },
1729
+ TimeoutInMinutes: 15
1730
+ }
1731
+ };
1732
+ };
1733
+ var triggerPipelinesObjectKeyPrefix = [
1734
+ "cicd",
1735
+ "pipelines",
1736
+ "triggers",
1737
+ getProjectName()
1738
+ ].join("/");
1739
+ var getCicdTemplate = ({
1740
+ pipelines: pipelines2 = [],
1741
+ cpu = ECS_TASK_DEFAULT_CPU,
1742
+ memory = ECS_TASK_DEFAULT_MEMORY,
1743
+ s3: s32,
1744
+ slackWebhookUrl,
1745
+ taskEnvironment = []
1746
+ }) => {
1747
+ const resources2 = {};
1748
+ const executeEcsTaskVariables = {
1749
+ ECS_CLUSTER_ARN: {
1750
+ "Fn::GetAtt": [REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID, "Arn"]
1751
+ },
1752
+ ECS_CONTAINER_NAME: REPOSITORY_ECS_TASK_CONTAINER_NAME,
1753
+ ECS_TASK_DEFINITION: {
1754
+ Ref: REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID
1755
+ },
1756
+ VPC_SECURITY_GROUP: {
1757
+ "Fn::ImportValue": BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME
1758
+ },
1759
+ VPC_PUBLIC_SUBNET_0: {
1760
+ "Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME
1761
+ },
1762
+ VPC_PUBLIC_SUBNET_1: {
1763
+ "Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME
1764
+ },
1765
+ VPC_PUBLIC_SUBNET_2: {
1766
+ "Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME
1767
+ },
1768
+ ECS_TASK_REPORT_HANDLER_NAME: {
1769
+ Ref: ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID
1770
+ }
1771
+ };
1772
+ const getEcrRepositoryResource = () => {
1773
+ return {
1774
+ Type: "AWS::ECR::Repository",
1775
+ Properties: {
1776
+ LifecyclePolicy: {
1777
+ LifecyclePolicyText: JSON.stringify(
1778
+ {
1779
+ rules: [
1780
+ {
1781
+ rulePriority: 1,
1782
+ description: "Only keep the latest image",
1783
+ selection: {
1784
+ tagStatus: "any",
1785
+ countType: "imageCountMoreThan",
1786
+ countNumber: 1
1787
+ },
1788
+ action: {
1789
+ type: "expire"
1790
+ }
1791
+ }
1792
+ ]
1793
+ },
1794
+ null,
1795
+ 2
1796
+ )
1797
+ }
1798
+ }
1799
+ };
1800
+ };
1801
+ resources2[ECR_REPOSITORY_LOGICAL_ID] = getEcrRepositoryResource();
1802
+ const commonFunctionProperties = {
1803
+ CodeUri: {
1804
+ Bucket: s32.bucket,
1805
+ Key: s32.key,
1806
+ Version: s32.versionId
1807
+ },
1808
+ Role: {
1809
+ "Fn::GetAtt": [FUNCTION_IAM_ROLE_LOGICAL_ID, "Arn"]
1810
+ },
1811
+ Runtime: NODE_RUNTIME,
1812
+ Timeout: 60
1813
+ };
1814
+ (() => {
1815
+ resources2[CODE_BUILD_PROJECT_LOGS_LOGICAL_ID] = {
1816
+ Type: "AWS::Logs::LogGroup",
1817
+ DeletionPolicy: "Delete",
1818
+ Properties: {}
1819
+ };
1820
+ resources2[CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID] = {
1821
+ Type: "AWS::IAM::Role",
1822
+ Properties: {
1823
+ AssumeRolePolicyDocument: {
1824
+ Version: "2012-10-17",
1825
+ Statement: [
1826
+ {
1827
+ Effect: "Allow",
1828
+ Principal: {
1829
+ Service: "codebuild.amazonaws.com"
1830
+ },
1831
+ Action: "sts:AssumeRole"
1832
+ }
1833
+ ]
1834
+ },
1835
+ Path: getIamPath(),
1836
+ Policies: [
1837
+ {
1838
+ PolicyName: `${CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID}Policy`,
1839
+ PolicyDocument: {
1840
+ Version: "2012-10-17",
1841
+ Statement: [
1842
+ {
1843
+ Effect: "Allow",
1844
+ Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
1845
+ Resource: "*"
1846
+ },
1847
+ {
1848
+ Effect: "Allow",
1849
+ Action: ["ecr:GetAuthorizationToken"],
1850
+ Resource: "*"
1851
+ },
1852
+ {
1853
+ Effect: "Allow",
1854
+ Action: [
1855
+ "ecr:BatchCheckLayerAvailability",
1856
+ "ecr:CompleteLayerUpload",
1857
+ "ecr:InitiateLayerUpload",
1858
+ "ecr:PutImage",
1859
+ "ecr:UploadLayerPart"
1860
+ ],
1861
+ Resource: {
1862
+ "Fn::GetAtt": [ECR_REPOSITORY_LOGICAL_ID, "Arn"]
1863
+ }
1864
+ }
1865
+ ]
1866
+ }
1867
+ }
1868
+ ]
1869
+ }
1870
+ };
1871
+ resources2[REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID] = getRepositoryImageBuilder();
1872
+ const cicdConfig = {
1873
+ ...getCicdConfig(),
1874
+ "ssh-key": "/root/.ssh/id_rsa",
1875
+ environment: getEnvironment()
1876
+ };
1877
+ resources2[IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID] = {
1878
+ Type: "AWS::Serverless::Function",
1879
+ Properties: {
1880
+ ...commonFunctionProperties,
1881
+ Events: {
1882
+ Schedule: {
1883
+ Type: "Schedule",
1884
+ Properties: {
1885
+ Schedule: "rate(7 days)"
1886
+ }
1887
+ }
1888
+ },
1889
+ Environment: {
1890
+ Variables: {
1891
+ [PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME]: {
1892
+ Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
1893
+ },
1894
+ CICD_CONFIG: JSON.stringify(cicdConfig),
1895
+ ...executeEcsTaskVariables
1896
+ }
1897
+ },
1898
+ Handler: "index.imageUpdaterScheduleHandler"
1899
+ }
1900
+ };
1901
+ })();
1902
+ const createApiResources = () => {
1903
+ resources2[API_LOGICAL_ID] = {
1904
+ Type: "AWS::Serverless::Api",
1905
+ Properties: {
1906
+ Auth: {
1907
+ ApiKeyRequired: false
1908
+ },
1909
+ StageName: "v1"
1910
+ }
1911
+ };
1912
+ resources2[FUNCTION_IAM_ROLE_LOGICAL_ID] = {
1913
+ Type: "AWS::IAM::Role",
1914
+ Properties: {
1915
+ AssumeRolePolicyDocument: {
1916
+ Version: "2012-10-17",
1917
+ Statement: [
1918
+ {
1919
+ Effect: "Allow",
1920
+ Principal: {
1921
+ Service: "lambda.amazonaws.com"
1922
+ },
1923
+ Action: ["sts:AssumeRole"]
1924
+ }
1925
+ ]
1926
+ },
1927
+ ManagedPolicyArns: [
1928
+ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
1929
+ ],
1930
+ Path: getIamPath(),
1931
+ Policies: [
1932
+ {
1933
+ PolicyName: `${FUNCTION_IAM_ROLE_LOGICAL_ID}Policy`,
1934
+ PolicyDocument: {
1935
+ Version: "2012-10-17",
1936
+ Statement: [
1937
+ {
1938
+ Effect: "Allow",
1939
+ Action: ["codebuild:StartBuild"],
1940
+ Resource: {
1941
+ "Fn::GetAtt": [
1942
+ REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID,
1943
+ "Arn"
1944
+ ]
1945
+ }
1946
+ },
1947
+ {
1948
+ Effect: "Allow",
1949
+ Action: ["iam:PassRole"],
1950
+ Resource: [
1951
+ {
1952
+ "Fn::GetAtt": [
1953
+ REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID,
1954
+ "Arn"
1955
+ ]
1956
+ },
1957
+ {
1958
+ "Fn::GetAtt": [
1959
+ REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID,
1960
+ "Arn"
1961
+ ]
1962
+ }
1963
+ ]
1964
+ },
1965
+ {
1966
+ Effect: "Allow",
1967
+ Action: ["ecs:DescribeTasks"],
1968
+ Resource: "*"
1969
+ },
1970
+ {
1971
+ Effect: "Allow",
1972
+ Action: ["ecs:RunTask"],
1973
+ Resource: [
1974
+ {
1975
+ Ref: REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID
1976
+ }
1977
+ ]
1978
+ },
1979
+ {
1980
+ Action: [
1981
+ "codepipeline:PutApprovalResult",
1982
+ "codepipeline:GetJobDetails",
1983
+ "codepipeline:GetPipelineState",
1984
+ "codepipeline:PutJobSuccessResult",
1985
+ "codepipeline:PutJobFailureResult"
1986
+ ],
1987
+ Effect: "Allow",
1988
+ Resource: "*"
1989
+ },
1990
+ {
1991
+ Action: "s3:*",
1992
+ Effect: "Allow",
1993
+ Resource: {
1994
+ "Fn::Sub": [
1995
+ `arn:aws:s3:::\${BucketName}/${triggerPipelinesObjectKeyPrefix}*`,
1996
+ {
1997
+ BucketName: {
1998
+ "Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
1999
+ }
2000
+ }
2001
+ ]
2002
+ }
2003
+ }
2004
+ ]
2005
+ }
2006
+ }
2007
+ ]
2008
+ }
2009
+ };
2010
+ resources2[ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID] = {
2011
+ Type: "AWS::Serverless::Function",
2012
+ Properties: {
2013
+ ...commonFunctionProperties,
2014
+ Environment: {
2015
+ Variables: {
2016
+ ECS_TASK_LOGS_LOG_GROUP: {
2017
+ Ref: REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID
2018
+ },
2019
+ ECS_TASK_CONTAINER_NAME: REPOSITORY_ECS_TASK_CONTAINER_NAME,
2020
+ SLACK_WEBHOOK_URL: slackWebhookUrl
2021
+ }
2022
+ },
2023
+ Handler: "index.ecsTaskReportHandler"
2024
+ }
2025
+ };
2026
+ resources2.CicdApiV1ServerlessFunction = {
2027
+ Type: "AWS::Serverless::Function",
2028
+ Properties: {
2029
+ ...commonFunctionProperties,
2030
+ Events: {
2031
+ ApiEvent: {
2032
+ Type: "Api",
2033
+ Properties: {
2034
+ Method: "POST",
2035
+ Path: "/cicd",
2036
+ RestApiId: { Ref: API_LOGICAL_ID }
2037
+ }
2038
+ }
2039
+ },
2040
+ Environment: {
2041
+ Variables: {
2042
+ [PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME]: {
2043
+ Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
2044
+ },
2045
+ ...executeEcsTaskVariables
2046
+ }
2047
+ },
2048
+ Handler: "index.cicdApiV1Handler"
2049
+ }
2050
+ };
2051
+ resources2.GitHubWebhooksApiV1ServerlessFunction = {
2052
+ Type: "AWS::Serverless::Function",
2053
+ Properties: {
2054
+ ...commonFunctionProperties,
2055
+ Events: {
2056
+ ApiEvent: {
2057
+ Type: "Api",
2058
+ Properties: {
2059
+ Method: "POST",
2060
+ Path: "/github/webhooks",
2061
+ RestApiId: { Ref: API_LOGICAL_ID }
2062
+ }
2063
+ }
2064
+ },
2065
+ Environment: {
2066
+ Variables: {
2067
+ BASE_STACK_BUCKET_NAME: {
2068
+ "Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
2069
+ },
2070
+ TRIGGER_PIPELINES_OBJECT_KEY_PREFIX: triggerPipelinesObjectKeyPrefix,
2071
+ PIPELINES_JSON: JSON.stringify(pipelines2),
2072
+ ...executeEcsTaskVariables
2073
+ }
2074
+ },
2075
+ Handler: "index.githubWebhooksApiV1Handler"
2076
+ }
2077
+ };
2078
+ };
2079
+ createApiResources();
2080
+ (() => {
2081
+ resources2[REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID] = {
2082
+ Type: "AWS::ECS::Cluster",
2083
+ Properties: {}
2084
+ };
2085
+ resources2[REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID] = {
2086
+ Type: "AWS::Logs::LogGroup",
2087
+ DeletionPolicy: "Delete",
2088
+ Properties: {}
2089
+ };
2090
+ resources2[REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID] = {
2091
+ Type: "AWS::IAM::Role",
2092
+ Properties: {
2093
+ AssumeRolePolicyDocument: {
2094
+ Version: "2012-10-17",
2095
+ Statement: [
2096
+ {
2097
+ Effect: "Allow",
2098
+ Principal: {
2099
+ Service: "ecs-tasks.amazonaws.com"
2100
+ },
2101
+ Action: "sts:AssumeRole"
2102
+ }
2103
+ ]
2104
+ },
2105
+ ManagedPolicyArns: [
2106
+ "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
2107
+ ],
2108
+ Path: getIamPath()
2109
+ }
2110
+ };
2111
+ resources2[REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID] = {
2112
+ Type: "AWS::IAM::Role",
2113
+ Properties: {
2114
+ AssumeRolePolicyDocument: {
2115
+ Version: "2012-10-17",
2116
+ Statement: [
2117
+ {
2118
+ Effect: "Allow",
2119
+ Principal: {
2120
+ Service: "ecs-tasks.amazonaws.com"
2121
+ },
2122
+ Action: "sts:AssumeRole"
2123
+ }
2124
+ ]
2125
+ },
2126
+ ManagedPolicyArns: [
2127
+ "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
2128
+ ],
2129
+ Path: getIamPath(),
2130
+ /**
2131
+ * TODO: improve the policies rules.
2132
+ */
2133
+ Policies: [
2134
+ {
2135
+ PolicyName: `${REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID}Policy`,
2136
+ PolicyDocument: {
2137
+ Version: "2012-10-17",
2138
+ Statement: [
2139
+ {
2140
+ Effect: "Allow",
2141
+ Action: ["*"],
2142
+ Resource: "*"
2143
+ }
2144
+ ]
2145
+ }
2146
+ }
2147
+ ]
2148
+ }
2149
+ };
2150
+ resources2[REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID] = {
2151
+ Type: "AWS::ECS::TaskDefinition",
2152
+ Properties: {
2153
+ ContainerDefinitions: [
2154
+ {
2155
+ Environment: [
2156
+ {
2157
+ /**
2158
+ * https://docs.aws.amazon.com/AmazonECS/latest/developerguide/container-metadata.html#enable-metadata
2159
+ */
2160
+ Name: "ECS_ENABLE_CONTAINER_METADATA",
2161
+ Value: "true"
2162
+ },
2163
+ {
2164
+ Name: "CI",
2165
+ Value: "true"
2166
+ },
2167
+ ...taskEnvironment.map((te) => {
2168
+ return {
2169
+ Name: te.name,
2170
+ Value: te.value
2171
+ };
2172
+ })
2173
+ ],
2174
+ Image: {
2175
+ "Fn::Sub": [
2176
+ // eslint-disable-next-line no-template-curly-in-string
2177
+ "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryECR}:latest",
2178
+ {
2179
+ RepositoryECR: { Ref: ECR_REPOSITORY_LOGICAL_ID }
2180
+ }
2181
+ ]
2182
+ },
2183
+ LogConfiguration: {
2184
+ LogDriver: "awslogs",
2185
+ Options: {
2186
+ "awslogs-group": {
2187
+ Ref: REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID
2188
+ },
2189
+ "awslogs-region": { Ref: "AWS::Region" },
2190
+ "awslogs-stream-prefix": "ecs"
2191
+ }
2192
+ },
2193
+ Name: REPOSITORY_ECS_TASK_CONTAINER_NAME
2194
+ }
2195
+ ],
2196
+ Cpu: cpu,
2197
+ ExecutionRoleArn: {
2198
+ "Fn::GetAtt": [
2199
+ REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID,
2200
+ "Arn"
2201
+ ]
2202
+ },
2203
+ Memory: memory,
2204
+ NetworkMode: "awsvpc",
2205
+ RequiresCompatibilities: ["FARGATE"],
2206
+ TaskRoleArn: {
2207
+ "Fn::GetAtt": [
2208
+ REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID,
2209
+ "Arn"
2210
+ ]
2211
+ }
2212
+ }
2213
+ };
2214
+ })();
2215
+ if (pipelines2.includes("main") || pipelines2.includes("tag")) {
2216
+ resources2[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID] = {
2217
+ Type: "AWS::S3::Bucket",
2218
+ Properties: {
2219
+ LifecycleConfiguration: {
2220
+ Rules: [
2221
+ {
2222
+ /**
2223
+ * We won't use the artifacts forever.
2224
+ */
2225
+ ExpirationInDays: 7,
2226
+ Status: "Enabled"
2227
+ }
2228
+ ]
2229
+ }
2230
+ }
2231
+ };
2232
+ resources2[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID] = {
2233
+ Type: "AWS::Lambda::Function",
2234
+ Properties: {
2235
+ Code: {
2236
+ S3Bucket: s32.bucket,
2237
+ S3Key: s32.key,
2238
+ S3ObjectVersion: s32.versionId
2239
+ },
2240
+ Environment: {
2241
+ Variables: {
2242
+ ...executeEcsTaskVariables
2243
+ }
2244
+ },
2245
+ Handler: "index.pipelinesHandler",
2246
+ MemorySize: 128,
2247
+ Role: {
2248
+ "Fn::GetAtt": [FUNCTION_IAM_ROLE_LOGICAL_ID, "Arn"]
2249
+ },
2250
+ Runtime: NODE_RUNTIME,
2251
+ Timeout: 60
2252
+ }
2253
+ };
2254
+ resources2[PIPELINES_ROLE_LOGICAL_ID] = {
2255
+ Type: "AWS::IAM::Role",
2256
+ Properties: {
2257
+ AssumeRolePolicyDocument: {
2258
+ Version: "2012-10-17",
2259
+ Statement: [
2260
+ {
2261
+ Effect: "Allow",
2262
+ Principal: {
2263
+ Service: "codepipeline.amazonaws.com"
2264
+ },
2265
+ Action: "sts:AssumeRole"
2266
+ }
2267
+ ]
2268
+ },
2269
+ ManagedPolicyArns: [],
2270
+ Path: getIamPath(),
2271
+ Policies: [
2272
+ {
2273
+ PolicyName: `${PIPELINES_ROLE_LOGICAL_ID}Policy`,
2274
+ PolicyDocument: {
2275
+ Version: "2012-10-17",
2276
+ Statement: [
2277
+ {
2278
+ Effect: "Allow",
2279
+ Action: "lambda:InvokeFunction",
2280
+ Resource: [
2281
+ {
2282
+ "Fn::GetAtt": [
2283
+ PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID,
2284
+ "Arn"
2285
+ ]
2286
+ }
2287
+ ]
2288
+ },
2289
+ {
2290
+ Effect: "Allow",
2291
+ Action: "s3:*",
2292
+ Resource: [
2293
+ {
2294
+ "Fn::GetAtt": [
2295
+ PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID,
2296
+ "Arn"
2297
+ ]
2298
+ },
2299
+ {
2300
+ "Fn::Sub": `arn:aws:s3:::\${${PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID}}/*`
2301
+ }
2302
+ ]
2303
+ },
2304
+ {
2305
+ Effect: "Allow",
2306
+ Action: "s3:*",
2307
+ Resource: {
2308
+ "Fn::Sub": [
2309
+ `arn:aws:s3:::\${BucketName}/${triggerPipelinesObjectKeyPrefix}*`,
2310
+ {
2311
+ BucketName: {
2312
+ "Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
2313
+ }
2314
+ }
2315
+ ]
2316
+ }
2317
+ },
2318
+ {
2319
+ Effect: "Allow",
2320
+ Action: ["s3:Get*", "s3:List*"],
2321
+ Resource: {
2322
+ "Fn::Sub": [
2323
+ `arn:aws:s3:::\${BucketName}`,
2324
+ {
2325
+ BucketName: {
2326
+ "Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
2327
+ }
2328
+ }
2329
+ ]
2330
+ }
2331
+ }
2332
+ ]
2333
+ }
2334
+ }
2335
+ ]
2336
+ }
2337
+ };
2338
+ const getCodePipelinePipeline = (pipeline) => {
2339
+ const pipelinePascalCase = _changecase.pascalCase.call(void 0, pipeline);
2340
+ const pipelineS3SourceOutputName = `Pipeline${pipelinePascalCase}S3SourceOutput`;
2341
+ return {
2342
+ Type: "AWS::CodePipeline::Pipeline",
2343
+ Properties: {
2344
+ ArtifactStore: {
2345
+ Location: { Ref: PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID },
2346
+ Type: "S3"
2347
+ },
2348
+ RestartExecutionOnUpdate: false,
2349
+ RoleArn: {
2350
+ "Fn::GetAtt": [PIPELINES_ROLE_LOGICAL_ID, "Arn"]
2351
+ },
2352
+ Stages: [
2353
+ {
2354
+ Actions: [
2355
+ {
2356
+ ActionTypeId: {
2357
+ Category: "Source",
2358
+ Owner: "AWS",
2359
+ Provider: "S3",
2360
+ Version: 1
2361
+ },
2362
+ Configuration: {
2363
+ S3Bucket: {
2364
+ "Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
2365
+ },
2366
+ S3ObjectKey: getTriggerPipelinesObjectKey({
2367
+ prefix: triggerPipelinesObjectKeyPrefix,
2368
+ pipeline
2369
+ })
2370
+ },
2371
+ Name: `Pipeline${pipelinePascalCase}S3SourceAction`,
2372
+ OutputArtifacts: [
2373
+ {
2374
+ Name: pipelineS3SourceOutputName
2375
+ }
2376
+ ]
2377
+ }
2378
+ ],
2379
+ Name: `Pipeline${pipelinePascalCase}S3SourceStage`
2380
+ },
2381
+ {
2382
+ Actions: [
2383
+ {
2384
+ ActionTypeId: {
2385
+ Category: "Invoke",
2386
+ Owner: "AWS",
2387
+ Provider: "Lambda",
2388
+ Version: 1
2389
+ },
2390
+ Configuration: {
2391
+ FunctionName: {
2392
+ Ref: PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID
2393
+ },
2394
+ UserParameters: /* @__PURE__ */ (() => {
2395
+ return pipeline;
2396
+ })()
2397
+ },
2398
+ InputArtifacts: [
2399
+ {
2400
+ Name: pipelineS3SourceOutputName
2401
+ }
2402
+ ],
2403
+ Name: `Pipeline${pipelinePascalCase}RunECSTasksAction`
2404
+ },
2405
+ {
2406
+ ActionTypeId: {
2407
+ Category: "Approval",
2408
+ Owner: "AWS",
2409
+ Provider: "Manual",
2410
+ Version: 1
2411
+ },
2412
+ Name: PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME
2413
+ }
2414
+ ],
2415
+ Name: PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME
2416
+ }
2417
+ ]
2418
+ }
2419
+ };
2420
+ };
2421
+ if (pipelines2.includes("main")) {
2422
+ resources2[PIPELINES_MAIN_LOGICAL_ID] = getCodePipelinePipeline("main");
2423
+ }
2424
+ if (pipelines2.includes("tag")) {
2425
+ resources2[PIPELINES_TAG_LOGICAL_ID] = getCodePipelinePipeline("tag");
2426
+ }
2427
+ }
2428
+ return {
2429
+ AWSTemplateFormatVersion: "2010-09-09",
2430
+ Transform: "AWS::Serverless-2016-10-31",
2431
+ Resources: resources2,
2432
+ Parameters: {
2433
+ SSHKey: {
2434
+ NoEcho: true,
2435
+ Type: "String"
2436
+ },
2437
+ SSHUrl: {
2438
+ Type: "String"
2439
+ }
2440
+ },
2441
+ Outputs: {
2442
+ [REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID]: {
2443
+ Value: { Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID }
2444
+ },
2445
+ ApiV1Endpoint: {
2446
+ Description: "CICD API v1 stage endpoint.",
2447
+ Value: {
2448
+ "Fn::Sub": `https://\${${API_LOGICAL_ID}}.execute-api.\${AWS::Region}.amazonaws.com/v1/`
2449
+ }
2450
+ }
2451
+ }
2452
+ };
2453
+ };
2454
+
2455
+ // src/deploy/lambda/buildLambdaSingleFile.ts
2456
+ var _esbuild = require('esbuild'); var esbuild = _interopRequireWildcard(_esbuild);
2457
+ var _builtinmodules = require('builtin-modules'); var _builtinmodules2 = _interopRequireDefault(_builtinmodules);
2458
+
2459
+
2460
+ var logPrefix5 = "lambda";
2461
+ var outFolder = "dist";
2462
+ var outFile = "index.js";
2463
+ var buildLambdaSingleFile = async ({
2464
+ lambdaExternals,
2465
+ lambdaInput
2466
+ }) => {
2467
+ _npmlog2.default.info(logPrefix5, "Building Lambda single file...");
2468
+ const { errors } = esbuild.buildSync({
2469
+ banner: {
2470
+ js: "// Powered by carlin (https://ttoss.dev/docs/carlin/)"
2471
+ },
2472
+ bundle: true,
2473
+ entryPoints: [path2.default.resolve(process.cwd(), lambdaInput)],
2474
+ external: [
2475
+ /**
2476
+ * Only AWS SDK v3 on Node.js 18.x or higher.
2477
+ * https://aws.amazon.com/blogs/compute/node-js-18-x-runtime-now-available-in-aws-lambda/
2478
+ */
2479
+ "@aws-sdk/*",
2480
+ ..._builtinmodules2.default,
2481
+ ...lambdaExternals
2482
+ ],
2483
+ /**
2484
+ * https://esbuild.github.io/api/#minify
2485
+ */
2486
+ minifySyntax: true,
2487
+ platform: "node",
2488
+ outfile: path2.default.join(process.cwd(), outFolder, outFile),
2489
+ target: "node20",
2490
+ treeShaking: true
2491
+ });
2492
+ if (errors.length > 0) {
2493
+ throw errors;
2494
+ }
2495
+ };
2496
+
2497
+ // src/deploy/lambdaLayer/deployLambdaLayer.ts
2498
+
2499
+
2500
+ // src/deploy/lambdaLayer/getPackageLambdaLayerStackName.ts
2501
+
2502
+ var lambdaLayerStackNamePrefix = `LambdaLayer`;
2503
+ var getPackageLambdaLayerStackName = (packageName) => {
2504
+ const [scopedName, version] = packageName.split("@").filter((part) => {
2505
+ return !!part;
2506
+ });
2507
+ return [
2508
+ lambdaLayerStackNamePrefix,
2509
+ _changecase.pascalCase.call(void 0, scopedName),
2510
+ version.replace(/[^0-9.]/g, "").replace(/\./g, "-")
2511
+ ].join("-");
2512
+ };
2513
+
2514
+ // src/deploy/lambdaLayer/deployLambdaLayer.ts
2515
+
2516
+ var logPrefix6 = "lambda-layer";
2517
+ var createLambdaLayerZipFile = async ({
2518
+ codeBuildProjectName,
2519
+ packageName
2520
+ }) => {
2521
+ _npmlog2.default.info(logPrefix6, `Creating zip file for package ${packageName}...`);
2522
+ const codeBuild2 = new (0, _awssdk.CodeBuild)();
2523
+ const { build } = await codeBuild2.startBuild({
2524
+ environmentVariablesOverride: [
2525
+ {
2526
+ name: "PACKAGE_NAME",
2527
+ value: packageName
2528
+ }
2529
+ ],
2530
+ projectName: codeBuildProjectName
2531
+ }).promise();
2532
+ if (!_optionalChain([build, 'optionalAccess', _10 => _10.id])) {
2533
+ throw new Error("Cannot start build.");
2534
+ }
2535
+ const result = await waitCodeBuildFinish({
2536
+ buildId: build.id,
2537
+ name: packageName
2538
+ });
2539
+ if (_optionalChain([result, 'access', _11 => _11.artifacts, 'optionalAccess', _12 => _12.location])) {
2540
+ const location = result.artifacts.location.split("/");
2541
+ const bucket = _optionalChain([location, 'access', _13 => _13.shift, 'call', _14 => _14(), 'optionalAccess', _15 => _15.replace, 'call', _16 => _16("arn:aws:s3:::", "")]);
2542
+ if (!bucket) {
2543
+ throw new Error("Cannot retrieve bucket name.");
2544
+ }
2545
+ const key = location.join("/");
2546
+ return { bucket, key };
2547
+ }
2548
+ throw new Error(`Cannot get artifact location for package ${packageName}`);
2549
+ };
2550
+ var getLambdaLayerTemplate = ({
2551
+ bucket,
2552
+ key,
2553
+ packageName
2554
+ }) => {
2555
+ const description = packageName.substring(0, 256);
2556
+ return {
2557
+ AWSTemplateFormatVersion: "2010-09-09",
2558
+ Resources: {
2559
+ LambdaLayer: {
2560
+ Type: "AWS::Lambda::LayerVersion",
2561
+ Properties: {
2562
+ CompatibleRuntimes: [NODE_RUNTIME],
2563
+ Content: {
2564
+ S3Bucket: bucket,
2565
+ S3Key: key
2566
+ },
2567
+ Description: description,
2568
+ LayerName: { Ref: "AWS::StackName" }
2569
+ }
2570
+ }
2571
+ },
2572
+ Outputs: {
2573
+ LambdaLayerVersion: {
2574
+ Description: description,
2575
+ Value: { Ref: "LambdaLayer" },
2576
+ Export: {
2577
+ Name: { Ref: "AWS::StackName" }
2578
+ }
2579
+ }
2580
+ }
2581
+ };
2582
+ };
2583
+ var getPackagesThatAreNotDeployed = async ({
2584
+ packages
2585
+ }) => {
2586
+ return (await Promise.all(
2587
+ packages.map(async (packageName) => {
2588
+ const stackName = getPackageLambdaLayerStackName(packageName);
2589
+ return await doesStackExist({ stackName }) ? "" : packageName;
2590
+ })
2591
+ )).filter((packageName) => {
2592
+ return !!packageName;
2593
+ });
2594
+ };
2595
+ var deployLambdaLayer = async ({
2596
+ packages,
2597
+ deployIfExists = true
2598
+ }) => {
2599
+ try {
2600
+ const packagesToBeDeployed = deployIfExists ? packages : await getPackagesThatAreNotDeployed({ packages });
2601
+ if (packagesToBeDeployed.length === 0) {
2602
+ return;
2603
+ }
2604
+ const codeBuildProjectName = await getBaseStackResource(
2605
+ "BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME"
2606
+ );
2607
+ if (!codeBuildProjectName) {
2608
+ throw new Error(
2609
+ "Cannot deploy lambda-layer because AWS CodeBuild project doesn't exist."
2610
+ );
2611
+ }
2612
+ const deployLambdaLayerSinglePackage = async (packageName) => {
2613
+ try {
2614
+ const { bucket, key } = await createLambdaLayerZipFile({
2615
+ codeBuildProjectName,
2616
+ packageName
2617
+ });
2618
+ const lambdaLayerTemplate = getLambdaLayerTemplate({
2619
+ packageName,
2620
+ bucket,
2621
+ key
2622
+ });
2623
+ await deploy({
2624
+ template: lambdaLayerTemplate,
2625
+ terminationProtection: true,
2626
+ params: { StackName: getPackageLambdaLayerStackName(packageName) }
2627
+ });
2628
+ } catch (error) {
2629
+ handleDeployError({ error, logPrefix: logPrefix6 });
2630
+ }
2631
+ };
2632
+ await Promise.all(
2633
+ packagesToBeDeployed.map((packageName) => {
2634
+ return deployLambdaLayerSinglePackage(packageName);
2635
+ })
2636
+ );
2637
+ } catch (error) {
2638
+ handleDeployError({ error, logPrefix: logPrefix6 });
2639
+ }
2640
+ };
2641
+
2642
+ // src/deploy/lambda/deployLambdaLayers.ts
2643
+
2644
+
2645
+ var logPrefix7 = "lambda";
2646
+ var deployLambdaLayers = async ({
2647
+ lambdaExternals = []
2648
+ }) => {
2649
+ if (lambdaExternals.length === 0) {
2650
+ return;
2651
+ }
2652
+ _npmlog2.default.info(
2653
+ logPrefix7,
2654
+ `--lambda-externals [${lambdaExternals.join(
2655
+ ", "
2656
+ )}] was found. Creating other layers...`
2657
+ );
2658
+ const { dependencies = {} } = (() => {
2659
+ try {
2660
+ return __require(path2.default.resolve(process.cwd(), "package.json")) || {};
2661
+ } catch (err) {
2662
+ _npmlog2.default.error(
2663
+ logPrefix7,
2664
+ "Cannot read package.json. Error message: %j",
2665
+ err.message
2666
+ );
2667
+ return {};
2668
+ }
2669
+ })();
2670
+ const packages = lambdaExternals.map((lambdaExternal) => {
2671
+ try {
2672
+ const semver2 = dependencies[lambdaExternal].replace(/(~|\^)/g, "");
2673
+ return `${lambdaExternal}@${semver2}`;
2674
+ } catch (e2) {
2675
+ throw new Error(
2676
+ `Cannot find ${lambdaExternal} on package.json dependencies.`
2677
+ );
2678
+ }
2679
+ });
2680
+ await deployLambdaLayer({ packages, deployIfExists: false });
2681
+ };
2682
+
2683
+ // src/deploy/lambda/uploadCodeToECR.ts
2684
+
2685
+ var codeBuild = new (0, _awssdk.CodeBuild)({ region: AWS_DEFAULT_REGION });
2686
+ var uploadCodeToECR = async ({
2687
+ bucket,
2688
+ key,
2689
+ lambdaExternals,
2690
+ lambdaDockerfile
2691
+ }) => {
2692
+ const TEMP = 1;
2693
+ if (TEMP) {
2694
+ throw new Error("uploadCodeToECR not finished yet.");
2695
+ }
2696
+ const lambdaBuilder = await getBaseStackResource(
2697
+ "BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME"
2698
+ );
2699
+ const defaultDockerfile = [
2700
+ "FROM public.ecr.aws/lambda/nodejs:14",
2701
+ // eslint-disable-next-line no-template-curly-in-string
2702
+ "COPY . ${LAMBDA_TASK_ROOT}",
2703
+ "RUN npm install"
2704
+ ].join("\n");
2705
+ const { build } = await codeBuild.startBuild({
2706
+ environmentVariablesOverride: [
2707
+ {
2708
+ name: "DOCKERFILE",
2709
+ value: lambdaDockerfile || defaultDockerfile
2710
+ },
2711
+ {
2712
+ name: "LAMBDA_EXTERNALS",
2713
+ value: lambdaExternals.join(" ")
2714
+ },
2715
+ {
2716
+ name: "IMAGE_TAG",
2717
+ value: getPackageVersion()
2718
+ },
2719
+ {
2720
+ name: "REPOSITORY_ECR_REPOSITORY",
2721
+ value: "testtesteste"
2722
+ }
2723
+ ],
2724
+ projectName: lambdaBuilder,
2725
+ sourceLocationOverride: `${bucket}/${key}`,
2726
+ sourceTypeOverride: "S3"
2727
+ }).promise();
2728
+ if (!_optionalChain([build, 'optionalAccess', _17 => _17.id])) {
2729
+ throw new Error("Cannot start build.");
2730
+ }
2731
+ await waitCodeBuildFinish({ buildId: build.id, name: "lambda-builder" });
2732
+ const imageUri = "178804353523.dkr.ecr.us-east-1.amazonaws.com/testtesteste:0.0.1";
2733
+ return { imageUri };
2734
+ };
2735
+
2736
+ // src/deploy/lambda/uploadCodeToS3.ts
2737
+ var _admzip = require('adm-zip'); var _admzip2 = _interopRequireDefault(_admzip);
2738
+
2739
+
2740
+
2741
+ var logPrefix8 = "lambda";
2742
+ var outFolder2 = "dist";
2743
+ var outFile2 = "index.js";
2744
+ var uploadCodeToS3 = async ({ stackName }) => {
2745
+ _npmlog2.default.info(logPrefix8, `Uploading code to S3...`);
2746
+ const zip = new (0, _admzip2.default)();
2747
+ const code = fs3.default.readFileSync(path2.default.join(process.cwd(), outFolder2, outFile2));
2748
+ zip.addFile("index.js", code);
2749
+ if (!fs3.default.existsSync("dist")) {
2750
+ fs3.default.mkdirSync("dist");
2751
+ }
2752
+ zip.writeZip("dist/lambda.zip");
2753
+ const bucketName = await getBaseStackResource(
2754
+ "BASE_STACK_BUCKET_LOGICAL_NAME"
2755
+ );
2756
+ return uploadFileToS3({
2757
+ bucket: bucketName,
2758
+ contentType: "application/zip",
2759
+ key: `lambdas/${stackName}/lambda.zip`,
2760
+ file: zip.toBuffer()
2761
+ });
2762
+ };
2763
+
2764
+ // src/deploy/lambda/deployLambdaCode.ts
2765
+
2766
+
2767
+ var logPrefix9 = "lambda";
2768
+ var deployLambdaCode = async ({
2769
+ lambdaDockerfile,
2770
+ lambdaExternals,
2771
+ lambdaImage,
2772
+ lambdaInput,
2773
+ stackName
2774
+ }) => {
2775
+ if (!fs3.default.existsSync(lambdaInput)) {
2776
+ return void 0;
2777
+ }
2778
+ _npmlog2.default.info(logPrefix9, "Deploying Lambda code...");
2779
+ await buildLambdaSingleFile({ lambdaExternals, lambdaInput });
2780
+ const { bucket, key, versionId } = await uploadCodeToS3({ stackName });
2781
+ if (!lambdaImage) {
2782
+ await deployLambdaLayers({ lambdaExternals });
2783
+ return { bucket, key, versionId };
2784
+ }
2785
+ const { imageUri } = await uploadCodeToECR({
2786
+ bucket,
2787
+ key,
2788
+ versionId,
2789
+ lambdaDockerfile,
2790
+ lambdaExternals
2791
+ });
2792
+ return { imageUri };
2793
+ };
2794
+
2795
+ // src/deploy/cicd/getCicdStackName.ts
2796
+
2797
+ var getCicdStackName = () => {
2798
+ const project = getProjectName();
2799
+ return _changecase.pascalCase.call(void 0, [NAME, "Cicd", project].join(" "));
2800
+ };
2801
+
2802
+ // src/deploy/cicd/deployCicd.ts
2803
+
2804
+ var logPrefix10 = "cicd";
2805
+ var getLambdaInput = (extension) => {
2806
+ return path6.resolve(__dirname, `lambdas/index.${extension}`);
2807
+ };
2808
+ var deployCicdLambdas = async ({ stackName }) => {
2809
+ const lambdaInput = (() => {
2810
+ if (fs6.existsSync(getLambdaInput("js"))) {
2811
+ return getLambdaInput("js");
2812
+ }
2813
+ if (fs6.existsSync(getLambdaInput("ts"))) {
2814
+ return getLambdaInput("ts");
2815
+ }
2816
+ throw new Error("Cannot read CICD lambdas file.");
2817
+ })();
2818
+ const s32 = await deployLambdaCode({
2819
+ lambdaInput,
2820
+ lambdaExternals: [],
2821
+ /**
2822
+ * Needs stackName to define the S3 key.
2823
+ */
2824
+ stackName
2825
+ });
2826
+ if (!s32 || !s32.bucket) {
2827
+ throw new Error(
2828
+ "Cannot retrieve bucket in which Lambda code was deployed."
2829
+ );
2830
+ }
2831
+ return s32;
2832
+ };
2833
+ var waitRepositoryImageUpdate = async ({
2834
+ stackName
2835
+ }) => {
2836
+ _npmlog2.default.info(logPrefix10, "Starting repository image update...");
2837
+ const { OutputValue: projectName } = await getStackOutput({
2838
+ stackName,
2839
+ outputKey: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
2840
+ });
2841
+ if (!projectName) {
2842
+ throw new Error(`Cannot retrieve repository image CodeBuild project name.`);
2843
+ }
2844
+ const build = await startCodeBuildBuild({ projectName });
2845
+ if (build.id) {
2846
+ await waitCodeBuildFinish({ buildId: build.id, name: stackName });
2847
+ }
2848
+ };
2849
+ var deployCicd = async ({
2850
+ cpu,
2851
+ memory,
2852
+ pipelines: pipelines2,
2853
+ updateRepository,
2854
+ slackWebhookUrl,
2855
+ sshKey,
2856
+ sshUrl,
2857
+ taskEnvironment
2858
+ }) => {
2859
+ try {
2860
+ const { stackName } = await handleDeployInitialization({
2861
+ logPrefix: logPrefix10,
2862
+ stackName: getCicdStackName()
2863
+ });
2864
+ await deploy({
2865
+ template: getCicdTemplate({
2866
+ cpu,
2867
+ memory,
2868
+ pipelines: pipelines2,
2869
+ s3: await deployCicdLambdas({ stackName }),
2870
+ slackWebhookUrl,
2871
+ taskEnvironment
2872
+ }),
2873
+ params: {
2874
+ StackName: stackName,
2875
+ Parameters: [
2876
+ { ParameterKey: "SSHUrl", ParameterValue: sshUrl },
2877
+ { ParameterKey: "SSHKey", ParameterValue: sshKey }
2878
+ ]
2879
+ },
2880
+ terminationProtection: true
2881
+ });
2882
+ if (updateRepository) {
2883
+ await waitRepositoryImageUpdate({ stackName });
2884
+ }
2885
+ } catch (error) {
2886
+ handleDeployError({ error, logPrefix: logPrefix10 });
2887
+ }
2888
+ };
2889
+
2890
+ // src/deploy/cicd/readSSHKey.ts
2891
+
2892
+ var readSSHKey = (dir) => {
2893
+ return fs7.readFileSync(dir, "utf-8");
2894
+ };
2895
+
2896
+ // src/deploy/cicd/command.ts
2897
+
2898
+ var logPrefix11 = "deploy-cicd";
2899
+ var deployCicdCommand = {
2900
+ command: "cicd",
2901
+ describe: "Deploy CICD.",
2902
+ builder: (yargs3) => {
2903
+ return yargs3.options(addGroupToOptions(options, "Deploy CICD Options"));
2904
+ },
2905
+ handler: ({ destroy: destroy2, ...rest }) => {
2906
+ if (destroy2) {
2907
+ _npmlog2.default.info(logPrefix11, `${NAME} doesn't destroy CICD stack.`);
2908
+ } else {
2909
+ deployCicd({
2910
+ ...rest,
2911
+ sshKey: readSSHKey(rest["ssh-key"])
2912
+ });
2913
+ }
2914
+ }
2915
+ };
2916
+
2917
+ // ../cloudformation/src/findAndReadCloudFormationTemplate.ts
2918
+
2919
+
2920
+
2921
+ // ../cloudformation/src/readCloudFormationYamlTemplate.ts
2922
+
2923
+
2924
+
2925
+ // ../cloudformation/src/cloudFormationYamlTemplate.ts
2926
+
2927
+ var cloudFormationTypes = [
2928
+ {
2929
+ tag: "!Equals",
2930
+ options: {
2931
+ kind: "sequence",
2932
+ construct: (data) => {
2933
+ return { "Fn::Equals": data };
2934
+ }
2935
+ }
2936
+ },
2937
+ {
2938
+ tag: "!FindInMap",
2939
+ options: {
2940
+ kind: "sequence",
2941
+ construct: (data) => {
2942
+ return { "Fn::FindInMap": data };
2943
+ }
2944
+ }
2945
+ },
2946
+ {
2947
+ tag: "!GetAtt",
2948
+ options: {
2949
+ kind: "scalar",
2950
+ construct: (data) => {
2951
+ return { "Fn::GetAtt": data.split(".") };
2952
+ }
2953
+ }
2954
+ },
2955
+ {
2956
+ tag: "!GetAtt",
2957
+ options: {
2958
+ kind: "sequence",
2959
+ construct: (data) => {
2960
+ return { "Fn::GetAtt": data };
2961
+ }
2962
+ }
2963
+ },
2964
+ {
2965
+ tag: "!If",
2966
+ options: {
2967
+ kind: "sequence",
2968
+ construct: (data) => {
2969
+ return { "Fn::If": data };
2970
+ }
2971
+ }
2972
+ },
2973
+ {
2974
+ tag: "!ImportValue",
2975
+ options: {
2976
+ kind: "scalar",
2977
+ construct: (data) => {
2978
+ return { "Fn::ImportValue": data };
2979
+ }
2980
+ }
2981
+ },
2982
+ {
2983
+ tag: "!Join",
2984
+ options: {
2985
+ kind: "sequence",
2986
+ construct: (data) => {
2987
+ return { "Fn::Join": data };
2988
+ }
2989
+ }
2990
+ },
2991
+ {
2992
+ tag: "!Not",
2993
+ options: {
2994
+ kind: "sequence",
2995
+ construct: (data) => {
2996
+ return { "Fn::Not": data };
2997
+ }
2998
+ }
2999
+ },
3000
+ {
3001
+ tag: "!Ref",
3002
+ options: {
3003
+ kind: "scalar",
3004
+ construct: (data) => {
3005
+ return { Ref: data };
3006
+ }
3007
+ }
3008
+ },
3009
+ {
3010
+ tag: "!Sub",
3011
+ options: {
3012
+ kind: "scalar",
3013
+ construct: (data) => {
3014
+ return { "Fn::Sub": data };
3015
+ }
3016
+ }
3017
+ },
3018
+ {
3019
+ tag: "!Sub",
3020
+ options: {
3021
+ kind: "sequence",
3022
+ construct: (data) => {
3023
+ return { "Fn::Sub": data };
3024
+ }
3025
+ }
3026
+ }
3027
+ ];
3028
+ var getYamlTypes = (tagAndTypeArr) => {
3029
+ return tagAndTypeArr.map(({ tag, options: options9 }) => {
3030
+ return new _jsyaml2.default.Type(tag, options9);
3031
+ });
3032
+ };
3033
+ var getSchema = (tagAndTypeArr = []) => {
3034
+ return _jsyaml2.default.DEFAULT_SCHEMA.extend(
3035
+ getYamlTypes([...tagAndTypeArr, ...cloudFormationTypes])
3036
+ );
3037
+ };
3038
+ var loadCloudFormationTemplate = (template, tagAndTypeArr = []) => {
3039
+ return _jsyaml2.default.load(template, { schema: getSchema(tagAndTypeArr) });
3040
+ };
3041
+
3042
+ // ../cloudformation/src/readCloudFormationYamlTemplate.ts
3043
+ var getTypes = () => {
3044
+ return [
3045
+ {
3046
+ tag: `!SubString`,
3047
+ options: {
3048
+ kind: "scalar",
3049
+ construct: (filePath) => {
3050
+ return fs8.readFileSync(path7.resolve(process.cwd(), filePath)).toString();
3051
+ }
3052
+ }
3053
+ }
3054
+ ];
3055
+ };
3056
+ var readCloudFormationYamlTemplate = ({
3057
+ templatePath
3058
+ }) => {
3059
+ const template = fs8.readFileSync(templatePath).toString();
3060
+ const parsed = loadCloudFormationTemplate(template, getTypes());
3061
+ if (!parsed || typeof parsed === "string") {
3062
+ throw new Error("Cannot parse CloudFormation template.");
3063
+ }
3064
+ return parsed;
3065
+ };
3066
+
3067
+ // ../cloudformation/src/readObjectFile.ts
3068
+
3069
+
3070
+ var readYaml = ({ path: path12 }) => {
3071
+ const template = fs3.default.readFileSync(path12, "utf8") || JSON.stringify({});
3072
+ return _jsyaml2.default.load(template);
3073
+ };
3074
+ var readObjectFile = ({ path: path12 }) => {
3075
+ if (!fs3.default.existsSync(path12)) {
3076
+ return {};
3077
+ }
3078
+ const extension = path12.split(".").pop();
3079
+ if (extension === "ts") {
3080
+ __require("ts-node").register({
3081
+ compilerOptions: { moduleResolution: "node", module: "commonjs" },
3082
+ moduleTypes: {
3083
+ "carlin.*": "cjs"
3084
+ },
3085
+ transpileOnly: true
3086
+ });
3087
+ const tsObj = __require(path12);
3088
+ const obj = tsObj.default || tsObj;
3089
+ return typeof obj === "function" ? obj() : obj;
3090
+ }
3091
+ if (extension === "js") {
3092
+ const obj = __require(path12);
3093
+ return typeof obj === "function" ? obj() : obj;
3094
+ }
3095
+ if (extension === "json") {
3096
+ return __require(path12);
3097
+ }
3098
+ if (extension === "yml" || extension === "yaml") {
3099
+ return readYaml({ path: path12 });
3100
+ }
3101
+ return {};
3102
+ };
3103
+
3104
+ // ../cloudformation/src/findAndReadCloudFormationTemplate.ts
3105
+ var defaultTemplatePaths2 = ["ts", "js", "yaml", "yml", "json"].map(
3106
+ (extension) => {
3107
+ return `./src/cloudformation.${extension}`;
3108
+ }
3109
+ );
3110
+ var findAndReadCloudFormationTemplate = ({
3111
+ templatePath: defaultTemplatePath
3112
+ }) => {
3113
+ const templatePath = defaultTemplatePath || defaultTemplatePaths2.reduce((acc, cur) => {
3114
+ if (acc) {
3115
+ return acc;
3116
+ }
3117
+ return fs10.existsSync(path8.resolve(process.cwd(), cur)) ? cur : acc;
3118
+ }, "");
3119
+ if (!templatePath) {
3120
+ throw new Error("Cannot find a CloudFormation template.");
3121
+ }
3122
+ const extension = _optionalChain([templatePath, 'optionalAccess', _18 => _18.split, 'call', _19 => _19("."), 'access', _20 => _20.pop, 'call', _21 => _21()]);
3123
+ const fullPath = path8.resolve(process.cwd(), templatePath);
3124
+ if (["yaml", "yml"].includes(extension)) {
3125
+ return readCloudFormationYamlTemplate({ templatePath });
3126
+ }
3127
+ return readObjectFile({ path: fullPath });
3128
+ };
3129
+
3130
+ // src/deploy/cloudformation.ts
3131
+
3132
+ var logPrefix12 = "cloudformation";
3133
+ _npmlog2.default.addLevel("event", 1e4, { fg: "yellow" });
3134
+ _npmlog2.default.addLevel("output", 1e4, { fg: "blue" });
3135
+ var defaultTemplatePaths3 = ["ts", "js", "yaml", "yml", "json"].map(
3136
+ (extension) => {
3137
+ return `./src/cloudformation.${extension}`;
3138
+ }
3139
+ );
3140
+ var deployCloudFormation = async ({
3141
+ lambdaDockerfile,
3142
+ lambdaInput,
3143
+ lambdaImage,
3144
+ lambdaExternals = [],
3145
+ parameters,
3146
+ template,
3147
+ templatePath
3148
+ }) => {
3149
+ try {
3150
+ const { stackName } = await handleDeployInitialization({ logPrefix: logPrefix12 });
3151
+ const cloudFormationTemplate = (() => {
3152
+ if (template) {
3153
+ return { ...template };
3154
+ }
3155
+ return findAndReadCloudFormationTemplate({ templatePath });
3156
+ })();
3157
+ _optionalChain([parameters, 'optionalAccess', _22 => _22.forEach, 'call', _23 => _23((parameter) => {
3158
+ if (_optionalChain([cloudFormationTemplate, 'access', _24 => _24.Parameters, 'optionalAccess', _25 => _25[parameter.key]])) {
3159
+ return;
3160
+ }
3161
+ if (!cloudFormationTemplate.Parameters) {
3162
+ cloudFormationTemplate.Parameters = {};
3163
+ }
3164
+ const type = (() => {
3165
+ if (typeof parameter.value === "string") {
3166
+ return "String";
3167
+ }
3168
+ if (typeof parameter.value === "number") {
3169
+ return "Number";
3170
+ }
3171
+ throw new Error(
3172
+ `Parameter assertion failed. Parameter ${parameter.key} value ${parameter.value} is not mapped.`
3173
+ );
3174
+ })();
3175
+ cloudFormationTemplate.Parameters[parameter.key] = {
3176
+ Type: type
3177
+ };
3178
+ })]);
3179
+ await validateTemplate({ stackName, template: cloudFormationTemplate });
3180
+ const params = {
3181
+ StackName: stackName,
3182
+ Parameters: _optionalChain([parameters, 'optionalAccess', _26 => _26.map, 'call', _27 => _27((parameter) => {
3183
+ return {
3184
+ ParameterKey: parameter.key,
3185
+ ParameterValue: parameter.value,
3186
+ UsePreviousValue: parameter.usePreviousValue,
3187
+ ResolvedValue: parameter.resolvedValue
3188
+ };
3189
+ })]) || []
3190
+ };
3191
+ const deployCloudFormationDeployLambdaCode = async () => {
3192
+ const response = await deployLambdaCode({
3193
+ lambdaDockerfile,
3194
+ lambdaExternals,
3195
+ lambdaInput,
3196
+ lambdaImage,
3197
+ stackName
3198
+ });
3199
+ if (response) {
3200
+ const { bucket, key, versionId, imageUri } = response;
3201
+ if (imageUri) {
3202
+ cloudFormationTemplate.Parameters = {
3203
+ LambdaImageUri: { Type: "String" },
3204
+ ...cloudFormationTemplate.Parameters
3205
+ };
3206
+ params.Parameters.push({
3207
+ ParameterKey: "LambdaImageUri",
3208
+ ParameterValue: imageUri
3209
+ });
3210
+ } else if (bucket && key && versionId) {
3211
+ cloudFormationTemplate.Parameters = {
3212
+ LambdaS3Bucket: { Type: "String" },
3213
+ LambdaS3Key: { Type: "String" },
3214
+ LambdaS3ObjectVersion: { Type: "String" },
3215
+ ...cloudFormationTemplate.Parameters
3216
+ };
3217
+ params.Parameters.push(
3218
+ {
3219
+ ParameterKey: "LambdaS3Bucket",
3220
+ ParameterValue: bucket
3221
+ },
3222
+ {
3223
+ ParameterKey: "LambdaS3Key",
3224
+ ParameterValue: key
3225
+ },
3226
+ /**
3227
+ * Used by CloudFormation AWS::Lambda::Function
3228
+ * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html}
3229
+ * and by CloudFormation AWS::Serverless::Function
3230
+ * @see {@link https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-functioncode.html}
3231
+ */
3232
+ {
3233
+ ParameterKey: "LambdaS3ObjectVersion",
3234
+ ParameterValue: versionId
3235
+ }
3236
+ );
3237
+ }
3238
+ }
3239
+ };
3240
+ await deployCloudFormationDeployLambdaCode();
3241
+ const output = await deploy({
3242
+ params,
3243
+ template: cloudFormationTemplate
3244
+ });
3245
+ return output;
3246
+ } catch (error) {
3247
+ return handleDeployError({ error, logPrefix: logPrefix12 });
3248
+ }
3249
+ };
3250
+ var emptyStackBuckets = async ({ stackName }) => {
3251
+ const buckets = [];
3252
+ await (async ({ nextToken }) => {
3253
+ const {
3254
+ // NextToken,
3255
+ StackResourceSummaries
3256
+ } = await cloudFormationV2().listStackResources({ StackName: stackName, NextToken: nextToken }).promise();
3257
+ (StackResourceSummaries || []).forEach(
3258
+ ({ ResourceType, PhysicalResourceId }) => {
3259
+ if (ResourceType === "AWS::S3::Bucket" && PhysicalResourceId) {
3260
+ buckets.push(PhysicalResourceId);
3261
+ }
3262
+ }
3263
+ );
3264
+ })({});
3265
+ return Promise.all(
3266
+ buckets.map((bucket) => {
3267
+ return emptyS3Directory({ bucket });
3268
+ })
3269
+ );
3270
+ };
3271
+ var destroy = async ({ stackName }) => {
3272
+ const environment = getEnvironment();
3273
+ if (environment) {
3274
+ _npmlog2.default.info(
3275
+ logPrefix12,
3276
+ `Cannot destroy stack when environment (${environment}) is defined.`
3277
+ );
3278
+ return;
3279
+ }
3280
+ if (!await doesStackExist({ stackName })) {
3281
+ _npmlog2.default.info(logPrefix12, `Stack ${stackName} doesn't exist.`);
3282
+ return;
3283
+ }
3284
+ if (!await canDestroyStack({ stackName })) {
3285
+ const message = `Stack ${stackName} cannot be destroyed while TerminationProtection is enabled.`;
3286
+ throw new Error(message);
3287
+ }
3288
+ await emptyStackBuckets({ stackName });
3289
+ await deleteStack({ stackName });
3290
+ };
3291
+ var destroyCloudFormation = async ({
3292
+ stackName: defaultStackName
3293
+ } = {}) => {
3294
+ try {
3295
+ _npmlog2.default.info(logPrefix12, "CAUTION! Starting CloudFormation destroy...");
3296
+ const stackName = defaultStackName || await getStackName();
3297
+ _npmlog2.default.info(logPrefix12, `stackName: ${stackName}`);
3298
+ await destroy({ stackName });
3299
+ } catch (error) {
3300
+ handleDeployError({ error, logPrefix: logPrefix12 });
3301
+ }
3302
+ };
3303
+
3304
+ // src/deploy/lambdaLayer/command.ts
3305
+
3306
+ var logPrefix13 = "deploy-lambda-layer";
3307
+ var packageNameRegex = /@[~^]?([\dvx*]+(?:[-.](?:[\dx*]+|alpha|beta))*)/;
3308
+ var options2 = {
3309
+ packages: {
3310
+ array: true,
3311
+ describe: `NPM packages' names to be deployed as Lambda Layers. It must follow the format: ${packageNameRegex.toString()}.`,
3312
+ required: true,
3313
+ type: "string"
3314
+ }
3315
+ };
3316
+ var deployLambdaLayerCommand = {
3317
+ command: "lambda-layer",
3318
+ describe: "Deploy Lambda Layer.",
3319
+ builder: (yargs3) => yargs3.options(addGroupToOptions(options2, "Deploy Lambda Layer Options")).check(({ packages }) => {
3320
+ const invalidPackages = packages.map((packageName) => {
3321
+ return packageNameRegex.test(packageName) ? void 0 : packageName;
3322
+ }).filter((packageName) => !!packageName);
3323
+ if (invalidPackages.length > 0) {
3324
+ throw new Error(
3325
+ `Some package names are invalid: ${invalidPackages.join(
3326
+ ", "
3327
+ )}. The package must follow the pattern: ${packageNameRegex.toString()}.`
3328
+ );
3329
+ } else {
3330
+ return true;
3331
+ }
3332
+ }),
3333
+ handler: ({ destroy: destroy2, ...rest }) => {
3334
+ if (destroy2) {
3335
+ _npmlog2.default.info(logPrefix13, `${NAME} doesn't destroy lambda layers.`);
3336
+ } else {
3337
+ deployLambdaLayer(rest);
3338
+ }
3339
+ }
3340
+ };
3341
+
3342
+ // src/deploy/staticApp/findDefaultBuildFolder.ts
3343
+ var defaultBuildFolders = [
3344
+ /**
3345
+ * Create React App default build folder
3346
+ */
3347
+ "build",
3348
+ /**
3349
+ * Next.js default output folder
3350
+ */
3351
+ "out",
3352
+ /**
3353
+ * Storybook default output folder
3354
+ */
3355
+ "storybook-static",
3356
+ /**
3357
+ * Vite.js default build folder
3358
+ */
3359
+ "dist"
3360
+ ];
3361
+ var findDefaultBuildFolder = async () => {
3362
+ const validFolders = await Promise.all(
3363
+ defaultBuildFolders.map(async (directory) => {
3364
+ const allFiles = await getAllFilesInsideADirectory({
3365
+ directory
3366
+ });
3367
+ return { directory, isValid: allFiles.length !== 0 };
3368
+ })
3369
+ );
3370
+ const validFolder = validFolders.reduce((acc, cur) => {
3371
+ if (cur.isValid) {
3372
+ return cur.directory;
3373
+ }
3374
+ return acc;
3375
+ }, "");
3376
+ return validFolder;
3377
+ };
3378
+
3379
+ // src/deploy/staticApp/getStaticAppBucket.ts
3380
+ var STATIC_APP_BUCKET_LOGICAL_ID = "StaticBucket";
3381
+ var getStaticAppBucket = async ({
3382
+ stackName
3383
+ }) => {
3384
+ const params = {
3385
+ LogicalResourceId: STATIC_APP_BUCKET_LOGICAL_ID,
3386
+ StackName: stackName
3387
+ };
3388
+ try {
3389
+ const { StackResourceDetail } = await describeStackResource(params);
3390
+ return _optionalChain([StackResourceDetail, 'optionalAccess', _28 => _28.PhysicalResourceId]);
3391
+ } catch (error) {
3392
+ return void 0;
3393
+ }
3394
+ };
3395
+
3396
+ // src/deploy/staticApp/staticApp.template.ts
3397
+ var PACKAGE_VERSION = getPackageVersion();
3398
+ var STATIC_APP_BUCKET_LOGICAL_ID2 = "StaticBucket";
3399
+ var CLOUDFRONT_DISTRIBUTION_LOGICAL_ID = "CloudFrontDistribution";
3400
+ var CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID = "OriginAccessControl";
3401
+ var ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID = "Route53RecordSetGroup";
3402
+ var ERROR_DOCUMENT = "404/index.html";
3403
+ var CACHE_POLICY_ID = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
3404
+ var ORIGIN_REQUEST_POLICY_ID = "88a5eaf4-2fd4-4709-b370-b4c650ea3fcf";
3405
+ var ORIGIN_RESPONSE_POLICY_ID = "eaab4381-ed33-4a86-88ca-d9558dc6cd63";
3406
+ var getBucketStaticWebsiteTemplate = ({
3407
+ spa
3408
+ }) => {
3409
+ return {
3410
+ AWSTemplateFormatVersion: "2010-09-09",
3411
+ Resources: {
3412
+ [STATIC_APP_BUCKET_LOGICAL_ID2]: {
3413
+ Type: "AWS::S3::Bucket",
3414
+ Properties: {
3415
+ CorsConfiguration: {
3416
+ CorsRules: [
3417
+ {
3418
+ AllowedHeaders: ["*"],
3419
+ AllowedMethods: ["GET"],
3420
+ AllowedOrigins: ["*"],
3421
+ Id: "OpenCors",
3422
+ MaxAge: 600
3423
+ }
3424
+ ]
3425
+ },
3426
+ PublicAccessBlockConfiguration: {
3427
+ BlockPublicPolicy: false
3428
+ },
3429
+ WebsiteConfiguration: {
3430
+ IndexDocument: `index.html`,
3431
+ ErrorDocument: spa ? "index.html" : ERROR_DOCUMENT
3432
+ }
3433
+ }
3434
+ },
3435
+ [`${STATIC_APP_BUCKET_LOGICAL_ID2}S3BucketPolicy`]: {
3436
+ Type: "AWS::S3::BucketPolicy",
3437
+ Properties: {
3438
+ Bucket: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3439
+ PolicyDocument: {
3440
+ Statement: [
3441
+ {
3442
+ Action: ["s3:GetObject"],
3443
+ Effect: "Allow",
3444
+ Principal: "*",
3445
+ Resource: {
3446
+ "Fn::Join": [
3447
+ "",
3448
+ [
3449
+ "arn:aws:s3:::",
3450
+ { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3451
+ "/*"
3452
+ ]
3453
+ ]
3454
+ }
3455
+ }
3456
+ ]
3457
+ }
3458
+ }
3459
+ }
3460
+ },
3461
+ Outputs: {
3462
+ BucketWebsiteURL: {
3463
+ Description: "Bucket static app website URL",
3464
+ Value: {
3465
+ "Fn::GetAtt": [STATIC_APP_BUCKET_LOGICAL_ID2, "WebsiteURL"]
3466
+ }
3467
+ }
3468
+ }
3469
+ };
3470
+ };
3471
+ var getCloudFrontTemplate = ({
3472
+ acm,
3473
+ aliases = [],
3474
+ spa,
3475
+ hostedZoneName
3476
+ }) => {
3477
+ const template = {
3478
+ AWSTemplateFormatVersion: "2010-09-09",
3479
+ Resources: {
3480
+ [STATIC_APP_BUCKET_LOGICAL_ID2]: {
3481
+ Type: "AWS::S3::Bucket",
3482
+ Properties: {
3483
+ PublicAccessBlockConfiguration: {
3484
+ BlockPublicPolicy: false
3485
+ }
3486
+ }
3487
+ },
3488
+ [`${STATIC_APP_BUCKET_LOGICAL_ID2}S3BucketPolicy`]: {
3489
+ Type: "AWS::S3::BucketPolicy",
3490
+ Properties: {
3491
+ Bucket: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3492
+ PolicyDocument: {
3493
+ Statement: [
3494
+ /**
3495
+ * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
3496
+ */
3497
+ {
3498
+ Sid: "AllowCloudFrontServicePrincipalReadOnly",
3499
+ Effect: "Allow",
3500
+ Principal: {
3501
+ Service: "cloudfront.amazonaws.com"
3502
+ },
3503
+ Action: "s3:GetObject",
3504
+ Resource: {
3505
+ "Fn::Join": [
3506
+ "",
3507
+ [
3508
+ "arn:aws:s3:::",
3509
+ { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3510
+ "/*"
3511
+ ]
3512
+ ]
3513
+ },
3514
+ Condition: {
3515
+ StringEquals: {
3516
+ "AWS:SourceArn": (
3517
+ // 'arn:aws:cloudfront::<AWS account ID>:distribution/<CloudFront distribution ID>',
3518
+ {
3519
+ "Fn::Join": [
3520
+ "",
3521
+ [
3522
+ "arn:aws:cloudfront::",
3523
+ { Ref: "AWS::AccountId" },
3524
+ ":distribution/",
3525
+ { Ref: CLOUDFRONT_DISTRIBUTION_LOGICAL_ID }
3526
+ ]
3527
+ ]
3528
+ }
3529
+ )
3530
+ }
3531
+ }
3532
+ }
3533
+ ]
3534
+ }
3535
+ }
3536
+ }
3537
+ }
3538
+ };
3539
+ const cloudFrontResources = {
3540
+ [CLOUDFRONT_DISTRIBUTION_LOGICAL_ID]: {
3541
+ Type: "AWS::CloudFront::Distribution",
3542
+ Properties: {
3543
+ DistributionConfig: {
3544
+ Comment: {
3545
+ "Fn::Sub": [
3546
+ "CloudFront Distribution for ${Project} project.",
3547
+ { Project: { Ref: "Project" } }
3548
+ ]
3549
+ },
3550
+ CustomErrorResponses: [403, 404].map((errorCode) => {
3551
+ if (spa) {
3552
+ return {
3553
+ ErrorCachingMinTTL: 60 * 60 * 24,
3554
+ ErrorCode: errorCode,
3555
+ ResponseCode: 200,
3556
+ ResponsePagePath: "/index.html"
3557
+ };
3558
+ }
3559
+ return {
3560
+ ErrorCachingMinTTL: 0,
3561
+ ErrorCode: errorCode,
3562
+ ResponseCode: 404,
3563
+ ResponsePagePath: "/" + ERROR_DOCUMENT
3564
+ };
3565
+ }),
3566
+ DefaultCacheBehavior: {
3567
+ AllowedMethods: ["GET", "HEAD", "OPTIONS"],
3568
+ Compress: true,
3569
+ CachedMethods: ["GET", "HEAD", "OPTIONS"],
3570
+ /**
3571
+ * Caching OPTIONS. Related to OriginRequestPolicyId property.
3572
+ * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors
3573
+ */
3574
+ OriginRequestPolicyId: ORIGIN_REQUEST_POLICY_ID,
3575
+ /**
3576
+ * CachePolicyId property:
3577
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-cachepolicyid
3578
+ */
3579
+ CachePolicyId: CACHE_POLICY_ID,
3580
+ ResponseHeadersPolicyId: ORIGIN_RESPONSE_POLICY_ID,
3581
+ TargetOriginId: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3582
+ ViewerProtocolPolicy: "redirect-to-https"
3583
+ },
3584
+ DefaultRootObject: "index.html",
3585
+ Enabled: true,
3586
+ HttpVersion: "http2",
3587
+ Origins: [
3588
+ {
3589
+ DomainName: {
3590
+ "Fn::GetAtt": [STATIC_APP_BUCKET_LOGICAL_ID2, "DomainName"]
3591
+ },
3592
+ Id: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
3593
+ OriginAccessControlId: {
3594
+ "Fn::GetAtt": [
3595
+ CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID,
3596
+ "Id"
3597
+ ]
3598
+ },
3599
+ /**
3600
+ * Note: As of September 2022, an empty OriginAccessIdentity must be specified in S3OriginConfig.
3601
+ */
3602
+ S3OriginConfig: {
3603
+ OriginAccessIdentity: ""
3604
+ }
3605
+ }
3606
+ ]
3607
+ }
3608
+ }
3609
+ },
3610
+ [CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID]: {
3611
+ Type: "AWS::CloudFront::OriginAccessControl",
3612
+ Properties: {
3613
+ OriginAccessControlConfig: {
3614
+ Description: {
3615
+ "Fn::Sub": [
3616
+ "Default Origin Access Control for ${Project} project.",
3617
+ { Project: { Ref: "Project" } }
3618
+ ]
3619
+ },
3620
+ Name: {
3621
+ Ref: "AWS::StackName"
3622
+ },
3623
+ OriginAccessControlOriginType: "s3",
3624
+ SigningBehavior: "always",
3625
+ SigningProtocol: "sigv4"
3626
+ }
3627
+ }
3628
+ }
3629
+ };
3630
+ if (acm) {
3631
+ const acmRegex = /^arn:aws:acm:[-a-z0-9]+:\d{12}:certificate\/[-a-z0-9]+$/;
3632
+ const acmCertificateArn = acmRegex.test(acm) ? acm : {
3633
+ "Fn::ImportValue": acm
3634
+ };
3635
+ cloudFrontResources.CloudFrontDistribution.Properties.DistributionConfig = {
3636
+ ...cloudFrontResources.CloudFrontDistribution.Properties.DistributionConfig,
3637
+ Aliases: aliases || { Ref: "AWS::NoValue" },
3638
+ ViewerCertificate: {
3639
+ AcmCertificateArn: acmCertificateArn,
3640
+ /**
3641
+ * AWS CloudFront recommendation.
3642
+ */
3643
+ MinimumProtocolVersion: "TLSv1.2_2021",
3644
+ SslSupportMethod: "sni-only"
3645
+ }
3646
+ };
3647
+ }
3648
+ if (hostedZoneName && aliases) {
3649
+ const recordSets = aliases.map((alias) => {
3650
+ if (alias === hostedZoneName) {
3651
+ return {
3652
+ AliasTarget: {
3653
+ DNSName: {
3654
+ "Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
3655
+ },
3656
+ /**
3657
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
3658
+ */
3659
+ HostedZoneId: "Z2FDTNDATAQYW2"
3660
+ },
3661
+ Name: alias,
3662
+ Type: "A"
3663
+ };
3664
+ }
3665
+ return {
3666
+ Name: alias,
3667
+ ResourceRecords: [
3668
+ {
3669
+ "Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
3670
+ }
3671
+ ],
3672
+ TTL: 60,
3673
+ Type: "CNAME"
3674
+ };
3675
+ });
3676
+ const route53RecordSetGroupResources = {
3677
+ [ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID]: {
3678
+ Type: "AWS::Route53::RecordSetGroup",
3679
+ DependsOn: [CLOUDFRONT_DISTRIBUTION_LOGICAL_ID],
3680
+ Properties: {
3681
+ // https://forums.aws.amazon.com/thread.jspa?threadID=103919
3682
+ HostedZoneName: `${hostedZoneName}${hostedZoneName.endsWith(".") ? "" : "."}`,
3683
+ RecordSets: recordSets
3684
+ }
3685
+ }
3686
+ };
3687
+ template.Resources = {
3688
+ ...template.Resources,
3689
+ ...route53RecordSetGroupResources
3690
+ };
3691
+ }
3692
+ template.Resources = { ...template.Resources, ...cloudFrontResources };
3693
+ const aliasesOutput = (aliases || []).reduce(
3694
+ (acc, alias, index) => {
3695
+ return {
3696
+ ...acc,
3697
+ [`Alias${index}URL`]: {
3698
+ Value: `https://${alias}`
3699
+ }
3700
+ };
3701
+ },
3702
+ {}
3703
+ );
3704
+ template.Outputs = {
3705
+ ...template.Outputs,
3706
+ ...aliasesOutput,
3707
+ CloudFrontURL: {
3708
+ Value: {
3709
+ "Fn::Join": [
3710
+ "",
3711
+ [
3712
+ "https://",
3713
+ {
3714
+ "Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
3715
+ }
3716
+ ]
3717
+ ]
3718
+ }
3719
+ },
3720
+ CloudFrontDistributionId: {
3721
+ Value: {
3722
+ Ref: CLOUDFRONT_DISTRIBUTION_LOGICAL_ID
3723
+ }
3724
+ },
3725
+ CurrentVersion: {
3726
+ Value: PACKAGE_VERSION
3727
+ }
3728
+ };
3729
+ return template;
3730
+ };
3731
+ var getStaticAppTemplate = ({
3732
+ acm,
3733
+ aliases,
3734
+ cloudfront,
3735
+ spa,
3736
+ hostedZoneName,
3737
+ region
3738
+ }) => {
3739
+ if (cloudfront) {
3740
+ return getCloudFrontTemplate({
3741
+ acm,
3742
+ aliases,
3743
+ cloudfront,
3744
+ spa,
3745
+ hostedZoneName,
3746
+ region
3747
+ });
3748
+ }
3749
+ return getBucketStaticWebsiteTemplate({ spa });
3750
+ };
3751
+
3752
+ // src/deploy/staticApp/invalidateCloudFront.ts
3753
+
3754
+
3755
+ var CLOUDFRONT_DISTRIBUTION_ID = "CloudFrontDistributionId";
3756
+ var logPrefix14 = "static-app";
3757
+ var invalidateCloudFront = async ({
3758
+ outputs
3759
+ }) => {
3760
+ _npmlog2.default.info(logPrefix14, "Invalidating CloudFront...");
3761
+ if (!outputs) {
3762
+ _npmlog2.default.info(logPrefix14, "Invalidation: outputs do not exist.");
3763
+ return;
3764
+ }
3765
+ const cloudFrontDistributionIDOutput = outputs.find(
3766
+ (output) => output.OutputKey === CLOUDFRONT_DISTRIBUTION_ID
3767
+ );
3768
+ if (_optionalChain([cloudFrontDistributionIDOutput, 'optionalAccess', _29 => _29.OutputValue])) {
3769
+ const distributionId = cloudFrontDistributionIDOutput.OutputValue;
3770
+ const params = {
3771
+ DistributionId: distributionId,
3772
+ InvalidationBatch: {
3773
+ CallerReference: (/* @__PURE__ */ new Date()).toISOString(),
3774
+ Paths: {
3775
+ Items: ["/*"],
3776
+ Quantity: 1
3777
+ }
3778
+ }
3779
+ };
3780
+ const cloudFront = new (0, _awssdk.CloudFront)();
3781
+ try {
3782
+ await cloudFront.createInvalidation(params).promise();
3783
+ _npmlog2.default.info(
3784
+ logPrefix14,
3785
+ `CloudFront Distribution ID ${distributionId} invalidated with success.`
3786
+ );
3787
+ } catch (err) {
3788
+ _npmlog2.default.error(
3789
+ logPrefix14,
3790
+ `Error while trying to invalidate CloudFront distribution ${distributionId}.`
3791
+ );
3792
+ _npmlog2.default.error(logPrefix14, err);
3793
+ }
3794
+ } else {
3795
+ _npmlog2.default.info(
3796
+ logPrefix14,
3797
+ `Cannot invalidate because distribution does not exist.`
3798
+ );
3799
+ }
3800
+ };
3801
+
3802
+ // src/deploy/staticApp/removeOldVersions.ts
3803
+
3804
+ var _semver = require('semver'); var _semver2 = _interopRequireDefault(_semver);
3805
+ var logPrefix15 = "static-app";
3806
+ var removeOldVersions = async ({ bucket }) => {
3807
+ try {
3808
+ _npmlog2.default.info(logPrefix15, "Removing old versions...");
3809
+ const { CommonPrefixes = [] } = await s3.listObjectsV2({ Bucket: bucket, Delimiter: "/" }).promise();
3810
+ const versions = _optionalChain([CommonPrefixes, 'optionalAccess', _30 => _30.map, 'call', _31 => _31(({ Prefix }) => {
3811
+ return _optionalChain([Prefix, 'optionalAccess', _32 => _32.replace, 'call', _33 => _33("/", "")]);
3812
+ }), 'access', _34 => _34.filter, 'call', _35 => _35((version) => {
3813
+ return _semver2.default.valid(version);
3814
+ }), 'access', _36 => _36.sort, 'call', _37 => _37((a, b) => {
3815
+ return _semver2.default.gt(a, b) ? -1 : 1;
3816
+ })]);
3817
+ versions.shift();
3818
+ versions.shift();
3819
+ versions.shift();
3820
+ await Promise.all(
3821
+ versions.map((version) => {
3822
+ return deleteS3Directory({ bucket, directory: `${version}` });
3823
+ })
3824
+ );
3825
+ } catch (error) {
3826
+ _npmlog2.default.info(
3827
+ logPrefix15,
3828
+ `Cannot remove older versions from "${bucket}" bucket.`
3829
+ );
3830
+ }
3831
+ };
3832
+
3833
+ // src/deploy/staticApp/uploadBuiltAppToS3.ts
3834
+ var uploadBuiltAppToS3 = async ({
3835
+ buildFolder: directory,
3836
+ bucket
3837
+ }) => {
3838
+ if (directory) {
3839
+ const files = await getAllFilesInsideADirectory({ directory });
3840
+ if (files.length > 0) {
3841
+ await emptyS3Directory({ bucket });
3842
+ }
3843
+ await uploadDirectoryToS3({ bucket, directory });
3844
+ return;
3845
+ }
3846
+ const defaultDirectory = await findDefaultBuildFolder();
3847
+ if (defaultDirectory) {
3848
+ await emptyS3Directory({ bucket });
3849
+ await uploadDirectoryToS3({ bucket, directory: defaultDirectory });
3850
+ await copyRoot404To404Index({ bucket });
3851
+ return;
3852
+ }
3853
+ throw new Error(
3854
+ `build-folder option wasn't provided and files weren't found in ${defaultBuildFolders.join(
3855
+ ", "
3856
+ )} directories.`
3857
+ );
3858
+ };
3859
+
3860
+ // src/deploy/staticApp/deployStaticApp.ts
3861
+ var logPrefix16 = "static-app";
3862
+ var deployStaticApp = async ({
3863
+ acm,
3864
+ aliases,
3865
+ buildFolder,
3866
+ cloudfront,
3867
+ spa,
3868
+ hostedZoneName,
3869
+ region,
3870
+ skipUpload
3871
+ }) => {
3872
+ try {
3873
+ const { stackName } = await handleDeployInitialization({ logPrefix: logPrefix16 });
3874
+ const params = { StackName: stackName };
3875
+ const template = getStaticAppTemplate({
3876
+ acm,
3877
+ aliases,
3878
+ cloudfront,
3879
+ spa,
3880
+ hostedZoneName,
3881
+ region
3882
+ });
3883
+ const bucket = await getStaticAppBucket({ stackName });
3884
+ if (bucket) {
3885
+ if (!skipUpload) {
3886
+ await uploadBuiltAppToS3({ buildFolder, bucket, cloudfront });
3887
+ }
3888
+ const { Outputs } = await deploy({ params, template });
3889
+ await invalidateCloudFront({ outputs: Outputs });
3890
+ if (!skipUpload) {
3891
+ await removeOldVersions({ bucket });
3892
+ }
3893
+ } else {
3894
+ await deploy({ params, template });
3895
+ const newBucket = await getStaticAppBucket({ stackName });
3896
+ if (!newBucket) {
3897
+ throw new Error(`Cannot find bucket at ${stackName}.`);
3898
+ }
3899
+ await uploadBuiltAppToS3({ buildFolder, bucket: newBucket, cloudfront });
3900
+ }
3901
+ } catch (error) {
3902
+ handleDeployError({ error, logPrefix: logPrefix16 });
3903
+ }
3904
+ };
3905
+
3906
+ // src/deploy/staticApp/command.ts
3907
+
3908
+ var options3 = {
3909
+ acm: {
3910
+ 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.",
3911
+ type: "string"
3912
+ },
3913
+ aliases: {
3914
+ describe: "The aliases that will be associated with the CloudFront. See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html",
3915
+ implies: ["acm"],
3916
+ type: "array"
3917
+ },
3918
+ "build-folder": {
3919
+ describe: `The folder that will be uploaded. If not provided, it'll search for the folders "${defaultBuildFolders.join(
3920
+ ", "
3921
+ )}."`,
3922
+ type: "string"
3923
+ },
3924
+ cloudfront: {
3925
+ default: false,
3926
+ describe: "A CloudFront resource is created along with S3 if this option is `true`.",
3927
+ require: false,
3928
+ type: "boolean"
3929
+ },
3930
+ "hosted-zone-name": {
3931
+ required: false,
3932
+ describe: `Is the name of a Route 53 hosted zone. If this value is provided, ${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".`,
3933
+ type: "string"
3934
+ },
3935
+ /**
3936
+ * CloudFront triggers can be only in US East (N. Virginia) Region.
3937
+ * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers
3938
+ */
3939
+ region: {
3940
+ coerce: () => {
3941
+ return CLOUDFRONT_REGION;
3942
+ },
3943
+ default: CLOUDFRONT_REGION,
3944
+ hidden: true,
3945
+ type: "string"
3946
+ },
3947
+ "skip-upload": {
3948
+ default: false,
3949
+ describe: "Skip files upload to S3. Useful when wanting update only CloudFormation.",
3950
+ type: "boolean"
3951
+ },
3952
+ spa: {
3953
+ default: false,
3954
+ describe: "This option enables CloudFront to serve a single page application (SPA).",
3955
+ require: false,
3956
+ type: "boolean"
3957
+ }
3958
+ };
3959
+ var deployStaticAppCommand = {
3960
+ command: "static-app",
3961
+ describe: "Deploy static app.",
3962
+ builder: (yargs3) => {
3963
+ return yargs3.options(addGroupToOptions(options3, "Deploy Static App Options")).middleware(() => {
3964
+ _awssdk2.default.config.region = CLOUDFRONT_REGION;
3965
+ });
3966
+ },
3967
+ handler: ({ destroy: destroy2, ...rest }) => {
3968
+ if (destroy2) {
3969
+ destroyCloudFormation();
3970
+ } else {
3971
+ deployStaticApp(rest);
3972
+ }
3973
+ }
3974
+ };
3975
+
3976
+ // src/deploy/vercel/deployVercel.ts
3977
+
3978
+ var logPrefix17 = "deploy vercel";
3979
+ var makeCommand = (cmds) => {
3980
+ return cmds.filter((cmd) => {
3981
+ return cmd !== void 0 && cmd !== null && cmd !== "";
3982
+ }).join(" ");
3983
+ };
3984
+ var deployVercel = async ({ token }) => {
3985
+ try {
3986
+ _npmlog2.default.info(logPrefix17, "Deploying on Vercel...");
3987
+ const environment = getEnvironment();
3988
+ const finalToken = token || process.env.VERCEL_TOKEN;
3989
+ if (!finalToken) {
3990
+ throw new Error("Missing Vercel token");
3991
+ }
3992
+ const cmdToken = finalToken ? `--token=${finalToken}` : "";
3993
+ const cmdProdFlag = environment === "Production" ? "--prod" : "";
3994
+ const cmdEnvironment = `--environment=${environment === "Production" ? "production" : "preview"}`;
3995
+ const pullCmd = makeCommand([
3996
+ "vercel",
3997
+ "pull",
3998
+ "--yes",
3999
+ cmdEnvironment,
4000
+ cmdToken
4001
+ ]);
4002
+ await spawn(pullCmd);
4003
+ const buildCdm = makeCommand(["vercel", "build", cmdProdFlag, cmdToken]);
4004
+ await spawn(buildCdm);
4005
+ const deployCmd = makeCommand([
4006
+ "vercel",
4007
+ "deploy",
4008
+ "--prebuilt",
4009
+ cmdProdFlag,
4010
+ cmdToken
4011
+ ]);
4012
+ await spawn(deployCmd);
4013
+ } catch (error) {
4014
+ handleDeployError({ error, logPrefix: logPrefix17 });
4015
+ }
4016
+ };
4017
+
4018
+ // src/deploy/vercel/command.ts
4019
+
4020
+ var logPrefix18 = "deploy vercel";
4021
+ var options4 = {
4022
+ token: {
4023
+ describe: "Vercel authorization token.",
4024
+ type: "string"
4025
+ }
4026
+ };
4027
+ var deployVercelCommand = {
4028
+ command: "vercel",
4029
+ describe: "Deploy on Vercel.",
4030
+ builder: (yargs3) => {
4031
+ return yargs3.options(
4032
+ addGroupToOptions(options4, "Deploy on Vercel Options")
4033
+ );
4034
+ },
4035
+ handler: ({ destroy: destroy2, ...rest }) => {
4036
+ if (destroy2) {
4037
+ _npmlog2.default.info(logPrefix18, "Destroy Vercel deployment not implemented yet.");
4038
+ } else {
4039
+ deployVercel(rest);
4040
+ }
4041
+ }
4042
+ };
4043
+
4044
+ // src/deploy/readDockerfile.ts
4045
+
4046
+
4047
+ var readDockerfile = (dockerfilePath) => {
4048
+ try {
4049
+ return fs11.readFileSync(path9.join(process.cwd(), dockerfilePath), "utf8");
4050
+ } catch (e3) {
4051
+ return "";
4052
+ }
4053
+ };
4054
+
4055
+ // src/deploy/command.ts
4056
+
4057
+ var logPrefix19 = "deploy";
4058
+ var checkAwsAccountId = async (awsAccountId) => {
4059
+ try {
4060
+ const currentAwsAccountId = await getAwsAccountId();
4061
+ if (String(awsAccountId) !== String(currentAwsAccountId)) {
4062
+ throw new Error(
4063
+ `AWS account id does not match. Current is "${currentAwsAccountId}" but the defined in configuration files is "${awsAccountId}".`
4064
+ );
4065
+ }
4066
+ } catch (error) {
4067
+ if (error.code === "CredentialsError") {
4068
+ return;
4069
+ }
4070
+ _npmlog2.default.error(logPrefix19, error.message);
4071
+ process.exit();
4072
+ }
4073
+ };
4074
+ var describeDeployCommand = {
4075
+ command: "describe",
4076
+ describe: "Print the outputs of the deployment.",
4077
+ handler: async ({ stackName }) => {
4078
+ try {
4079
+ const newStackName = stackName || await getStackName();
4080
+ await printStackOutputsAfterDeploy({ stackName: newStackName });
4081
+ } catch (error) {
4082
+ _npmlog2.default.info(logPrefix19, "Cannot describe stack. Message: %s", error.message);
4083
+ }
4084
+ }
4085
+ };
4086
+ var options5 = {
4087
+ "aws-account-id": {
4088
+ description: "AWS account id associated with the deployment.",
4089
+ type: "string"
4090
+ },
4091
+ destroy: {
4092
+ default: false,
4093
+ description: 'Destroy the deployment. You cannot destroy a deploy with "environment" is defined.',
4094
+ type: "boolean"
4095
+ },
4096
+ "lambda-dockerfile": {
4097
+ coerce: (arg) => {
4098
+ return readDockerfile(arg);
4099
+ },
4100
+ default: "Dockerfile",
4101
+ describe: "Instructions to create the Lambda image.",
4102
+ type: "string"
4103
+ },
4104
+ "lambda-image": {
4105
+ default: false,
4106
+ describe: "A Lambda image will be created instead using S3.",
4107
+ type: "boolean"
4108
+ },
4109
+ "lambda-externals": {
4110
+ default: [],
4111
+ describe: "Lambda external packages.",
4112
+ type: "array"
4113
+ },
4114
+ "lambda-input": {
4115
+ default: "src/lambda.ts",
4116
+ describe: "Lambda input file. This file export all handlers used by the Lambda Functions.",
4117
+ type: "string"
4118
+ },
4119
+ /**
4120
+ * This option has the format:
4121
+ *
4122
+ * ```ts
4123
+ * {
4124
+ * key: string,
4125
+ * value: string,
4126
+ * usePreviousValue: boolean,
4127
+ * resolvedValue: string
4128
+ * }[]
4129
+ * ```
4130
+ */
4131
+ parameters: {
4132
+ alias: "p",
4133
+ default: [],
4134
+ describe: "A list of parameters that will be passed to CloudFormation Parameters when deploying."
4135
+ },
4136
+ "skip-deploy": {
4137
+ alias: "skip",
4138
+ default: false,
4139
+ describe: "Skip deploy.",
4140
+ type: "boolean"
4141
+ },
4142
+ "stack-name": {
4143
+ describe: "CloudFormation Stack name.",
4144
+ type: "string"
4145
+ },
4146
+ "template-path": {
4147
+ alias: "t",
4148
+ type: "string"
4149
+ }
4150
+ };
4151
+ var examples = [
4152
+ [
4153
+ "carlin deploy -t src/cloudformation.template1.yml",
4154
+ "Change the CloudFormation template path."
4155
+ ],
4156
+ ["carlin deploy -e Production", "Set environment."],
4157
+ [
4158
+ "carlin deploy --lambda-input src/lambda/index.ts --lambda-externals momentjs",
4159
+ "Lambda exists. Don't bundle momentjs."
4160
+ ],
4161
+ [
4162
+ "carlin deploy --destroy --stack-name StackToBeDeleted",
4163
+ "Destroy a specific stack."
4164
+ ]
4165
+ ];
4166
+ var deployCommand = {
4167
+ command: "deploy [deploy]",
4168
+ describe: "Deploy cloud resources.",
4169
+ builder: (yargsBuilder) => {
4170
+ yargsBuilder.example(examples).options(addGroupToOptions(options5, "Deploy Options")).middleware(({ stackName }) => {
4171
+ if (stackName) {
4172
+ setPreDefinedStackName(stackName);
4173
+ }
4174
+ }).middleware((argv) => {
4175
+ if (argv.lambdaDockerfile) {
4176
+ Object.assign(argv, {
4177
+ lambdaImage: true
4178
+ });
4179
+ }
4180
+ }).middleware(
4181
+ async ({
4182
+ environments,
4183
+ environment,
4184
+ awsAccountId: defaultAwsAccountId
4185
+ }) => {
4186
+ const envAwsAccountId = (() => {
4187
+ return environments && environment && environments[environment] ? environments[environment].awsAccountId : void 0;
4188
+ })();
4189
+ if (envAwsAccountId) {
4190
+ await checkAwsAccountId(envAwsAccountId);
4191
+ }
4192
+ if (defaultAwsAccountId) {
4193
+ await checkAwsAccountId(defaultAwsAccountId);
4194
+ }
4195
+ }
4196
+ ).middleware(({ skipDeploy }) => {
4197
+ if (skipDeploy) {
4198
+ _npmlog2.default.warn(
4199
+ logPrefix19,
4200
+ "Skip deploy flag is true, then the deploy command wasn't executed."
4201
+ );
4202
+ process.exit(0);
4203
+ }
4204
+ });
4205
+ const commands = [
4206
+ deployLambdaLayerCommand,
4207
+ describeDeployCommand,
4208
+ deployBaseStackCommand,
4209
+ deployStaticAppCommand,
4210
+ deployCicdCommand,
4211
+ deployVercelCommand
4212
+ ];
4213
+ yargsBuilder.positional("deploy", {
4214
+ choices: commands.map(({ command }) => {
4215
+ return command;
4216
+ }),
4217
+ describe: "Type of deployment.",
4218
+ type: "string"
4219
+ });
4220
+ commands.forEach((command) => {
4221
+ return yargsBuilder.command(command);
4222
+ });
4223
+ return yargsBuilder;
4224
+ },
4225
+ handler: ({ destroy: destroy2, ...rest }) => {
4226
+ if (destroy2) {
4227
+ destroyCloudFormation();
4228
+ } else {
4229
+ deployCloudFormation(rest);
4230
+ }
4231
+ }
4232
+ };
4233
+
4234
+ // src/deploy/cicd/ecsTaskReportCommand.ts
4235
+
4236
+
4237
+ var logPrefix20 = "cicd-ecs-task-report";
4238
+ var sendEcsTaskReport = async ({ status }) => {
4239
+ if (!process.env.ECS_TASK_REPORT_HANDLER_NAME) {
4240
+ _npmlog2.default.info(logPrefix20, "ECS_TASK_REPORT_HANDLER_NAME not defined.");
4241
+ return;
4242
+ }
4243
+ const lambda = new _awssdk2.default.Lambda();
4244
+ const payload = { status };
4245
+ if (process.env.ECS_TASK_ARN) {
4246
+ payload.ecsTaskArn = process.env.ECS_TASK_ARN;
4247
+ }
4248
+ if (process.env.PIPELINE_NAME) {
4249
+ payload.pipelineName = process.env.PIPELINE_NAME;
4250
+ }
4251
+ await lambda.invokeAsync({
4252
+ FunctionName: process.env.ECS_TASK_REPORT_HANDLER_NAME,
4253
+ InvokeArgs: JSON.stringify(payload)
4254
+ }).promise();
4255
+ _npmlog2.default.info(logPrefix20, "Report sent.");
4256
+ };
4257
+ var options6 = {
4258
+ status: {
4259
+ choices: ["Approved", "Rejected", "MainTagFound"],
4260
+ demandOption: true,
4261
+ type: "string"
4262
+ }
4263
+ };
4264
+ var ecsTaskReportCommand = {
4265
+ command: "cicd-ecs-task-report",
4266
+ describe: false,
4267
+ builder: (yargs3) => {
4268
+ return yargs3.options(options6);
4269
+ },
4270
+ handler: async (args) => {
4271
+ return sendEcsTaskReport(args);
4272
+ }
4273
+ };
4274
+
4275
+ // src/generateEnv/generateEnv.ts
4276
+
4277
+
4278
+
4279
+ var logPrefix21 = "generate-env";
4280
+ var readEnvFile = async ({
4281
+ envFileName,
4282
+ envsPath
4283
+ }) => {
4284
+ try {
4285
+ const content = await fs12.promises.readFile(
4286
+ path10.resolve(process.cwd(), envsPath, envFileName),
4287
+ "utf8"
4288
+ );
4289
+ return content;
4290
+ } catch (e4) {
4291
+ return void 0;
4292
+ }
4293
+ };
4294
+ var writeEnvFile = async ({
4295
+ envFileName,
4296
+ content
4297
+ }) => {
4298
+ return fs12.promises.writeFile(
4299
+ path10.resolve(process.cwd(), envFileName),
4300
+ content
4301
+ );
4302
+ };
4303
+ var generateEnv = async ({
4304
+ defaultEnvironment,
4305
+ path: envsPath
4306
+ }) => {
4307
+ const environment = getEnvironment() || defaultEnvironment;
4308
+ const envFileName = `.env.${environment}`;
4309
+ const envFile = await readEnvFile({ envFileName, envsPath });
4310
+ if (!envFile) {
4311
+ _npmlog2.default.info(
4312
+ logPrefix21,
4313
+ "Env file %s doesn't exist. Skip generating env file.",
4314
+ envFileName
4315
+ );
4316
+ return;
4317
+ }
4318
+ await writeEnvFile({ content: envFile, envFileName: ".env" });
4319
+ _npmlog2.default.info(
4320
+ logPrefix21,
4321
+ "Generate env file %s from %s successfully.",
4322
+ ".env",
4323
+ envFileName
4324
+ );
4325
+ };
4326
+
4327
+ // src/generateEnv/generateEnvCommand.ts
4328
+ var DEFAULT_ENVIRONMENT = "Staging";
4329
+ var options7 = {
4330
+ "default-environment": {
4331
+ alias: "d",
4332
+ type: "string",
4333
+ describe: "Default environment.",
4334
+ default: DEFAULT_ENVIRONMENT
4335
+ },
4336
+ path: {
4337
+ alias: "p",
4338
+ type: "string",
4339
+ describe: "Path to the directory where envs files are located.",
4340
+ default: "./"
4341
+ }
4342
+ };
4343
+ var generateEnvCommand = {
4344
+ command: ["generate-env", "ge", "env"],
4345
+ describe: "Generate environment files.",
4346
+ builder: (yargs3) => {
4347
+ return yargs3.options(options7);
4348
+ },
4349
+ handler: (args) => {
4350
+ return generateEnv(args);
4351
+ }
4352
+ };
4353
+
4354
+ // src/cli.ts
4355
+
4356
+
4357
+ var _deepequal = require('deep-equal'); var _deepequal2 = _interopRequireDefault(_deepequal);
4358
+
4359
+
4360
+
4361
+
4362
+ var coerceSetEnvVar = (env) => {
4363
+ return (value) => {
4364
+ setEnvVar(env, value);
4365
+ return value;
4366
+ };
4367
+ };
4368
+ var options8 = {
4369
+ branch: {
4370
+ coerce: coerceSetEnvVar("BRANCH"),
4371
+ require: false,
4372
+ type: "string"
4373
+ },
4374
+ config: {
4375
+ alias: "c",
4376
+ describe: "Path to config file. You can create a config file and set all options there. Valid extensions: .js, .json, .ts, .yml, or .yaml.",
4377
+ require: false,
4378
+ type: "string"
4379
+ },
4380
+ environment: {
4381
+ alias: ["e", "env"],
4382
+ coerce: coerceSetEnvVar("ENVIRONMENT"),
4383
+ type: "string"
4384
+ },
4385
+ environments: {},
4386
+ project: {
4387
+ coerce: coerceSetEnvVar("PROJECT"),
4388
+ require: false,
4389
+ type: "string"
4390
+ },
4391
+ region: {
4392
+ alias: "r",
4393
+ // coerce: coerceSetEnvVar('REGION'),
4394
+ default: AWS_DEFAULT_REGION,
4395
+ describe: "AWS region.",
4396
+ type: "string"
4397
+ }
4398
+ };
4399
+ var getPkgConfig = () => {
4400
+ return NAME;
4401
+ };
4402
+ var getEnv = () => {
4403
+ return _changecase.constantCase.call(void 0, NAME);
4404
+ };
4405
+ var cli = () => {
4406
+ let finalConfig;
4407
+ const getConfig = () => {
4408
+ const names = ["js", "yml", "yaml", "json", "ts"].map((ext) => {
4409
+ return `${NAME}.${ext}`;
4410
+ });
4411
+ const paths = [];
4412
+ let currentPath = process.cwd();
4413
+ let findUpPath;
4414
+ do {
4415
+ findUpPath = _findupsync2.default.call(void 0, names, { cwd: currentPath });
4416
+ if (findUpPath) {
4417
+ currentPath = path2.default.resolve(findUpPath, "../..");
4418
+ paths.push(findUpPath);
4419
+ }
4420
+ } while (findUpPath);
4421
+ const configs = paths.map((p) => {
4422
+ return readObjectFile({ path: p }) || {};
4423
+ });
4424
+ finalConfig = _deepmerge2.default.all(configs.reverse());
4425
+ return finalConfig;
4426
+ };
4427
+ return _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).strictCommands().scriptName(NAME).env(getEnv()).options(addGroupToOptions(options8, "Common Options")).middleware((argv, { parsed }) => {
4428
+ const { environment, environments } = argv;
4429
+ if (environment && environments && environments[environment]) {
4430
+ Object.entries(environments[environment]).forEach(([key, value]) => {
4431
+ const isKeyFromCli = (() => {
4432
+ const paramCaseKey = _changecase.paramCase.call(void 0, key);
4433
+ if (_optionalChain([parsed, 'optionalAccess', _38 => _38.defaulted, 'optionalAccess', _39 => _39[paramCaseKey]])) {
4434
+ return false;
4435
+ }
4436
+ if (_deepequal2.default.call(void 0, argv[key], finalConfig[key])) {
4437
+ return false;
4438
+ }
4439
+ return true;
4440
+ })();
4441
+ if (!isKeyFromCli) {
4442
+ argv[key] = value;
4443
+ }
4444
+ });
4445
+ }
4446
+ }).middleware(({ environment }) => {
4447
+ if (!["string", "undefined"].includes(typeof environment)) {
4448
+ throw new Error(
4449
+ `environment type is invalid. The value: ${JSON.stringify(
4450
+ environment
4451
+ )}`
4452
+ );
4453
+ }
4454
+ }).middleware(({ region }) => {
4455
+ _awssdk2.default.config.region = region;
4456
+ setEnvVar("REGION", region);
4457
+ }).pkgConf(getPkgConfig()).config(getConfig()).config("config", (configPath) => {
4458
+ return readObjectFile({ path: configPath });
4459
+ }).command({
4460
+ command: "print-args",
4461
+ describe: false,
4462
+ handler: (argv) => {
4463
+ return console.log(JSON.stringify(argv, null, 2));
4464
+ }
4465
+ }).command(deployCommand).command(ecsTaskReportCommand).command(generateEnvCommand).epilogue(
4466
+ "For more information, read our docs at https://ttoss.dev/docs/carlin/"
4467
+ ).help();
4468
+ };
4469
+
4470
+ // src/index.ts
4471
+ cli().parse();