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.
- package/CHANGELOG.md +61 -0
- package/COMMAND.md +636 -0
- package/LICENSE +21 -0
- package/NOTICE.md +22 -0
- package/README.md +168 -0
- package/bin/slskit.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +25 -0
- package/dist/commands/base-command.d.ts +5 -0
- package/dist/commands/base-command.js +29 -0
- package/dist/commands/configure/action.d.ts +2 -0
- package/dist/commands/configure/action.js +120 -0
- package/dist/commands/configure/aws-cli.d.ts +16 -0
- package/dist/commands/configure/aws-cli.js +135 -0
- package/dist/commands/configure/index.d.ts +2 -0
- package/dist/commands/configure/index.js +20 -0
- package/dist/commands/configure/prompts.d.ts +8 -0
- package/dist/commands/configure/prompts.js +117 -0
- package/dist/commands/configure/types.d.ts +24 -0
- package/dist/commands/configure/types.js +6 -0
- package/dist/commands/deploy/action.d.ts +2 -0
- package/dist/commands/deploy/action.js +115 -0
- package/dist/commands/deploy/index.d.ts +2 -0
- package/dist/commands/deploy/index.js +23 -0
- package/dist/commands/deploy/sam-deploy.d.ts +10 -0
- package/dist/commands/deploy/sam-deploy.js +127 -0
- package/dist/commands/deploy/scope.d.ts +9 -0
- package/dist/commands/deploy/scope.js +34 -0
- package/dist/commands/deploy/types.d.ts +20 -0
- package/dist/commands/deploy/types.js +2 -0
- package/dist/commands/env/action.d.ts +11 -0
- package/dist/commands/env/action.js +274 -0
- package/dist/commands/env/dotenv.d.ts +6 -0
- package/dist/commands/env/dotenv.js +99 -0
- package/dist/commands/env/index.d.ts +2 -0
- package/dist/commands/env/index.js +76 -0
- package/dist/commands/env/keys.d.ts +8 -0
- package/dist/commands/env/keys.js +41 -0
- package/dist/commands/env/parameters.d.ts +7 -0
- package/dist/commands/env/parameters.js +63 -0
- package/dist/commands/env/templates.d.ts +3 -0
- package/dist/commands/env/templates.js +65 -0
- package/dist/commands/env/types.d.ts +21 -0
- package/dist/commands/env/types.js +2 -0
- package/dist/commands/function/action.d.ts +2 -0
- package/dist/commands/function/action.js +71 -0
- package/dist/commands/function/generator.d.ts +8 -0
- package/dist/commands/function/generator.js +58 -0
- package/dist/commands/function/index.d.ts +2 -0
- package/dist/commands/function/index.js +26 -0
- package/dist/commands/function/manifest.d.ts +5 -0
- package/dist/commands/function/manifest.js +57 -0
- package/dist/commands/function/prompts.d.ts +18 -0
- package/dist/commands/function/prompts.js +196 -0
- package/dist/commands/function/tooling.d.ts +2 -0
- package/dist/commands/function/tooling.js +43 -0
- package/dist/commands/function/types.d.ts +52 -0
- package/dist/commands/function/types.js +2 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/init/action.d.ts +2 -0
- package/dist/commands/init/action.js +36 -0
- package/dist/commands/init/content.d.ts +2 -0
- package/dist/commands/init/content.js +97 -0
- package/dist/commands/init/index.d.ts +2 -0
- package/dist/commands/init/index.js +30 -0
- package/dist/commands/init/manifest.d.ts +6 -0
- package/dist/commands/init/manifest.js +147 -0
- package/dist/commands/init/prompts.d.ts +6 -0
- package/dist/commands/init/prompts.js +155 -0
- package/dist/commands/init/scaffold.d.ts +7 -0
- package/dist/commands/init/scaffold.js +85 -0
- package/dist/commands/init/templates/helpers.d.ts +9 -0
- package/dist/commands/init/templates/helpers.js +42 -0
- package/dist/commands/init/templates/node.d.ts +4 -0
- package/dist/commands/init/templates/node.js +99 -0
- package/dist/commands/init/templates/project.d.ts +8 -0
- package/dist/commands/init/templates/project.js +239 -0
- package/dist/commands/init/templates/python.d.ts +4 -0
- package/dist/commands/init/templates/python.js +65 -0
- package/dist/commands/init/templates/sam.d.ts +4 -0
- package/dist/commands/init/templates/sam.js +369 -0
- package/dist/commands/init/templates/shared-code.d.ts +6 -0
- package/dist/commands/init/templates/shared-code.js +159 -0
- package/dist/commands/init/types.d.ts +47 -0
- package/dist/commands/init/types.js +75 -0
- package/dist/commands/rm/action.d.ts +2 -0
- package/dist/commands/rm/action.js +89 -0
- package/dist/commands/rm/index.d.ts +2 -0
- package/dist/commands/rm/index.js +25 -0
- package/dist/commands/rm/plan.d.ts +19 -0
- package/dist/commands/rm/plan.js +78 -0
- package/dist/commands/rm/prompts.d.ts +4 -0
- package/dist/commands/rm/prompts.js +107 -0
- package/dist/commands/rm/types.d.ts +7 -0
- package/dist/commands/rm/types.js +2 -0
- package/dist/commands/run/action.d.ts +2 -0
- package/dist/commands/run/action.js +171 -0
- package/dist/commands/run/index.d.ts +2 -0
- package/dist/commands/run/index.js +21 -0
- package/dist/commands/run/local-template.d.ts +7 -0
- package/dist/commands/run/local-template.js +51 -0
- package/dist/commands/run/manifest.d.ts +2 -0
- package/dist/commands/run/manifest.js +20 -0
- package/dist/commands/run/resources.d.ts +20 -0
- package/dist/commands/run/resources.js +93 -0
- package/dist/commands/run/sam-cli.d.ts +10 -0
- package/dist/commands/run/sam-cli.js +183 -0
- package/dist/commands/run/types.d.ts +18 -0
- package/dist/commands/run/types.js +2 -0
- package/dist/commands/run/watch.d.ts +11 -0
- package/dist/commands/run/watch.js +131 -0
- package/dist/core/context.d.ts +15 -0
- package/dist/core/context.js +41 -0
- package/dist/core/credentials.d.ts +10 -0
- package/dist/core/credentials.js +22 -0
- package/dist/core/environments.d.ts +46 -0
- package/dist/core/environments.js +161 -0
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.js +11 -0
- package/dist/core/flags-docs.d.ts +2 -0
- package/dist/core/flags-docs.js +63 -0
- package/dist/core/is-ci.d.ts +4 -0
- package/dist/core/is-ci.js +16 -0
- package/dist/core/logger.d.ts +4 -0
- package/dist/core/logger.js +19 -0
- package/dist/core/run.d.ts +1 -0
- package/dist/core/run.js +27 -0
- package/dist/core/scope.d.ts +33 -0
- package/dist/core/scope.js +108 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +9 -0
- package/dist/program.d.ts +2 -0
- package/dist/program.js +26 -0
- 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
|
+
}
|