skuba 0.0.0-master-20231121000709 → 0.0.0-master-20240206001217

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 (207) hide show
  1. package/README.md +3 -3
  2. package/jest/transform.test.ts +3 -1
  3. package/lib/api/jest/index.d.ts +1 -1
  4. package/lib/api/jest/index.js.map +2 -2
  5. package/lib/cli/adapter/prettier.d.ts +1 -1
  6. package/lib/cli/adapter/prettier.js +11 -10
  7. package/lib/cli/adapter/prettier.js.map +2 -2
  8. package/lib/cli/build/index.js +0 -2
  9. package/lib/cli/build/index.js.map +2 -2
  10. package/lib/cli/buildPackage.js +0 -2
  11. package/lib/cli/buildPackage.js.map +2 -2
  12. package/lib/cli/configure/analyseConfiguration.d.ts +2 -0
  13. package/lib/cli/configure/analyseConfiguration.js.map +2 -2
  14. package/lib/cli/configure/getEntryPoint.js +1 -1
  15. package/lib/cli/configure/getEntryPoint.js.map +2 -2
  16. package/lib/cli/configure/getProjectType.js +1 -1
  17. package/lib/cli/configure/getProjectType.js.map +2 -2
  18. package/lib/cli/configure/index.js +11 -8
  19. package/lib/cli/configure/index.js.map +2 -2
  20. package/lib/cli/configure/modules/index.js +0 -2
  21. package/lib/cli/configure/modules/index.js.map +2 -2
  22. package/lib/cli/configure/modules/package.d.ts +1 -1
  23. package/lib/cli/configure/modules/package.js +2 -1
  24. package/lib/cli/configure/modules/package.js.map +2 -2
  25. package/lib/cli/configure/patchRenovateConfig.d.ts +2 -1
  26. package/lib/cli/configure/patchRenovateConfig.js +23 -10
  27. package/lib/cli/configure/patchRenovateConfig.js.map +2 -2
  28. package/lib/cli/configure/types.d.ts +2 -0
  29. package/lib/cli/configure/types.js.map +1 -1
  30. package/lib/cli/configure/upgrade/index.d.ts +15 -0
  31. package/lib/cli/configure/upgrade/index.js +130 -0
  32. package/lib/cli/configure/upgrade/index.js.map +7 -0
  33. package/lib/cli/configure/{addEmptyExports.d.ts → upgrade/patches/7.3.1/addEmptyExports.d.ts} +2 -2
  34. package/lib/cli/configure/{addEmptyExports.js → upgrade/patches/7.3.1/addEmptyExports.js} +15 -11
  35. package/lib/cli/configure/upgrade/patches/7.3.1/addEmptyExports.js.map +7 -0
  36. package/lib/cli/configure/upgrade/patches/7.3.1/index.d.ts +2 -0
  37. package/lib/cli/configure/upgrade/patches/7.3.1/index.js +55 -0
  38. package/lib/cli/configure/upgrade/patches/7.3.1/index.js.map +7 -0
  39. package/lib/cli/configure/upgrade/patches/7.3.1/moveNpmrcOutOfGitignoreManagedSection.d.ts +2 -0
  40. package/lib/cli/configure/upgrade/patches/7.3.1/moveNpmrcOutOfGitignoreManagedSection.js +94 -0
  41. package/lib/cli/configure/upgrade/patches/7.3.1/moveNpmrcOutOfGitignoreManagedSection.js.map +7 -0
  42. package/lib/cli/configure/upgrade/patches/7.3.1/patchDockerfile.d.ts +2 -0
  43. package/lib/cli/configure/{patchDockerfile.js → upgrade/patches/7.3.1/patchDockerfile.js} +18 -12
  44. package/lib/cli/configure/upgrade/patches/7.3.1/patchDockerfile.js.map +7 -0
  45. package/lib/cli/configure/upgrade/patches/7.3.1/patchServerListener.d.ts +2 -0
  46. package/lib/cli/configure/{patchServerListener.js → upgrade/patches/7.3.1/patchServerListener.js} +18 -14
  47. package/lib/cli/configure/upgrade/patches/7.3.1/patchServerListener.js.map +7 -0
  48. package/lib/cli/format.js +7 -14
  49. package/lib/cli/format.js.map +2 -2
  50. package/lib/cli/init/getConfig.d.ts +4 -2
  51. package/lib/cli/init/getConfig.js +53 -26
  52. package/lib/cli/init/getConfig.js.map +2 -2
  53. package/lib/cli/init/git.d.ts +2 -1
  54. package/lib/cli/init/git.js +2 -9
  55. package/lib/cli/init/git.js.map +2 -2
  56. package/lib/cli/init/index.d.ts +1 -1
  57. package/lib/cli/init/index.js +19 -9
  58. package/lib/cli/init/index.js.map +2 -2
  59. package/lib/cli/init/prompts.d.ts +26 -3
  60. package/lib/cli/init/prompts.js +10 -2
  61. package/lib/cli/init/prompts.js.map +2 -2
  62. package/lib/cli/init/types.d.ts +21 -0
  63. package/lib/cli/init/types.js +5 -1
  64. package/lib/cli/init/types.js.map +2 -2
  65. package/lib/cli/init/writePackageJson.d.ts +6 -0
  66. package/lib/cli/init/writePackageJson.js.map +2 -2
  67. package/lib/cli/lint/annotate/buildkite/index.d.ts +2 -1
  68. package/lib/cli/lint/annotate/buildkite/index.js +5 -3
  69. package/lib/cli/lint/annotate/buildkite/index.js.map +2 -2
  70. package/lib/cli/lint/annotate/buildkite/internal.d.ts +2 -0
  71. package/lib/cli/lint/annotate/buildkite/internal.js +45 -0
  72. package/lib/cli/lint/annotate/buildkite/internal.js.map +7 -0
  73. package/lib/cli/lint/annotate/github/index.d.ts +2 -1
  74. package/lib/cli/lint/annotate/github/index.js +4 -2
  75. package/lib/cli/lint/annotate/github/index.js.map +2 -2
  76. package/lib/cli/lint/annotate/github/internal.d.ts +3 -0
  77. package/lib/cli/lint/annotate/github/internal.js +36 -0
  78. package/lib/cli/lint/annotate/github/internal.js.map +7 -0
  79. package/lib/cli/lint/annotate/index.d.ts +4 -3
  80. package/lib/cli/lint/annotate/index.js +9 -3
  81. package/lib/cli/lint/annotate/index.js.map +2 -2
  82. package/lib/cli/lint/autofix.d.ts +3 -1
  83. package/lib/cli/lint/autofix.js +36 -59
  84. package/lib/cli/lint/autofix.js.map +3 -3
  85. package/lib/cli/lint/external.d.ts +6 -1
  86. package/lib/cli/lint/external.js +6 -29
  87. package/lib/cli/lint/external.js.map +2 -2
  88. package/lib/cli/lint/index.d.ts +2 -1
  89. package/lib/cli/lint/index.js +46 -14
  90. package/lib/cli/lint/index.js.map +2 -2
  91. package/lib/cli/lint/internal.d.ts +12 -1
  92. package/lib/cli/lint/internal.js +55 -19
  93. package/lib/cli/lint/internal.js.map +3 -3
  94. package/lib/cli/lint/internalLints/deleteFiles.d.ts +3 -0
  95. package/lib/cli/lint/internalLints/deleteFiles.js +102 -0
  96. package/lib/cli/lint/internalLints/deleteFiles.js.map +7 -0
  97. package/lib/cli/lint/internalLints/noSkubaTemplateJs.d.ts +3 -0
  98. package/lib/cli/lint/internalLints/noSkubaTemplateJs.js +75 -0
  99. package/lib/cli/lint/internalLints/noSkubaTemplateJs.js.map +7 -0
  100. package/lib/cli/lint/internalLints/refreshConfigFiles.d.ts +11 -0
  101. package/lib/cli/lint/internalLints/refreshConfigFiles.js +147 -0
  102. package/lib/cli/lint/internalLints/refreshConfigFiles.js.map +7 -0
  103. package/lib/cli/test/index.js +0 -2
  104. package/lib/cli/test/index.js.map +2 -2
  105. package/lib/skuba.d.ts +1 -1
  106. package/lib/skuba.js.map +1 -1
  107. package/lib/utils/exec.d.ts +2 -1
  108. package/lib/utils/exec.js +1 -0
  109. package/lib/utils/exec.js.map +2 -2
  110. package/lib/utils/logging.d.ts +2 -0
  111. package/lib/utils/logging.js +1 -0
  112. package/lib/utils/logging.js.map +2 -2
  113. package/lib/utils/logo.js +6 -10
  114. package/lib/utils/logo.js.map +3 -3
  115. package/lib/utils/manifest.d.ts +1 -1
  116. package/lib/utils/manifest.js +1 -1
  117. package/lib/utils/manifest.js.map +2 -2
  118. package/lib/utils/npmrc.d.ts +1 -0
  119. package/lib/utils/npmrc.js +29 -0
  120. package/lib/utils/npmrc.js.map +7 -0
  121. package/lib/utils/packageManager.d.ts +24 -0
  122. package/lib/utils/packageManager.js +97 -0
  123. package/lib/utils/packageManager.js.map +7 -0
  124. package/lib/utils/template.d.ts +3 -0
  125. package/lib/utils/template.js +2 -0
  126. package/lib/utils/template.js.map +2 -2
  127. package/lib/utils/worker.d.ts +1 -0
  128. package/lib/wrapper/http.d.ts +1 -0
  129. package/package.json +39 -36
  130. package/template/base/_.dockerignore +0 -1
  131. package/template/base/_.eslintignore +1 -0
  132. package/template/base/_.gitignore +1 -1
  133. package/template/base/_.npmrc +8 -0
  134. package/template/express-rest-api/.buildkite/pipeline.yml +13 -10
  135. package/template/express-rest-api/.gantry/dev.yml +3 -0
  136. package/template/express-rest-api/Dockerfile +6 -12
  137. package/template/express-rest-api/Dockerfile.dev-deps +6 -5
  138. package/template/express-rest-api/README.md +6 -6
  139. package/template/express-rest-api/docker-compose.yml +0 -10
  140. package/template/express-rest-api/gantry.apply.yml +5 -0
  141. package/template/express-rest-api/gantry.build.yml +1 -2
  142. package/template/express-rest-api/package.json +2 -2
  143. package/template/express-rest-api/skuba.template.js +1 -0
  144. package/template/greeter/.buildkite/pipeline.yml +12 -9
  145. package/template/greeter/Dockerfile +6 -5
  146. package/template/greeter/README.md +6 -6
  147. package/template/greeter/docker-compose.yml +0 -10
  148. package/template/greeter/package.json +1 -1
  149. package/template/greeter/skuba.template.js +1 -0
  150. package/template/koa-rest-api/.buildkite/pipeline.yml +13 -10
  151. package/template/koa-rest-api/.gantry/dev.yml +3 -0
  152. package/template/koa-rest-api/.nvmrc +1 -1
  153. package/template/koa-rest-api/Dockerfile +6 -12
  154. package/template/koa-rest-api/Dockerfile.dev-deps +6 -5
  155. package/template/koa-rest-api/README.md +6 -6
  156. package/template/koa-rest-api/docker-compose.yml +0 -10
  157. package/template/koa-rest-api/gantry.apply.yml +5 -0
  158. package/template/koa-rest-api/gantry.build.yml +1 -2
  159. package/template/koa-rest-api/package.json +8 -6
  160. package/template/koa-rest-api/skuba.template.js +1 -0
  161. package/template/koa-rest-api/src/framework/validation.test.ts +48 -15
  162. package/template/koa-rest-api/src/framework/validation.ts +31 -8
  163. package/template/koa-rest-api/src/testing/types.ts +16 -4
  164. package/template/lambda-sqs-worker/.buildkite/pipeline.yml +21 -15
  165. package/template/lambda-sqs-worker/Dockerfile +6 -6
  166. package/template/lambda-sqs-worker/README.md +8 -8
  167. package/template/lambda-sqs-worker/_.npmrc +12 -0
  168. package/template/lambda-sqs-worker/docker-compose.yml +0 -15
  169. package/template/lambda-sqs-worker/package.json +3 -4
  170. package/template/lambda-sqs-worker/serverless.yml +4 -1
  171. package/template/lambda-sqs-worker/skuba.template.js +1 -0
  172. package/template/lambda-sqs-worker/src/hooks.ts +1 -2
  173. package/template/lambda-sqs-worker-cdk/.buildkite/pipeline.yml +42 -18
  174. package/template/lambda-sqs-worker-cdk/.nvmrc +1 -1
  175. package/template/lambda-sqs-worker-cdk/Dockerfile +9 -7
  176. package/template/lambda-sqs-worker-cdk/cdk.json +12 -6
  177. package/template/lambda-sqs-worker-cdk/docker-compose.yml +0 -15
  178. package/template/lambda-sqs-worker-cdk/infra/__snapshots__/appStack.test.ts.snap +1587 -225
  179. package/template/lambda-sqs-worker-cdk/infra/appStack.test.ts +23 -3
  180. package/template/lambda-sqs-worker-cdk/infra/appStack.ts +127 -14
  181. package/template/lambda-sqs-worker-cdk/package.json +5 -3
  182. package/template/lambda-sqs-worker-cdk/shared/context-types.ts +1 -0
  183. package/template/lambda-sqs-worker-cdk/skuba.template.js +1 -0
  184. package/template/lambda-sqs-worker-cdk/src/app.ts +14 -1
  185. package/template/lambda-sqs-worker-cdk/src/postHook.ts +154 -0
  186. package/template/lambda-sqs-worker-cdk/src/preHook.ts +95 -0
  187. package/template/oss-npm-package/.github/workflows/release.yml +10 -7
  188. package/template/oss-npm-package/.github/workflows/validate.yml +10 -7
  189. package/template/oss-npm-package/.releaserc +16 -0
  190. package/template/oss-npm-package/README.md +17 -17
  191. package/template/oss-npm-package/_package.json +3 -2
  192. package/template/oss-npm-package/skuba.template.js +1 -0
  193. package/template/private-npm-package/.releaserc +16 -0
  194. package/template/private-npm-package/README.md +16 -16
  195. package/template/private-npm-package/_package.json +3 -3
  196. package/template/private-npm-package/skuba.template.js +1 -0
  197. package/lib/cli/configure/addEmptyExports.js.map +0 -7
  198. package/lib/cli/configure/modules/tsconfig.d.ts +0 -2
  199. package/lib/cli/configure/modules/tsconfig.js +0 -87
  200. package/lib/cli/configure/modules/tsconfig.js.map +0 -7
  201. package/lib/cli/configure/patchDockerfile.d.ts +0 -1
  202. package/lib/cli/configure/patchDockerfile.js.map +0 -7
  203. package/lib/cli/configure/patchServerListener.d.ts +0 -3
  204. package/lib/cli/configure/patchServerListener.js.map +0 -7
  205. package/lib/cli/configure/refreshIgnoreFiles.d.ts +0 -3
  206. package/lib/cli/configure/refreshIgnoreFiles.js +0 -78
  207. package/lib/cli/configure/refreshIgnoreFiles.js.map +0 -7
