slskit-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/COMMAND.md +636 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE.md +22 -0
  5. package/README.md +168 -0
  6. package/bin/slskit.js +3 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +25 -0
  9. package/dist/commands/base-command.d.ts +5 -0
  10. package/dist/commands/base-command.js +29 -0
  11. package/dist/commands/configure/action.d.ts +2 -0
  12. package/dist/commands/configure/action.js +120 -0
  13. package/dist/commands/configure/aws-cli.d.ts +16 -0
  14. package/dist/commands/configure/aws-cli.js +135 -0
  15. package/dist/commands/configure/index.d.ts +2 -0
  16. package/dist/commands/configure/index.js +20 -0
  17. package/dist/commands/configure/prompts.d.ts +8 -0
  18. package/dist/commands/configure/prompts.js +117 -0
  19. package/dist/commands/configure/types.d.ts +24 -0
  20. package/dist/commands/configure/types.js +6 -0
  21. package/dist/commands/deploy/action.d.ts +2 -0
  22. package/dist/commands/deploy/action.js +115 -0
  23. package/dist/commands/deploy/index.d.ts +2 -0
  24. package/dist/commands/deploy/index.js +23 -0
  25. package/dist/commands/deploy/sam-deploy.d.ts +10 -0
  26. package/dist/commands/deploy/sam-deploy.js +127 -0
  27. package/dist/commands/deploy/scope.d.ts +9 -0
  28. package/dist/commands/deploy/scope.js +34 -0
  29. package/dist/commands/deploy/types.d.ts +20 -0
  30. package/dist/commands/deploy/types.js +2 -0
  31. package/dist/commands/env/action.d.ts +11 -0
  32. package/dist/commands/env/action.js +274 -0
  33. package/dist/commands/env/dotenv.d.ts +6 -0
  34. package/dist/commands/env/dotenv.js +99 -0
  35. package/dist/commands/env/index.d.ts +2 -0
  36. package/dist/commands/env/index.js +76 -0
  37. package/dist/commands/env/keys.d.ts +8 -0
  38. package/dist/commands/env/keys.js +41 -0
  39. package/dist/commands/env/parameters.d.ts +7 -0
  40. package/dist/commands/env/parameters.js +63 -0
  41. package/dist/commands/env/templates.d.ts +3 -0
  42. package/dist/commands/env/templates.js +65 -0
  43. package/dist/commands/env/types.d.ts +21 -0
  44. package/dist/commands/env/types.js +2 -0
  45. package/dist/commands/function/action.d.ts +2 -0
  46. package/dist/commands/function/action.js +71 -0
  47. package/dist/commands/function/generator.d.ts +8 -0
  48. package/dist/commands/function/generator.js +58 -0
  49. package/dist/commands/function/index.d.ts +2 -0
  50. package/dist/commands/function/index.js +26 -0
  51. package/dist/commands/function/manifest.d.ts +5 -0
  52. package/dist/commands/function/manifest.js +57 -0
  53. package/dist/commands/function/prompts.d.ts +18 -0
  54. package/dist/commands/function/prompts.js +196 -0
  55. package/dist/commands/function/tooling.d.ts +2 -0
  56. package/dist/commands/function/tooling.js +43 -0
  57. package/dist/commands/function/types.d.ts +52 -0
  58. package/dist/commands/function/types.js +2 -0
  59. package/dist/commands/index.d.ts +2 -0
  60. package/dist/commands/index.js +19 -0
  61. package/dist/commands/init/action.d.ts +2 -0
  62. package/dist/commands/init/action.js +36 -0
  63. package/dist/commands/init/content.d.ts +2 -0
  64. package/dist/commands/init/content.js +97 -0
  65. package/dist/commands/init/index.d.ts +2 -0
  66. package/dist/commands/init/index.js +30 -0
  67. package/dist/commands/init/manifest.d.ts +6 -0
  68. package/dist/commands/init/manifest.js +147 -0
  69. package/dist/commands/init/prompts.d.ts +6 -0
  70. package/dist/commands/init/prompts.js +155 -0
  71. package/dist/commands/init/scaffold.d.ts +7 -0
  72. package/dist/commands/init/scaffold.js +85 -0
  73. package/dist/commands/init/templates/helpers.d.ts +9 -0
  74. package/dist/commands/init/templates/helpers.js +42 -0
  75. package/dist/commands/init/templates/node.d.ts +4 -0
  76. package/dist/commands/init/templates/node.js +99 -0
  77. package/dist/commands/init/templates/project.d.ts +8 -0
  78. package/dist/commands/init/templates/project.js +239 -0
  79. package/dist/commands/init/templates/python.d.ts +4 -0
  80. package/dist/commands/init/templates/python.js +65 -0
  81. package/dist/commands/init/templates/sam.d.ts +4 -0
  82. package/dist/commands/init/templates/sam.js +369 -0
  83. package/dist/commands/init/templates/shared-code.d.ts +6 -0
  84. package/dist/commands/init/templates/shared-code.js +159 -0
  85. package/dist/commands/init/types.d.ts +47 -0
  86. package/dist/commands/init/types.js +75 -0
  87. package/dist/commands/rm/action.d.ts +2 -0
  88. package/dist/commands/rm/action.js +89 -0
  89. package/dist/commands/rm/index.d.ts +2 -0
  90. package/dist/commands/rm/index.js +25 -0
  91. package/dist/commands/rm/plan.d.ts +19 -0
  92. package/dist/commands/rm/plan.js +78 -0
  93. package/dist/commands/rm/prompts.d.ts +4 -0
  94. package/dist/commands/rm/prompts.js +107 -0
  95. package/dist/commands/rm/types.d.ts +7 -0
  96. package/dist/commands/rm/types.js +2 -0
  97. package/dist/commands/run/action.d.ts +2 -0
  98. package/dist/commands/run/action.js +171 -0
  99. package/dist/commands/run/index.d.ts +2 -0
  100. package/dist/commands/run/index.js +21 -0
  101. package/dist/commands/run/local-template.d.ts +7 -0
  102. package/dist/commands/run/local-template.js +51 -0
  103. package/dist/commands/run/manifest.d.ts +2 -0
  104. package/dist/commands/run/manifest.js +20 -0
  105. package/dist/commands/run/resources.d.ts +20 -0
  106. package/dist/commands/run/resources.js +93 -0
  107. package/dist/commands/run/sam-cli.d.ts +10 -0
  108. package/dist/commands/run/sam-cli.js +183 -0
  109. package/dist/commands/run/types.d.ts +18 -0
  110. package/dist/commands/run/types.js +2 -0
  111. package/dist/commands/run/watch.d.ts +11 -0
  112. package/dist/commands/run/watch.js +131 -0
  113. package/dist/core/context.d.ts +15 -0
  114. package/dist/core/context.js +41 -0
  115. package/dist/core/credentials.d.ts +10 -0
  116. package/dist/core/credentials.js +22 -0
  117. package/dist/core/environments.d.ts +46 -0
  118. package/dist/core/environments.js +161 -0
  119. package/dist/core/errors.d.ts +4 -0
  120. package/dist/core/errors.js +11 -0
  121. package/dist/core/flags-docs.d.ts +2 -0
  122. package/dist/core/flags-docs.js +63 -0
  123. package/dist/core/is-ci.d.ts +4 -0
  124. package/dist/core/is-ci.js +16 -0
  125. package/dist/core/logger.d.ts +4 -0
  126. package/dist/core/logger.js +19 -0
  127. package/dist/core/run.d.ts +1 -0
  128. package/dist/core/run.js +27 -0
  129. package/dist/core/scope.d.ts +33 -0
  130. package/dist/core/scope.js +108 -0
  131. package/dist/index.d.ts +3 -0
  132. package/dist/index.js +9 -0
  133. package/dist/program.d.ts +2 -0
  134. package/dist/program.js +26 -0
  135. package/package.json +110 -0
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.samServiceTemplate = samServiceTemplate;
4
+ exports.samFlatTemplate = samFlatTemplate;
5
+ exports.samRootTemplate = samRootTemplate;
6
+ const environments_js_1 = require("../../../core/environments.js");
7
+ const types_js_1 = require("../types.js");
8
+ const helpers_js_1 = require("./helpers.js");
9
+ // DATABASE_URL and MONGODB_URI are not special-cased: init seeds them into
10
+ // .env.<environment> like any other variable, so they arrive through envKeys and are
11
+ // overridable per stage. Only the DynamoDB table names stay literal -- the templates
12
+ // create those tables, so they are not stage configuration.
13
+ function databaseVariables(database) {
14
+ if (database === "dynamodb") {
15
+ return ["USERS_TABLE: users", "PRODUCTS_TABLE: products"];
16
+ }
17
+ return [];
18
+ }
19
+ // Stage variables are wired to parameters rather than literals so one template can
20
+ // serve every stage: the deploy supplies the values as --parameter-overrides.
21
+ function environmentYaml(database, envKeys, indent) {
22
+ const variables = [
23
+ `${environments_js_1.APP_ENVIRONMENT_KEY}: !Ref ${environments_js_1.APP_ENVIRONMENT_PARAM}`,
24
+ ...databaseVariables(database),
25
+ ...envKeys.map((key) => `${key}: !Ref ${(0, helpers_js_1.envParameterName)(key)}`),
26
+ ];
27
+ const body = variables.map((line) => `${indent} ${line}`).join("\n");
28
+ return `${indent}Environment:\n${indent} Variables:\n${body}\n`;
29
+ }
30
+ function stageParameterNames(envKeys) {
31
+ return envKeys.map(helpers_js_1.envParameterName);
32
+ }
33
+ function appEnvironmentParameter() {
34
+ return ` ${environments_js_1.APP_ENVIRONMENT_PARAM}:
35
+ Type: String
36
+ Default: ${environments_js_1.DEFAULT_ENVIRONMENT}
37
+ Description: Deployment environment; every stage-scoped resource name is built from it`;
38
+ }
39
+ function samParameters(envKeys, extra = []) {
40
+ const params = [appEnvironmentParameter(), ...extra];
41
+ // Every stage variable defaults to empty, so a stage that does not set one can
42
+ // still deploy from the same template without supplying an override. NoEcho is
43
+ // unconditional: any of them may hold a connection string or an API key, and
44
+ // nothing here reads a parameter value back out of CloudFormation.
45
+ for (const key of envKeys) {
46
+ params.push(` ${(0, helpers_js_1.envParameterName)(key)}:
47
+ Type: String
48
+ Default: ""
49
+ NoEcho: true
50
+ Description: ${key} environment variable`);
51
+ }
52
+ return `Parameters:\n${params.join("\n")}\n\n`;
53
+ }
54
+ function samHttpApiResource() {
55
+ return ` HttpApi:
56
+ Type: AWS::Serverless::HttpApi
57
+ Properties:
58
+ StageName: $default
59
+ CorsConfiguration:
60
+ AllowMethods:
61
+ - GET
62
+ - POST
63
+ - OPTIONS
64
+ AllowHeaders:
65
+ - "*"
66
+ AllowOrigins:
67
+ - "*"
68
+
69
+ `;
70
+ }
71
+ // Like CodeUri, this is relative to the template that declares it, not to the API mode.
72
+ function samSharedLayerResource(answers, site) {
73
+ const runtime = (0, types_js_1.lambdaRuntime)(answers.runtime);
74
+ const buildMethod = answers.runtime === "python" ? "python3.12" : "nodejs20.x";
75
+ const layerDir = answers.runtime === "python" ? "shared/python" : "shared/nodejs";
76
+ const contentUri = site === "root" ? `${types_js_1.SRC_DIR}/${layerDir}` : `../${types_js_1.SRC_DIR}/${layerDir}`;
77
+ return ` SharedLayer:
78
+ Type: AWS::Serverless::LayerVersion
79
+ Properties:
80
+ LayerName: !Sub "\${AWS::StackName}-shared"
81
+ Description: Shared utilities attached to every function
82
+ ContentUri: ${contentUri}
83
+ CompatibleRuntimes:
84
+ - ${runtime}
85
+ RetentionPolicy: Retain
86
+ Metadata:
87
+ BuildMethod: ${buildMethod}
88
+
89
+ `;
90
+ }
91
+ function samDynamoResources(service) {
92
+ const { resource, table } = (0, helpers_js_1.dynamoTableName)(service.name);
93
+ return `
94
+ ${resource}:
95
+ Type: AWS::DynamoDB::Table
96
+ Properties:
97
+ TableName: ${table}
98
+ BillingMode: PAY_PER_REQUEST
99
+ AttributeDefinitions:
100
+ - AttributeName: id
101
+ AttributeType: S
102
+ KeySchema:
103
+ - AttributeName: id
104
+ KeyType: HASH
105
+ `;
106
+ }
107
+ function samPolicies(database, service) {
108
+ if (database !== "dynamodb") {
109
+ return "";
110
+ }
111
+ const { resource } = (0, helpers_js_1.dynamoTableName)(service.name);
112
+ return ` Policies:
113
+ - DynamoDBCrudPolicy:
114
+ TableName: !Ref ${resource}
115
+ `;
116
+ }
117
+ function samHttpEvent(fn) {
118
+ return ` Events:
119
+ ${(0, helpers_js_1.pascal)(fn.name)}Api:
120
+ Type: HttpApi
121
+ Properties:
122
+ ApiId: !Ref HttpApi
123
+ Path: ${fn.httpPath}
124
+ Method: ${fn.method}
125
+ `;
126
+ }
127
+ function samFunctionName(answers, fn) {
128
+ return ` FunctionName: !Sub "${answers.name}-\${${environments_js_1.APP_ENVIRONMENT_PARAM}}-${fn.name}"\n`;
129
+ }
130
+ function codeUriFor(site) {
131
+ return site === "root" ? "./" : "../";
132
+ }
133
+ // With one shared API Gateway the API lives in the root stack, which SAM's HttpApi
134
+ // event cannot reach ("ApiId must be a valid reference to an AWS::Serverless::HttpApi
135
+ // resource in same template"). Plain API Gateway v2 resources have no such rule, so
136
+ // the routes are declared directly against the id the root passes down.
137
+ const SHARED_API_PARAM = "HttpApiId";
138
+ function samRawRoute(fn) {
139
+ const name = (0, helpers_js_1.pascal)(fn.name);
140
+ return `
141
+ ${name}Integration:
142
+ Type: AWS::ApiGatewayV2::Integration
143
+ Properties:
144
+ ApiId: !Ref ${SHARED_API_PARAM}
145
+ IntegrationType: AWS_PROXY
146
+ IntegrationUri: !GetAtt ${name}Function.Arn
147
+ PayloadFormatVersion: "2.0"
148
+
149
+ ${name}Route:
150
+ Type: AWS::ApiGatewayV2::Route
151
+ Properties:
152
+ ApiId: !Ref ${SHARED_API_PARAM}
153
+ RouteKey: "${fn.method} ${fn.httpPath}"
154
+ Target: !Sub "integrations/\${${name}Integration}"
155
+
156
+ ${name}Permission:
157
+ Type: AWS::Lambda::Permission
158
+ Properties:
159
+ Action: lambda:InvokeFunction
160
+ FunctionName: !Ref ${name}Function
161
+ Principal: apigateway.amazonaws.com
162
+ SourceArn: !Sub "arn:\${AWS::Partition}:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${SHARED_API_PARAM}}/*/*"
163
+ `;
164
+ }
165
+ function sharedApiParameter() {
166
+ return ` ${SHARED_API_PARAM}:
167
+ Type: String
168
+ Description: The project's shared HTTP API; this service attaches its routes to it`;
169
+ }
170
+ // The one API every service hangs its routes off. Declared with plain API Gateway v2
171
+ // resources rather than AWS::Serverless::HttpApi so that nested stacks can reference it.
172
+ function samSharedApiResources(answers) {
173
+ return ` SharedHttpApi:
174
+ Type: AWS::ApiGatewayV2::Api
175
+ Properties:
176
+ Name: !Sub "${answers.name}-\${${environments_js_1.APP_ENVIRONMENT_PARAM}}"
177
+ ProtocolType: HTTP
178
+ CorsConfiguration:
179
+ AllowMethods:
180
+ - GET
181
+ - POST
182
+ - OPTIONS
183
+ AllowHeaders:
184
+ - "*"
185
+ AllowOrigins:
186
+ - "*"
187
+
188
+ SharedHttpApiStage:
189
+ Type: AWS::ApiGatewayV2::Stage
190
+ Properties:
191
+ ApiId: !Ref SharedHttpApi
192
+ StageName: $default
193
+ AutoDeploy: true
194
+
195
+ `;
196
+ }
197
+ function samFunction(answers, service, fn, envKeys, site) {
198
+ const fnRuntime = fn.runtime ?? answers.runtime;
199
+ // A function in a different runtime family than the project can't attach the
200
+ // project's layer or import its db client, so it runs standalone instead.
201
+ const sameFamily = (0, types_js_1.sameRuntimeFamily)(fnRuntime, answers.runtime);
202
+ const useLayer = answers.layer && sameFamily;
203
+ const database = sameFamily ? answers.database : "none";
204
+ const memorySize = fn.memorySize ?? answers.memorySize;
205
+ const resource = `${(0, helpers_js_1.pascal)(fn.name)}Function`;
206
+ const functionName = samFunctionName(answers, fn);
207
+ const env = environmentYaml(database, envKeys, " ");
208
+ const policies = samPolicies(database, service);
209
+ const runtime = (0, types_js_1.lambdaRuntime)(fnRuntime);
210
+ const layers = useLayer ? ` Layers:\n - !Ref SharedLayer\n` : "";
211
+ // Routes are SAM events when the API is in this template, and separate API Gateway
212
+ // resources when it lives in the root stack.
213
+ const events = answers.apiGateway && !(answers.sharedApi && site === "service")
214
+ ? samHttpEvent(fn)
215
+ : "";
216
+ // CodeUri is the project root: SAM stages it and runs npm install / pip install there,
217
+ // so package.json and requirements.txt must be inside it.
218
+ if (fnRuntime === "python") {
219
+ return ` ${resource}:
220
+ Type: AWS::Serverless::Function
221
+ Properties:
222
+ ${functionName} CodeUri: ${codeUriFor(site)}
223
+ Handler: ${types_js_1.SRC_DIR}.functions.${service.name}.${fn.name}.handler.handler
224
+ Runtime: ${runtime}
225
+ MemorySize: ${memorySize}
226
+ ${env}${policies}${layers}${events}`;
227
+ }
228
+ const entryExt = fnRuntime === "typescript" ? "ts" : "js";
229
+ const entry = `${types_js_1.SRC_DIR}/functions/${service.name}/${fn.name}/handler.${entryExt}`;
230
+ const handlerPath = `${entry.replace(/\.(ts|js)$/, "")}.handler`;
231
+ const externals = useLayer
232
+ ? ` External:
233
+ - "/opt/nodejs/*"
234
+ `
235
+ : "";
236
+ return ` ${resource}:
237
+ Type: AWS::Serverless::Function
238
+ Properties:
239
+ ${functionName} CodeUri: ${codeUriFor(site)}
240
+ Handler: ${handlerPath}
241
+ Runtime: ${runtime}
242
+ MemorySize: ${memorySize}
243
+ ${env}${policies}${layers}${events} Metadata:
244
+ BuildMethod: esbuild
245
+ BuildProperties:
246
+ Format: cjs
247
+ Minify: false
248
+ Target: es2020
249
+ Sourcemap: true
250
+ EntryPoints:
251
+ - ${entry}
252
+ ${externals}`;
253
+ }
254
+ // One stack per service, nested by the root. With per-service APIs the stack owns its
255
+ // own HTTP API; with a shared one it takes the API's id from the root and attaches
256
+ // routes to it.
257
+ function samServiceTemplate(answers, service, envKeys = []) {
258
+ const shared = answers.apiGateway && answers.sharedApi;
259
+ const httpApi = answers.apiGateway && !shared ? samHttpApiResource() : "";
260
+ const sharedLayer = answers.layer ? samSharedLayerResource(answers, "service") : "";
261
+ const functions = service.functions
262
+ .map((fn) => samFunction(answers, service, fn, envKeys, "service"))
263
+ .join("\n");
264
+ const routes = shared
265
+ ? service.functions.map((fn) => samRawRoute(fn)).join("")
266
+ : "";
267
+ const dynamo = answers.database === "dynamodb" ? samDynamoResources(service) : "";
268
+ // Only a service that owns its API has a URL of its own to report upward.
269
+ const outputs = answers.apiGateway && !shared
270
+ ? `
271
+ Outputs:
272
+ HttpApiUrl:
273
+ Description: HTTP API URL for the ${service.name} service
274
+ Value: !Sub https://\${HttpApi}.execute-api.\${AWS::Region}.amazonaws.com
275
+ `
276
+ : "";
277
+ const parameters = shared
278
+ ? samParameters(envKeys, [sharedApiParameter()])
279
+ : samParameters(envKeys);
280
+ return `AWSTemplateFormatVersion: "2010-09-09"
281
+ Transform: AWS::Serverless-2016-10-31
282
+ Description: ${service.name} service
283
+
284
+ ${parameters}Globals:
285
+ Function:
286
+ Timeout: 10
287
+ MemorySize: ${answers.memorySize}
288
+
289
+ Resources:
290
+ ${httpApi}${sharedLayer}${functions}${routes}${dynamo}${outputs}`;
291
+ }
292
+ // Everything in one file: a single HTTP API, one layer, every function beside them.
293
+ // This is what "slskit run" serves, never what gets deployed. SAM local cannot resolve
294
+ // an API that lives in a parent stack, so a shared-API project is flattened for local
295
+ // use -- generated from the same manifest as the real templates, so it cannot drift.
296
+ function samFlatTemplate(answers, apps, envKeys = []) {
297
+ const httpApi = answers.apiGateway ? samHttpApiResource() : "";
298
+ const sharedLayer = answers.layer ? samSharedLayerResource(answers, "root") : "";
299
+ const functions = apps
300
+ .flatMap((service) => service.functions.map((fn) => samFunction(answers, service, fn, envKeys, "root")))
301
+ .join("\n");
302
+ const dynamo = answers.database === "dynamodb"
303
+ ? apps.map((service) => samDynamoResources(service)).join("")
304
+ : "";
305
+ const outputs = answers.apiGateway
306
+ ? `
307
+ Outputs:
308
+ ApiUrl:
309
+ Description: HTTP API URL for every function in this project
310
+ Value: !Sub https://\${HttpApi}.execute-api.\${AWS::Region}.amazonaws.com
311
+ `
312
+ : "";
313
+ return `AWSTemplateFormatVersion: "2010-09-09"
314
+ Transform: AWS::Serverless-2016-10-31
315
+ Description: ${answers.name}
316
+
317
+ ${samParameters(envKeys)}Globals:
318
+ Function:
319
+ Timeout: 10
320
+ MemorySize: ${answers.memorySize}
321
+
322
+ Resources:
323
+ ${httpApi}${sharedLayer}${functions}${dynamo}${outputs}`;
324
+ }
325
+ function samRootTemplate(answers, apps, envKeys = []) {
326
+ const shared = answers.apiGateway && answers.sharedApi;
327
+ const parameterNames = [environments_js_1.APP_ENVIRONMENT_PARAM, ...stageParameterNames(envKeys)];
328
+ const passed = parameterNames.map((name) => ` ${name}: !Ref ${name}`);
329
+ // The API is created here, so its id is handed to every service that attaches to it.
330
+ if (shared) {
331
+ passed.push(` ${SHARED_API_PARAM}: !Ref SharedHttpApi`);
332
+ }
333
+ const parametersBlock = passed.length > 0 ? ` Parameters:\n${passed.join("\n")}\n` : "";
334
+ const sharedApi = shared ? samSharedApiResources(answers) : "";
335
+ const services = apps
336
+ .map((service) => {
337
+ const resource = `${(0, helpers_js_1.pascal)(service.name)}Stack`;
338
+ return ` ${resource}:
339
+ Type: AWS::Serverless::Application
340
+ Properties:
341
+ Location: ${(0, types_js_1.serviceTemplatePath)(service.name)}
342
+ ${parametersBlock}`;
343
+ })
344
+ .join("\n");
345
+ // One shared API has one URL; per-service APIs each report their own.
346
+ const outputs = !answers.apiGateway
347
+ ? ""
348
+ : shared
349
+ ? `
350
+ Outputs:
351
+ ApiUrl:
352
+ Description: HTTP API URL for every service in this project
353
+ Value: !Sub https://\${SharedHttpApi}.execute-api.\${AWS::Region}.amazonaws.com
354
+ `
355
+ : `
356
+ Outputs:
357
+ ${apps
358
+ .map((service) => ` ${(0, helpers_js_1.pascal)(service.name)}ApiUrl:
359
+ Description: HTTP API URL for the ${service.name} service
360
+ Value: !GetAtt ${(0, helpers_js_1.pascal)(service.name)}Stack.Outputs.HttpApiUrl`)
361
+ .join("\n")}
362
+ `;
363
+ return `AWSTemplateFormatVersion: "2010-09-09"
364
+ Transform: AWS::Serverless-2016-10-31
365
+ Description: ${answers.name} root stack
366
+
367
+ ${samParameters(envKeys)}Resources:
368
+ ${sharedApi}${services}${outputs}`;
369
+ }
@@ -0,0 +1,6 @@
1
+ import type { InitAnswers } from "../types.js";
2
+ export declare function sharedLogger(runtime: InitAnswers["runtime"]): string;
3
+ export declare function sharedLayerLogger(): string;
4
+ export declare function sharedLayerLoggerTypes(): string;
5
+ export declare function sharedDb(answers: InitAnswers): string | undefined;
6
+ export declare function prismaSchema(): string;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sharedLogger = sharedLogger;
4
+ exports.sharedLayerLogger = sharedLayerLogger;
5
+ exports.sharedLayerLoggerTypes = sharedLayerLoggerTypes;
6
+ exports.sharedDb = sharedDb;
7
+ exports.prismaSchema = prismaSchema;
8
+ function sharedLogger(runtime) {
9
+ if (runtime === "python") {
10
+ return `import logging
11
+
12
+ logger = logging.getLogger("slskit")
13
+ if not logger.handlers:
14
+ logging.basicConfig(level=logging.INFO)
15
+ `;
16
+ }
17
+ if (runtime === "typescript") {
18
+ return `export const logger = {
19
+ info(message: string, extra?: unknown): void {
20
+ console.log(JSON.stringify({ level: "info", message, extra }));
21
+ },
22
+ error(message: string, extra?: unknown): void {
23
+ console.error(JSON.stringify({ level: "error", message, extra }));
24
+ },
25
+ };
26
+ `;
27
+ }
28
+ return `export const logger = {
29
+ info(message, extra) {
30
+ console.log(JSON.stringify({ level: "info", message, extra }));
31
+ },
32
+ error(message, extra) {
33
+ console.error(JSON.stringify({ level: "error", message, extra }));
34
+ },
35
+ };
36
+ `;
37
+ }
38
+ function sharedLayerLogger() {
39
+ return `exports.logger = {
40
+ info(message, extra) {
41
+ console.log(JSON.stringify({ level: "info", message, extra }));
42
+ },
43
+ error(message, extra) {
44
+ console.error(JSON.stringify({ level: "error", message, extra }));
45
+ },
46
+ };
47
+ `;
48
+ }
49
+ // The layer ships runtime JS, so a TypeScript project needs these declarations to type it.
50
+ function sharedLayerLoggerTypes() {
51
+ return `export declare const logger: {
52
+ info(message: string, extra?: unknown): void;
53
+ error(message: string, extra?: unknown): void;
54
+ };
55
+ `;
56
+ }
57
+ function sharedDb(answers) {
58
+ if (answers.database === "none") {
59
+ return undefined;
60
+ }
61
+ if (answers.runtime === "python") {
62
+ if (answers.database === "prisma") {
63
+ return `from prisma import Prisma
64
+
65
+ prisma = Prisma()
66
+
67
+
68
+ async def connect_db():
69
+ if not prisma.is_connected():
70
+ await prisma.connect()
71
+ return prisma
72
+ `;
73
+ }
74
+ if (answers.database === "mongoose") {
75
+ return `import os
76
+ from pymongo import MongoClient
77
+
78
+ _client = None
79
+
80
+
81
+ def connect_db():
82
+ global _client
83
+ if _client is None:
84
+ uri = os.environ.get("MONGODB_URI", "mongodb://127.0.0.1:27017/app")
85
+ _client = MongoClient(uri)
86
+ return _client.get_default_database()
87
+ `;
88
+ }
89
+ return `import os
90
+ import boto3
91
+
92
+ dynamodb = boto3.resource("dynamodb")
93
+
94
+
95
+ def users_table():
96
+ return dynamodb.Table(os.environ.get("USERS_TABLE", "users"))
97
+
98
+
99
+ def products_table():
100
+ return dynamodb.Table(os.environ.get("PRODUCTS_TABLE", "products"))
101
+ `;
102
+ }
103
+ if (answers.database === "prisma") {
104
+ return `import { PrismaClient } from "@prisma/client";
105
+
106
+ export const prisma = new PrismaClient();
107
+ `;
108
+ }
109
+ if (answers.database === "mongoose") {
110
+ const typed = answers.runtime === "typescript";
111
+ return `import mongoose from "mongoose";
112
+
113
+ export async function connectDb()${typed ? ": Promise<typeof mongoose>" : ""} {
114
+ if (mongoose.connection.readyState === 0) {
115
+ const uri = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/app";
116
+ await mongoose.connect(uri);
117
+ }
118
+
119
+ return mongoose;
120
+ }
121
+ `;
122
+ }
123
+ return `import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
124
+ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
125
+
126
+ const client = new DynamoDBClient({});
127
+ export const docClient = DynamoDBDocumentClient.from(client);
128
+
129
+ export const USERS_TABLE = process.env.USERS_TABLE ?? "users";
130
+ export const PRODUCTS_TABLE = process.env.PRODUCTS_TABLE ?? "products";
131
+ `;
132
+ }
133
+ function prismaSchema() {
134
+ // rhel-openssl-3.0.x is the query engine for the Amazon Linux 2023 Lambda runtimes.
135
+ return `generator client {
136
+ provider = "prisma-client-js"
137
+ binaryTargets = ["native", "rhel-openssl-3.0.x"]
138
+ }
139
+
140
+ datasource db {
141
+ provider = "postgresql"
142
+ url = env("DATABASE_URL")
143
+ }
144
+
145
+ model User {
146
+ id String @id @default(cuid())
147
+ email String @unique
148
+ password String
149
+ createdAt DateTime @default(now())
150
+ }
151
+
152
+ model Product {
153
+ id String @id @default(cuid())
154
+ name String
155
+ price Int
156
+ createdAt DateTime @default(now())
157
+ }
158
+ `;
159
+ }
@@ -0,0 +1,47 @@
1
+ export type RuntimeId = "typescript" | "javascript" | "python";
2
+ export type DatabaseId = "none" | "prisma" | "mongoose" | "dynamodb";
3
+ export type MemorySize = 128 | 256 | 512 | 1024 | 2048 | 3008 | 4096 | 10240;
4
+ export declare const MEMORY_SIZES: MemorySize[];
5
+ export interface InitOptions {
6
+ name?: string;
7
+ runtime?: string;
8
+ database?: string;
9
+ apiGateway?: string | boolean;
10
+ sharedApi?: string | boolean;
11
+ layer?: string | boolean;
12
+ memory?: string | number;
13
+ force?: boolean;
14
+ }
15
+ export interface InitAnswers {
16
+ name: string;
17
+ runtime: RuntimeId;
18
+ database: DatabaseId;
19
+ apiGateway: boolean;
20
+ sharedApi: boolean;
21
+ layer: boolean;
22
+ memorySize: MemorySize;
23
+ force: boolean;
24
+ }
25
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
26
+ export interface ServiceFunction {
27
+ name: string;
28
+ httpPath: string;
29
+ method: HttpMethod;
30
+ runtime?: RuntimeId;
31
+ memorySize?: MemorySize;
32
+ }
33
+ export interface ServiceDef {
34
+ name: string;
35
+ functions: ServiceFunction[];
36
+ }
37
+ export declare const LAMBDA_APPS: ServiceDef[];
38
+ export declare const INFRA_FILE = "template.yaml";
39
+ export declare const TEMPLATES_DIR = "templates";
40
+ export declare function lambdaRuntime(runtime: RuntimeId): string;
41
+ export declare function handlerFileName(runtime: RuntimeId): string;
42
+ export declare function sourceExt(runtime: RuntimeId): "ts" | "js" | "py";
43
+ export declare const SRC_DIR = "src";
44
+ export declare function sharedCodeDir(answers: InitAnswers): string;
45
+ export declare function serviceTemplatePath(appName: string): string;
46
+ export declare function runtimeFamily(runtime: RuntimeId): "node" | "python";
47
+ export declare function sameRuntimeFamily(a: RuntimeId, b: RuntimeId): boolean;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SRC_DIR = exports.TEMPLATES_DIR = exports.INFRA_FILE = exports.LAMBDA_APPS = exports.MEMORY_SIZES = void 0;
4
+ exports.lambdaRuntime = lambdaRuntime;
5
+ exports.handlerFileName = handlerFileName;
6
+ exports.sourceExt = sourceExt;
7
+ exports.sharedCodeDir = sharedCodeDir;
8
+ exports.serviceTemplatePath = serviceTemplatePath;
9
+ exports.runtimeFamily = runtimeFamily;
10
+ exports.sameRuntimeFamily = sameRuntimeFamily;
11
+ exports.MEMORY_SIZES = [
12
+ 128, 256, 512, 1024, 2048, 3008, 4096, 10240,
13
+ ];
14
+ exports.LAMBDA_APPS = [
15
+ {
16
+ name: "auth",
17
+ functions: [
18
+ { name: "login", httpPath: "/auth/login", method: "POST" },
19
+ { name: "register", httpPath: "/auth/register", method: "POST" },
20
+ ],
21
+ },
22
+ {
23
+ name: "product",
24
+ functions: [
25
+ { name: "getProducts", httpPath: "/products", method: "GET" },
26
+ { name: "createProduct", httpPath: "/products", method: "POST" },
27
+ ],
28
+ },
29
+ ];
30
+ // Every generated project is an AWS SAM project: a root stack that nests one
31
+ // template per application.
32
+ exports.INFRA_FILE = "template.yaml";
33
+ // Service templates are gathered in one directory rather than buried next to the
34
+ // handlers, so the whole infrastructure of a project is readable in one place --
35
+ // the thing that matters most once a project has more than a handful of services.
36
+ exports.TEMPLATES_DIR = "templates";
37
+ function lambdaRuntime(runtime) {
38
+ return runtime === "python" ? "python3.12" : "nodejs20.x";
39
+ }
40
+ function handlerFileName(runtime) {
41
+ if (runtime === "typescript") {
42
+ return "handler.ts";
43
+ }
44
+ if (runtime === "javascript") {
45
+ return "handler.js";
46
+ }
47
+ return "handler.py";
48
+ }
49
+ function sourceExt(runtime) {
50
+ if (runtime === "python") {
51
+ return "py";
52
+ }
53
+ return runtime === "typescript" ? "ts" : "js";
54
+ }
55
+ // Every generated source file lives under src/, which is also the Lambda CodeUri.
56
+ exports.SRC_DIR = "src";
57
+ function sharedCodeDir(answers) {
58
+ if (!answers.layer) {
59
+ return `${exports.SRC_DIR}/shared`;
60
+ }
61
+ return answers.runtime === "python"
62
+ ? `${exports.SRC_DIR}/shared/python`
63
+ : `${exports.SRC_DIR}/shared/nodejs`;
64
+ }
65
+ function serviceTemplatePath(appName) {
66
+ return `${exports.TEMPLATES_DIR}/${appName}.yaml`;
67
+ }
68
+ // Node and Python are different runtime families: a layer/db built for one can never
69
+ // be attached to or imported by a function running the other.
70
+ function runtimeFamily(runtime) {
71
+ return runtime === "python" ? "python" : "node";
72
+ }
73
+ function sameRuntimeFamily(a, b) {
74
+ return runtimeFamily(a) === runtimeFamily(b);
75
+ }
@@ -0,0 +1,2 @@
1
+ import type { RmOptions } from "./types.js";
2
+ export declare function rmAction(options: RmOptions): Promise<void>;