carlin 1.31.11 → 1.31.13

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