@@ -1,5 +1,5 @@
1
- import { SynthUtils } from '@aws-cdk/assert';
2
- import { App } from 'aws-cdk-lib';
1
+ import { App, aws_sns } from 'aws-cdk-lib';
2
+ import { Template } from 'aws-cdk-lib/assertions';
3
3
 
4
4
  import cdkJson from '../cdk.json';
5
5
 
@@ -17,14 +17,34 @@ const contexts = [
17
17
  },
18
18
  ];
19
19
 
20
+ const currentDate = '1212-12-12T12:12:12.121Z';
21
+
22
+ jest.useFakeTimers({
23
+ legacyFakeTimers: false,
24
+ doNotFake: [
25
+ 'nextTick',
26
+ 'setInterval',
27
+ 'clearInterval',
28
+ 'setTimeout',
29
+ 'clearTimeout',
30
+ ],
31
+ now: new Date(currentDate),
32
+ });
33
+
20
34
  it.each(contexts)(
21
35
  'returns expected CloudFormation stack for $stage',
22
36
  (context) => {
37
+ jest
38
+ .spyOn(aws_sns.Topic, 'fromTopicArn')
39
+ .mockImplementation((scope, id) => new aws_sns.Topic(scope, id));
40
+
23
41
  const app = new App({ context });
24
42
 
25
43
  const stack = new AppStack(app, 'appStack');
26
44
 
27
- const json = JSON.stringify(SynthUtils.toCloudFormation(stack)).replace(
45
+ const template = Template.fromStack(stack);
46
+
47
+ const json = JSON.stringify(template.toJSON()).replace(
28
48
  /"S3Key":"([0-9a-f]+)\.zip"/g,
29
49
  (_, hash) => `"S3Key":"${'x'.repeat(hash.length)}.zip"`,
30
50
  );
@@ -1,10 +1,14 @@
1
1
  import {
2
+ Duration,
2
3
  Stack,
3
4
  type StackProps,
5
+ aws_cloudwatch,
6
+ aws_codedeploy,
4
7
  aws_iam,
5
8
  aws_kms,
6
9
  aws_lambda,
7
10
  aws_lambda_event_sources,
11
+ aws_lambda_nodejs,
8
12
  aws_sns,
9
13
  aws_sns_subscriptions,
10
14
  aws_sqs,
@@ -31,11 +35,6 @@ export class AppStack extends Stack {
31
35
 
32
36
  kmsKey.grantEncrypt(accountPrincipal);
33
37
 
34
- const topic = new aws_sns.Topic(this, 'topic', {
35
- topicName: '<%- serviceName %>',
36
- masterKey: kmsKey,
37
- });
38
-
39
38
  const deadLetterQueue = new aws_sqs.Queue(this, 'worker-queue-dlq', {
40
39
  queueName: '<%- serviceName %>-dlq',
41
40
  encryptionMasterKey: kmsKey,
@@ -50,25 +49,139 @@ export class AppStack extends Stack {
50
49
  encryptionMasterKey: kmsKey,
51
50
  });
52
51
 
52
+ const topic = aws_sns.Topic.fromTopicArn(
53
+ this,
54
+ 'source-topic',
55
+ context.sourceSnsTopicArn,
56
+ );
57
+
58
+ topic.addSubscription(new aws_sns_subscriptions.SqsSubscription(queue));
59
+
53
60
  const architecture = '<%- lambdaCdkArchitecture %>';
54
61
 
55
- const worker = new aws_lambda.Function(this, 'worker', {
62
+ const defaultWorkerConfig: aws_lambda_nodejs.NodejsFunctionProps = {
56
63
  architecture: aws_lambda.Architecture[architecture],
57
- code: new aws_lambda.AssetCode('./lib'),
58
64
  runtime: aws_lambda.Runtime.NODEJS_20_X,
59
- handler: 'app.handler',
60
- functionName: '<%- serviceName %>',
61
65
  environmentEncryption: kmsKey,
66
+ // aws-sdk-v3 sets this to true by default, so it is not necessary to set the environment variable
67
+ // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html
68
+ awsSdkConnectionReuse: false,
69
+ };
70
+
71
+ const defaultWorkerBundlingConfig: aws_lambda_nodejs.BundlingOptions = {
72
+ sourceMap: true,
73
+ target: 'node20',
74
+ // aws-sdk-v3 is set as an external module by default, but we want it to be bundled with the function
75
+ externalModules: [],
76
+ };
77
+
78
+ const defaultWorkerEnvironment: Record<string, string> = {
79
+ NODE_ENV: 'production',
80
+ // https://nodejs.org/api/cli.html#cli_node_options_options
81
+ NODE_OPTIONS: '--enable-source-maps',
82
+ };
83
+
84
+ const worker = new aws_lambda_nodejs.NodejsFunction(this, 'worker', {
85
+ ...defaultWorkerConfig,
86
+ entry: './src/app.ts',
87
+ timeout: Duration.seconds(30),
88
+ bundling: defaultWorkerBundlingConfig,
89
+ functionName: '<%- serviceName %>',
62
90
  environment: {
63
- NODE_ENV: 'production',
64
- // https://nodejs.org/api/cli.html#cli_node_options_options
65
- NODE_OPTIONS: '--enable-source-maps',
91
+ ...defaultWorkerEnvironment,
66
92
  ...context.workerLambda.environment,
67
93
  },
94
+ // https://github.com/aws/aws-cdk/issues/28237
95
+ // This forces the lambda to be updated on every deployment
96
+ // If you do not wish to use hotswap, you can remove the new Date().toISOString() from the description
97
+ description: `Updated at ${new Date().toISOString()}`,
98
+ reservedConcurrentExecutions: context.workerLambda.reservedConcurrency,
68
99
  });
69
100
 
70
- worker.addEventSource(new aws_lambda_event_sources.SqsEventSource(queue));
101
+ const alias = worker.addAlias('live', {
102
+ description: 'The Lambda version currently receiving traffic',
103
+ });
71
104
 
72
- topic.addSubscription(new aws_sns_subscriptions.SqsSubscription(queue));
105
+ alias.addEventSource(
106
+ new aws_lambda_event_sources.SqsEventSource(queue, {
107
+ maxConcurrency: context.workerLambda.reservedConcurrency,
108
+ }),
109
+ );
110
+
111
+ const preHook = new aws_lambda_nodejs.NodejsFunction(
112
+ this,
113
+ 'worker-pre-hook',
114
+ {
115
+ ...defaultWorkerConfig,
116
+ entry: './src/preHook.ts',
117
+ timeout: Duration.seconds(120),
118
+ bundling: defaultWorkerBundlingConfig,
119
+ functionName: '<%- serviceName %>-pre-hook',
120
+ environment: {
121
+ ...defaultWorkerEnvironment,
122
+ ...context.workerLambda.environment,
123
+ FUNCTION_NAME_TO_INVOKE: worker.functionName,
124
+ },
125
+ },
126
+ );
127
+
128
+ worker.grantInvoke(preHook);
129
+
130
+ const postHook = new aws_lambda_nodejs.NodejsFunction(
131
+ this,
132
+ 'worker-post-hook',
133
+ {
134
+ ...defaultWorkerConfig,
135
+ entry: './src/postHook.ts',
136
+ timeout: Duration.seconds(30),
137
+ bundling: defaultWorkerBundlingConfig,
138
+ functionName: '<%- serviceName %>-post-hook',
139
+ environment: {
140
+ ...defaultWorkerEnvironment,
141
+ ...context.workerLambda.environment,
142
+ FUNCTION_NAME_TO_PRUNE: worker.functionName,
143
+ },
144
+ },
145
+ );
146
+
147
+ const prunePermissions = new aws_iam.PolicyStatement({
148
+ actions: [
149
+ 'lambda:ListAliases',
150
+ 'lambda:ListVersionsByFunction',
151
+ 'lambda:DeleteFunction',
152
+ ],
153
+ resources: [worker.functionArn, `${worker.functionArn}:*`],
154
+ });
155
+
156
+ postHook.addToRolePolicy(prunePermissions);
157
+
158
+ const application = new aws_codedeploy.LambdaApplication(
159
+ this,
160
+ 'codedeploy-application',
161
+ );
162
+
163
+ const deploymentGroup = new aws_codedeploy.LambdaDeploymentGroup(
164
+ this,
165
+ 'codedeploy-group',
166
+ {
167
+ application,
168
+ alias,
169
+ deploymentConfig: aws_codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE,
170
+ },
171
+ );
172
+
173
+ const alarm = new aws_cloudwatch.Alarm(this, 'codedeploy-alarm', {
174
+ metric: alias.metricErrors({
175
+ period: Duration.seconds(60),
176
+ }),
177
+ threshold: 1,
178
+ evaluationPeriods: 1,
179
+ });
180
+
181
+ deploymentGroup.addAlarm(alarm);
182
+
183
+ deploymentGroup.addPreHook(preHook);
184
+
185
+ deploymentGroup.addPostHook(postHook);
73
186
  }
74
187
  }
@@ -2,21 +2,23 @@
2
2
  "private": true,
3
3
  "license": "UNLICENSED",
4
4
  "scripts": {
5
- "build": "skuba build",
6
5
  "deploy": "cdk deploy appStack --require-approval never --context stage=${ENVIRONMENT}",
6
+ "deploy:hotswap": "pnpm run --silent deploy --hotswap",
7
+ "deploy:watch": "pnpm run --silent deploy:hotswap --watch",
7
8
  "format": "skuba format",
8
9
  "lint": "skuba lint",
9
- "package": "yarn install --ignore-optional --ignore-scripts --modules-folder ./lib/node_modules --non-interactive --offline --production",
10
10
  "test": "skuba test",
11
11
  "test:ci": "skuba test --coverage",
12
12
  "test:watch": "skuba test --watch"
13
13
  },
14
14
  "dependencies": {
15
+ "@aws-sdk/client-codedeploy": "^3.363.0",
16
+ "@aws-sdk/client-lambda": "^3.363.0",
17
+ "@aws-sdk/client-sns": "^3.363.0",
15
18
  "@seek/logger": "^6.0.0",
16
19
  "zod": "^3.19.1"
17
20
  },
18
21
  "devDependencies": {
19
- "@aws-cdk/assert": "^2.24.0",
20
22
  "@types/aws-lambda": "^8.10.82",
21
23
  "@types/node": "^20.9.0",
22
24
  "aws-cdk": "^2.109.0",
@@ -10,6 +10,7 @@ export const EnvContextSchema = z.object({
10
10
  SOMETHING: z.string(),
11
11
  }),
12
12
  }),
13
+ sourceSnsTopicArn: z.string(),
13
14
  });
14
15
 
15
16
  export type EnvContext = z.infer<typeof EnvContextSchema>;
@@ -23,5 +23,6 @@ module.exports = {
23
23
  validate: (value) => /^.+:.+$/.test(value),
24
24
  },
25
25
  ],
26
+ packageManager: 'pnpm',
26
27
  type: 'application',
27
28
  };
@@ -5,6 +5,19 @@ const logger = createLogger({
5
5
  name: '<%- serviceName %>',
6
6
  });
7
7
 
8
- export const handler: SQSHandler = (_: SQSEvent) => {
8
+ /**
9
+ * Tests connectivity to ensure appropriate access and network configuration.
10
+ */
11
+ const smokeTest = async () => Promise.resolve();
12
+
13
+ export const handler: SQSHandler = (event: SQSEvent) => {
14
+ // Treat an empty object as our smoke test event.
15
+ if (!Object.keys(event).length) {
16
+ logger.debug('Received smoke test request');
17
+ return smokeTest();
18
+ }
19
+
9
20
  logger.info('Hello World!');
21
+
22
+ return;
10
23
  };
@@ -0,0 +1,154 @@
1
+ /* eslint-disable no-console */
2
+ /* istanbul ignore file */
3
+
4
+ import {
5
+ CodeDeploy,
6
+ PutLifecycleEventHookExecutionStatusCommand,
7
+ } from '@aws-sdk/client-codedeploy';
8
+ import {
9
+ type AliasConfiguration,
10
+ DeleteFunctionCommand,
11
+ type FunctionConfiguration,
12
+ LambdaClient,
13
+ ListAliasesCommand,
14
+ ListVersionsByFunctionCommand,
15
+ } from '@aws-sdk/client-lambda';
16
+ import { z } from 'zod';
17
+
18
+ const lambda = new LambdaClient();
19
+ const codeDeploy = new CodeDeploy();
20
+
21
+ const listLambdaVersions = async (
22
+ functionName: string,
23
+ marker?: string,
24
+ ): Promise<FunctionConfiguration[]> => {
25
+ const result = await lambda.send(
26
+ new ListVersionsByFunctionCommand({
27
+ FunctionName: functionName,
28
+ Marker: marker,
29
+ }),
30
+ );
31
+ const versions = result.Versions ?? [];
32
+ if (result.NextMarker) {
33
+ return [
34
+ ...versions,
35
+ ...(await listLambdaVersions(functionName, result.NextMarker)),
36
+ ];
37
+ }
38
+
39
+ return versions;
40
+ };
41
+
42
+ const listAliases = async (
43
+ functionName: string,
44
+ marker?: string,
45
+ ): Promise<AliasConfiguration[]> => {
46
+ const result = await lambda.send(
47
+ new ListAliasesCommand({
48
+ FunctionName: functionName,
49
+ Marker: marker,
50
+ }),
51
+ );
52
+ const aliases = result.Aliases ?? [];
53
+ if (result.NextMarker) {
54
+ return [
55
+ ...aliases,
56
+ ...(await listAliases(functionName, result.NextMarker)),
57
+ ];
58
+ }
59
+
60
+ return aliases;
61
+ };
62
+
63
+ const pruneLambdas = async (
64
+ functionName: string,
65
+ numberToKeep: number,
66
+ ): Promise<void> => {
67
+ const [aliases, versions] = await Promise.all([
68
+ listAliases(functionName),
69
+ listLambdaVersions(functionName),
70
+ ]);
71
+
72
+ const aliasMap = new Map(
73
+ aliases.flatMap((alias) =>
74
+ alias.FunctionVersion ? [[alias.FunctionVersion, alias]] : [],
75
+ ),
76
+ );
77
+
78
+ const versionsToPrune = versions
79
+ .filter(
80
+ (version) =>
81
+ version.Version &&
82
+ !aliasMap.has(version.Version) &&
83
+ version.Version !== '$LATEST',
84
+ )
85
+ .sort((a, b) => Number(b.Version) - Number(a.Version))
86
+ .slice(numberToKeep);
87
+
88
+ if (!versionsToPrune.length) {
89
+ console.log('No function versions to prune');
90
+ return;
91
+ }
92
+
93
+ console.log(
94
+ `Pruning function versions: ${versionsToPrune
95
+ .map((version) => version.Version)
96
+ .join(', ')}`,
97
+ );
98
+
99
+ await Promise.all(
100
+ versionsToPrune.map((version) =>
101
+ lambda.send(
102
+ new DeleteFunctionCommand({
103
+ FunctionName: version.FunctionName,
104
+ Qualifier: version.Version,
105
+ }),
106
+ ),
107
+ ),
108
+ );
109
+ };
110
+
111
+ const EnvSchema = z.object({
112
+ FUNCTION_NAME_TO_PRUNE: z.string(),
113
+ NUMBER_OF_VERSIONS_TO_KEEP: z.coerce.number().default(0),
114
+ });
115
+
116
+ type Status = 'Succeeded' | 'Failed';
117
+
118
+ /**
119
+ * The event supplied to a CodeDeploy lifecycle hook Lambda function.
120
+ *
121
+ * {@link https://docs.aws.amazon.com/codedeploy/latest/userguide/tutorial-ecs-with-hooks-create-hooks.html}
122
+ */
123
+ interface CodeDeployLifecycleHookEvent {
124
+ DeploymentId: string;
125
+ LifecycleEventHookExecutionId: string;
126
+ }
127
+
128
+ /**
129
+ * A handler to clean up old Lambda function versions and layers
130
+ */
131
+ export const handler = async (
132
+ event: CodeDeployLifecycleHookEvent,
133
+ ): Promise<void> => {
134
+ let status: Status = 'Succeeded';
135
+ try {
136
+ const {
137
+ FUNCTION_NAME_TO_PRUNE: functionName,
138
+ NUMBER_OF_VERSIONS_TO_KEEP: numberToKeep,
139
+ } = EnvSchema.parse(process.env);
140
+
141
+ await pruneLambdas(functionName, numberToKeep);
142
+ } catch (err) {
143
+ console.error('Exception:', err);
144
+ status = 'Failed';
145
+ }
146
+
147
+ await codeDeploy.send(
148
+ new PutLifecycleEventHookExecutionStatusCommand({
149
+ deploymentId: event.DeploymentId,
150
+ lifecycleEventHookExecutionId: event.LifecycleEventHookExecutionId,
151
+ status,
152
+ }),
153
+ );
154
+ };
@@ -0,0 +1,95 @@
1
+ /* eslint-disable no-console */
2
+ /* istanbul ignore file */
3
+
4
+ // Use minimal dependencies to reduce the chance of crashes on module load.
5
+ import {
6
+ CodeDeployClient,
7
+ PutLifecycleEventHookExecutionStatusCommand,
8
+ } from '@aws-sdk/client-codedeploy';
9
+ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda';
10
+
11
+ const codeDeploy = new CodeDeployClient({
12
+ apiVersion: '2014-10-06',
13
+ maxAttempts: 5,
14
+ });
15
+
16
+ const lambda = new LambdaClient({
17
+ apiVersion: '2015-03-31',
18
+ maxAttempts: 5,
19
+ });
20
+
21
+ type Status = 'Succeeded' | 'Failed';
22
+
23
+ /**
24
+ * Synchronously invokes a Lambda function with a smoke test event.
25
+ *
26
+ * Any non-error response is treated as a success.
27
+ */
28
+ const smokeTestLambdaFunction = async (): Promise<Status> => {
29
+ const functionName = process.env.FUNCTION_NAME_TO_INVOKE;
30
+
31
+ if (!functionName) {
32
+ console.error('Missing process.env.FUNCTION_NAME_TO_INVOKE');
33
+ return 'Failed';
34
+ }
35
+
36
+ console.info('Function:', functionName);
37
+
38
+ const response = await lambda.send(
39
+ new InvokeCommand({
40
+ FunctionName: functionName,
41
+ InvocationType: 'RequestResponse',
42
+ // Treat an empty object as our smoke test event.
43
+ Payload: Buffer.from('{}'),
44
+ }),
45
+ );
46
+
47
+ console.info('Version:', response.ExecutedVersion ?? '?');
48
+ console.info('Status', response.StatusCode ?? '?');
49
+
50
+ if (response.FunctionError) {
51
+ console.error('Error:', response.FunctionError);
52
+ if (response.Payload) {
53
+ console.error(response.Payload.transformToString());
54
+ }
55
+ return 'Failed';
56
+ }
57
+
58
+ return response.StatusCode === 200 ? 'Succeeded' : 'Failed';
59
+ };
60
+
61
+ /**
62
+ * The event supplied to a CodeDeploy lifecycle hook Lambda function.
63
+ *
64
+ * {@link https://docs.aws.amazon.com/codedeploy/latest/userguide/tutorial-ecs-with-hooks-create-hooks.html}
65
+ */
66
+ interface CodeDeployLifecycleHookEvent {
67
+ DeploymentId: string;
68
+ LifecycleEventHookExecutionId: string;
69
+ }
70
+
71
+ /**
72
+ * A handler to smoke test a new Lambda function version before it goes live.
73
+ *
74
+ * This tries to be exception safe so that a status reaches CodeDeploy. If we
75
+ * crash or otherwise fail to report back, the deployment will hang for an hour.
76
+ */
77
+ export const handler = async (
78
+ event: CodeDeployLifecycleHookEvent,
79
+ ): Promise<void> => {
80
+ let status: Status;
81
+ try {
82
+ status = await smokeTestLambdaFunction();
83
+ } catch (err) {
84
+ console.error('Exception:', err);
85
+ status = 'Failed';
86
+ }
87
+
88
+ await codeDeploy.send(
89
+ new PutLifecycleEventHookExecutionStatusCommand({
90
+ deploymentId: event.DeploymentId,
91
+ lifecycleEventHookExecutionId: event.LifecycleEventHookExecutionId,
92
+ status,
93
+ }),
94
+ );
95
+ };
@@ -3,8 +3,8 @@ name: Release
3
3
  on:
4
4
  push:
5
5
  branches:
6
+ - $default-branch
6
7
  - beta
7
- - master
8
8
 
9
9
  permissions: {}
10
10
 
@@ -17,20 +17,23 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
19
  - name: Check out repo
20
- uses: actions/checkout@v3
20
+ uses: actions/checkout@v4
21
21
  with:
22
22
  fetch-depth: 0
23
23
 
24
- - name: Set up Node.js 16.x
25
- uses: actions/setup-node@v3
24
+ - name: Set up Node.js
25
+ uses: actions/setup-node@v4
26
26
  with:
27
- node-version: 16.x
27
+ node-version: 20.x
28
+
29
+ - name: Set up pnpm
30
+ run: corepack enable pnpm
28
31
 
29
32
  - name: Install dependencies
30
- run: yarn install --frozen-lockfile
33
+ run: pnpm install --frozen-lockfile
31
34
 
32
35
  - name: Publish to npm
33
- run: yarn release
36
+ run: pnpm run release
34
37
  env:
35
38
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36
39
  NPM_TOKEN: ${{ secrets.SEEK_OSS_CI_NPM_TOKEN }}
@@ -14,18 +14,21 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - name: Check out repo
17
- uses: actions/checkout@v3
17
+ uses: actions/checkout@v4
18
18
 
19
- - name: Set up Node.js 16.x
20
- uses: actions/setup-node@v3
19
+ - name: Set up Node.js
20
+ uses: actions/setup-node@v4
21
21
  with:
22
- node-version: 16.x
22
+ node-version: 20.x
23
+
24
+ - name: Set up pnpm
25
+ run: corepack enable pnpm
23
26
 
24
27
  - name: Install dependencies
25
- run: yarn install --frozen-lockfile
28
+ run: pnpm install --frozen-lockfile
26
29
 
27
30
  - name: Test
28
- run: yarn test:ci
31
+ run: pnpm run test:ci
29
32
 
30
33
  - name: Lint
31
- run: yarn lint
34
+ run: pnpm run lint
@@ -0,0 +1,16 @@
1
+ {
2
+ "branches": [
3
+ "+([0-9])?(.{+([0-9]),x}).x",
4
+ "<%- defaultBranch %>",
5
+ "next",
6
+ "next-major",
7
+ {
8
+ "name": "beta",
9
+ "prerelease": true
10
+ },
11
+ {
12
+ "name": "alpha",
13
+ "prerelease": true
14
+ }
15
+ ]
16
+ }