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,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildFileMap = buildFileMap;
|
|
4
|
+
const environments_js_1 = require("../../core/environments.js");
|
|
5
|
+
const manifest_js_1 = require("./manifest.js");
|
|
6
|
+
const types_js_1 = require("./types.js");
|
|
7
|
+
const helpers_js_1 = require("./templates/helpers.js");
|
|
8
|
+
const project_js_1 = require("./templates/project.js");
|
|
9
|
+
const shared_code_js_1 = require("./templates/shared-code.js");
|
|
10
|
+
const node_js_1 = require("./templates/node.js");
|
|
11
|
+
const python_js_1 = require("./templates/python.js");
|
|
12
|
+
const sam_js_1 = require("./templates/sam.js");
|
|
13
|
+
function handlerSource(answers, appName, fn) {
|
|
14
|
+
if (answers.runtime === "python") {
|
|
15
|
+
return (0, python_js_1.pythonHandler)(appName, fn);
|
|
16
|
+
}
|
|
17
|
+
return (0, node_js_1.nodeHandler)(answers, appName, fn, answers.runtime === "typescript");
|
|
18
|
+
}
|
|
19
|
+
function serviceSource(answers, fn) {
|
|
20
|
+
if (answers.runtime === "python") {
|
|
21
|
+
return (0, python_js_1.pythonService)(answers, fn);
|
|
22
|
+
}
|
|
23
|
+
return (0, node_js_1.nodeService)(answers, fn, answers.runtime === "typescript");
|
|
24
|
+
}
|
|
25
|
+
function dotenvKeys(body) {
|
|
26
|
+
return body
|
|
27
|
+
.split("\n")
|
|
28
|
+
.map((line) => line.trim())
|
|
29
|
+
.filter((line) => line && !line.startsWith("#") && line.includes("="))
|
|
30
|
+
.map((line) => line.slice(0, line.indexOf("=")).trim())
|
|
31
|
+
.filter((key) => key && key !== environments_js_1.APP_ENVIRONMENT_KEY);
|
|
32
|
+
}
|
|
33
|
+
function buildFileMap(answers, envKeys = []) {
|
|
34
|
+
const files = {
|
|
35
|
+
".gitignore": (0, project_js_1.gitignore)(answers.runtime),
|
|
36
|
+
"README.md": (0, project_js_1.readme)(answers),
|
|
37
|
+
"package.json": (0, project_js_1.packageJson)(answers),
|
|
38
|
+
};
|
|
39
|
+
// Exactly one environment file. Every other environment is created later by
|
|
40
|
+
// "slskit env add", which writes its own .env.<name> alongside this one.
|
|
41
|
+
const dotenv = (0, project_js_1.environmentDotenv)(environments_js_1.DEFAULT_ENVIRONMENT, answers.database);
|
|
42
|
+
files[`.env.${environments_js_1.DEFAULT_ENVIRONMENT}`] = dotenv;
|
|
43
|
+
// Whatever init seeds into that file (DATABASE_URL and friends) is an ordinary
|
|
44
|
+
// stage variable, so the templates have to declare a parameter for it too.
|
|
45
|
+
const keys = [...new Set([...envKeys, ...dotenvKeys(dotenv)])].sort();
|
|
46
|
+
if (answers.runtime === "typescript") {
|
|
47
|
+
files["tsconfig.json"] = (0, project_js_1.tsconfig)(answers);
|
|
48
|
+
}
|
|
49
|
+
if (answers.runtime === "python") {
|
|
50
|
+
files["requirements.txt"] = (0, project_js_1.requirementsTxt)(answers);
|
|
51
|
+
files[`${types_js_1.SRC_DIR}/__init__.py`] = "";
|
|
52
|
+
files[`${types_js_1.SRC_DIR}/functions/__init__.py`] = "";
|
|
53
|
+
files[`${types_js_1.SRC_DIR}/services/__init__.py`] = "";
|
|
54
|
+
files[`${types_js_1.SRC_DIR}/shared/__init__.py`] = "";
|
|
55
|
+
if (answers.layer) {
|
|
56
|
+
files[`${types_js_1.SRC_DIR}/shared/python/__init__.py`] = "";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
files["template.yaml"] = (0, sam_js_1.samRootTemplate)(answers, types_js_1.LAMBDA_APPS, keys);
|
|
60
|
+
const sharedDir = (0, types_js_1.sharedCodeDir)(answers);
|
|
61
|
+
const isNodeLayer = answers.layer && answers.runtime !== "python";
|
|
62
|
+
// A real Lambda layer is deployed raw, never esbuild-bundled, so it must ship plain CommonJS.
|
|
63
|
+
const loggerFileExt = isNodeLayer ? "js" : (0, helpers_js_1.loggerExt)(answers.runtime);
|
|
64
|
+
files[`${sharedDir}/logger.${loggerFileExt}`] = isNodeLayer
|
|
65
|
+
? (0, shared_code_js_1.sharedLayerLogger)()
|
|
66
|
+
: (0, shared_code_js_1.sharedLogger)(answers.runtime);
|
|
67
|
+
if (isNodeLayer && answers.runtime === "typescript") {
|
|
68
|
+
files[`${sharedDir}/logger.d.ts`] = (0, shared_code_js_1.sharedLayerLoggerTypes)();
|
|
69
|
+
}
|
|
70
|
+
// db stays out of the layer: a layer ships no node_modules, so anything importing a
|
|
71
|
+
// database client must be packaged with the function instead.
|
|
72
|
+
const db = (0, shared_code_js_1.sharedDb)(answers);
|
|
73
|
+
if (db) {
|
|
74
|
+
files[`${types_js_1.SRC_DIR}/shared/db.${(0, types_js_1.sourceExt)(answers.runtime)}`] = db;
|
|
75
|
+
}
|
|
76
|
+
if (answers.database === "prisma") {
|
|
77
|
+
files["prisma/schema.prisma"] = (0, shared_code_js_1.prismaSchema)();
|
|
78
|
+
}
|
|
79
|
+
const serviceExt = (0, types_js_1.sourceExt)(answers.runtime);
|
|
80
|
+
for (const app of types_js_1.LAMBDA_APPS) {
|
|
81
|
+
if (answers.runtime === "python") {
|
|
82
|
+
files[`${types_js_1.SRC_DIR}/functions/${app.name}/__init__.py`] = "";
|
|
83
|
+
files[`${types_js_1.SRC_DIR}/services/${app.name}/__init__.py`] = "";
|
|
84
|
+
}
|
|
85
|
+
files[(0, types_js_1.serviceTemplatePath)(app.name)] = (0, sam_js_1.samServiceTemplate)(answers, app, keys);
|
|
86
|
+
for (const fn of app.functions) {
|
|
87
|
+
if (answers.runtime === "python") {
|
|
88
|
+
files[`${types_js_1.SRC_DIR}/functions/${app.name}/${fn.name}/__init__.py`] = "";
|
|
89
|
+
}
|
|
90
|
+
files[`${types_js_1.SRC_DIR}/functions/${app.name}/${fn.name}/${(0, types_js_1.handlerFileName)(answers.runtime)}`] = handlerSource(answers, app.name, fn);
|
|
91
|
+
files[`${types_js_1.SRC_DIR}/services/${app.name}/${fn.name}.${serviceExt}`] =
|
|
92
|
+
serviceSource(answers, fn);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
files["slskit.json"] = `${JSON.stringify((0, manifest_js_1.buildProjectManifest)(answers, types_js_1.LAMBDA_APPS, Object.keys(files)), null, 2)}\n`;
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerInitCommand = registerInitCommand;
|
|
4
|
+
const run_js_1 = require("../../core/run.js");
|
|
5
|
+
function registerInitCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("init")
|
|
8
|
+
.description("Scaffold a multi-service AWS SAM project")
|
|
9
|
+
.argument("[name]", "Project name (example: my-lambda-app)")
|
|
10
|
+
.option("-r, --runtime <runtime>", "typescript | javascript | python")
|
|
11
|
+
.option("--database <database>", "none | prisma | mongoose | dynamodb")
|
|
12
|
+
.option("--api-gateway <yes|no>", "Expose the functions over HTTP")
|
|
13
|
+
.option("--shared-api <yes|no>", "One API Gateway for every function (default: no — one per service)")
|
|
14
|
+
.option("--layer <yes|no>", "Use shared/ as a common Lambda layer")
|
|
15
|
+
.option("--memory <mb>", "Function memory size: 128 | 256 | 512 | 1024 | 2048 | 3008 | 4096 | 10240")
|
|
16
|
+
.option("-f, --force", "Overwrite files if the folder already exists", false)
|
|
17
|
+
.action((name, options) => (0, run_js_1.runCommand)(async () => {
|
|
18
|
+
const { initAction } = await import("./action.js");
|
|
19
|
+
await initAction({
|
|
20
|
+
name: name ?? options.name,
|
|
21
|
+
runtime: options.runtime,
|
|
22
|
+
database: options.database,
|
|
23
|
+
apiGateway: options.apiGateway,
|
|
24
|
+
sharedApi: options.sharedApi,
|
|
25
|
+
layer: options.layer,
|
|
26
|
+
memory: options.memory,
|
|
27
|
+
force: options.force,
|
|
28
|
+
});
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InitAnswers, ServiceDef } from "./types.js";
|
|
2
|
+
export interface PreservedState {
|
|
3
|
+
version?: string;
|
|
4
|
+
environments?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildProjectManifest(answers: InitAnswers, apps: ServiceDef[], generatedFiles: string[], preserved?: PreservedState): Record<string, unknown>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildProjectManifest = buildProjectManifest;
|
|
4
|
+
const environments_js_1 = require("../../core/environments.js");
|
|
5
|
+
const types_js_1 = require("./types.js");
|
|
6
|
+
// Handler paths are relative to the Lambda CodeUri, which is the project root.
|
|
7
|
+
function handlerValue(runtime, appName, fnName) {
|
|
8
|
+
if (runtime === "python") {
|
|
9
|
+
return `${types_js_1.SRC_DIR}.functions.${appName}.${fnName}.handler.handler`;
|
|
10
|
+
}
|
|
11
|
+
return `${types_js_1.SRC_DIR}/functions/${appName}/${fnName}/handler.handler`;
|
|
12
|
+
}
|
|
13
|
+
function buildProjectManifest(answers, apps, generatedFiles, preserved = {}) {
|
|
14
|
+
const sharedDir = (0, types_js_1.sharedCodeDir)(answers);
|
|
15
|
+
const applications = apps.map((app) => ({
|
|
16
|
+
name: app.name,
|
|
17
|
+
path: `${types_js_1.SRC_DIR}/functions/${app.name}`,
|
|
18
|
+
template: answers.sharedApi ? types_js_1.INFRA_FILE : (0, types_js_1.serviceTemplatePath)(app.name),
|
|
19
|
+
functions: app.functions.map((fn) => {
|
|
20
|
+
const fnRuntime = fn.runtime ?? answers.runtime;
|
|
21
|
+
const sameFamily = (0, types_js_1.sameRuntimeFamily)(fnRuntime, answers.runtime);
|
|
22
|
+
const attachesLayer = answers.layer && sameFamily;
|
|
23
|
+
return {
|
|
24
|
+
id: `${app.name}.${fn.name}`,
|
|
25
|
+
name: fn.name,
|
|
26
|
+
path: `${types_js_1.SRC_DIR}/functions/${app.name}/${fn.name}`,
|
|
27
|
+
handlerFile: `${types_js_1.SRC_DIR}/functions/${app.name}/${fn.name}/${(0, types_js_1.handlerFileName)(fnRuntime)}`,
|
|
28
|
+
handler: handlerValue(fnRuntime, app.name, fn.name),
|
|
29
|
+
service: {
|
|
30
|
+
application: app.name,
|
|
31
|
+
path: `${types_js_1.SRC_DIR}/services/${app.name}/${fn.name}.${(0, types_js_1.sourceExt)(fnRuntime)}`,
|
|
32
|
+
export: fn.name,
|
|
33
|
+
},
|
|
34
|
+
runtime: (0, types_js_1.lambdaRuntime)(fnRuntime),
|
|
35
|
+
memorySize: fn.memorySize ?? answers.memorySize,
|
|
36
|
+
apiGateway: answers.apiGateway
|
|
37
|
+
? {
|
|
38
|
+
enabled: true,
|
|
39
|
+
gateway: answers.sharedApi ? `${answers.name}-http-api` : app.name,
|
|
40
|
+
path: fn.httpPath,
|
|
41
|
+
method: fn.method,
|
|
42
|
+
}
|
|
43
|
+
: { enabled: false },
|
|
44
|
+
layer: attachesLayer
|
|
45
|
+
? { enabled: true, name: "shared", path: `${types_js_1.SRC_DIR}/shared` }
|
|
46
|
+
: { enabled: false },
|
|
47
|
+
};
|
|
48
|
+
}),
|
|
49
|
+
}));
|
|
50
|
+
const services = apps.map((app) => ({
|
|
51
|
+
application: app.name,
|
|
52
|
+
path: `${types_js_1.SRC_DIR}/services/${app.name}`,
|
|
53
|
+
files: app.functions.map((fn) => `${types_js_1.SRC_DIR}/services/${app.name}/${fn.name}.${(0, types_js_1.sourceExt)(fn.runtime ?? answers.runtime)}`),
|
|
54
|
+
}));
|
|
55
|
+
const routes = answers.apiGateway
|
|
56
|
+
? apps.flatMap((app) => app.functions.map((fn) => ({
|
|
57
|
+
path: fn.httpPath,
|
|
58
|
+
method: fn.method,
|
|
59
|
+
function: `${app.name}.${fn.name}`,
|
|
60
|
+
})))
|
|
61
|
+
: [];
|
|
62
|
+
const directories = [
|
|
63
|
+
types_js_1.SRC_DIR,
|
|
64
|
+
`${types_js_1.SRC_DIR}/functions`,
|
|
65
|
+
`${types_js_1.SRC_DIR}/services`,
|
|
66
|
+
...apps.flatMap((app) => [
|
|
67
|
+
`${types_js_1.SRC_DIR}/functions/${app.name}`,
|
|
68
|
+
...app.functions.map((fn) => `${types_js_1.SRC_DIR}/functions/${app.name}/${fn.name}`),
|
|
69
|
+
`${types_js_1.SRC_DIR}/services/${app.name}`,
|
|
70
|
+
]),
|
|
71
|
+
sharedDir,
|
|
72
|
+
...(answers.sharedApi ? [] : [types_js_1.TEMPLATES_DIR]),
|
|
73
|
+
...(answers.database === "prisma" ? ["prisma"] : []),
|
|
74
|
+
];
|
|
75
|
+
const layerAttachedTo = apps.flatMap((app) => app.functions
|
|
76
|
+
.filter((fn) => (0, types_js_1.sameRuntimeFamily)(fn.runtime ?? answers.runtime, answers.runtime))
|
|
77
|
+
.map((fn) => `${app.name}.${fn.name}`));
|
|
78
|
+
return {
|
|
79
|
+
name: answers.name,
|
|
80
|
+
version: preserved.version ?? "0.1.0",
|
|
81
|
+
generatedBy: "slskit",
|
|
82
|
+
runtime: {
|
|
83
|
+
id: answers.runtime,
|
|
84
|
+
lambda: (0, types_js_1.lambdaRuntime)(answers.runtime),
|
|
85
|
+
},
|
|
86
|
+
framework: {
|
|
87
|
+
id: "sam",
|
|
88
|
+
files: {
|
|
89
|
+
root: types_js_1.INFRA_FILE,
|
|
90
|
+
application: types_js_1.INFRA_FILE,
|
|
91
|
+
applications: answers.sharedApi
|
|
92
|
+
? []
|
|
93
|
+
: apps.map((app) => (0, types_js_1.serviceTemplatePath)(app.name)),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
database: {
|
|
97
|
+
id: answers.database,
|
|
98
|
+
enabled: answers.database !== "none",
|
|
99
|
+
},
|
|
100
|
+
functions: {
|
|
101
|
+
memorySize: answers.memorySize,
|
|
102
|
+
},
|
|
103
|
+
apiGateway: answers.apiGateway
|
|
104
|
+
? {
|
|
105
|
+
enabled: true,
|
|
106
|
+
name: `${answers.name}-http-api`,
|
|
107
|
+
type: "HttpApi",
|
|
108
|
+
// SAM only resolves an ApiId inside the template that declares the API, so
|
|
109
|
+
// one shared API means one flat template and per-service APIs mean one each.
|
|
110
|
+
perService: !answers.sharedApi,
|
|
111
|
+
templates: answers.sharedApi
|
|
112
|
+
? [types_js_1.INFRA_FILE]
|
|
113
|
+
: apps.map((app) => (0, types_js_1.serviceTemplatePath)(app.name)),
|
|
114
|
+
attachAllFunctions: true,
|
|
115
|
+
routes,
|
|
116
|
+
}
|
|
117
|
+
: { enabled: false },
|
|
118
|
+
layer: answers.layer
|
|
119
|
+
? {
|
|
120
|
+
enabled: true,
|
|
121
|
+
name: `${answers.name}-shared`,
|
|
122
|
+
path: `${types_js_1.SRC_DIR}/shared`,
|
|
123
|
+
source: sharedDir,
|
|
124
|
+
templates: answers.sharedApi
|
|
125
|
+
? [types_js_1.INFRA_FILE]
|
|
126
|
+
: apps.map((app) => (0, types_js_1.serviceTemplatePath)(app.name)),
|
|
127
|
+
compatibleRuntimes: [(0, types_js_1.lambdaRuntime)(answers.runtime)],
|
|
128
|
+
attachAllFunctions: layerAttachedTo.length === applications.flatMap((a) => a.functions).length,
|
|
129
|
+
attachedTo: layerAttachedTo,
|
|
130
|
+
}
|
|
131
|
+
: { enabled: false, path: `${types_js_1.SRC_DIR}/shared` },
|
|
132
|
+
environments: preserved.environments ?? {
|
|
133
|
+
default: environments_js_1.DEFAULT_ENVIRONMENT,
|
|
134
|
+
list: {
|
|
135
|
+
[environments_js_1.DEFAULT_ENVIRONMENT]: {
|
|
136
|
+
stackName: (0, environments_js_1.stackNameFor)(answers.name, environments_js_1.DEFAULT_ENVIRONMENT),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
applications,
|
|
141
|
+
services,
|
|
142
|
+
structure: {
|
|
143
|
+
directories,
|
|
144
|
+
files: [...generatedFiles, "slskit.json"].sort(),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InitAnswers, InitOptions, MemorySize, RuntimeId } from "./types.js";
|
|
2
|
+
export declare const RUNTIME_ALIASES: Record<string, RuntimeId>;
|
|
3
|
+
export declare function parseAlias<T extends string>(value: string | undefined, aliases: Record<string, T>, label: string): T | undefined;
|
|
4
|
+
export declare function parseMemory(value: string | number | undefined): MemorySize | undefined;
|
|
5
|
+
export declare function memoryLabel(size: MemorySize): string;
|
|
6
|
+
export declare function collectAnswers(options: InitOptions): Promise<InitAnswers>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RUNTIME_ALIASES = void 0;
|
|
4
|
+
exports.parseAlias = parseAlias;
|
|
5
|
+
exports.parseMemory = parseMemory;
|
|
6
|
+
exports.memoryLabel = memoryLabel;
|
|
7
|
+
exports.collectAnswers = collectAnswers;
|
|
8
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
9
|
+
const types_js_1 = require("./types.js");
|
|
10
|
+
exports.RUNTIME_ALIASES = {
|
|
11
|
+
typescript: "typescript",
|
|
12
|
+
ts: "typescript",
|
|
13
|
+
"node-ts": "typescript",
|
|
14
|
+
javascript: "javascript",
|
|
15
|
+
js: "javascript",
|
|
16
|
+
node: "javascript",
|
|
17
|
+
nodejs: "javascript",
|
|
18
|
+
python: "python",
|
|
19
|
+
py: "python",
|
|
20
|
+
python3: "python",
|
|
21
|
+
};
|
|
22
|
+
const DATABASE_ALIASES = {
|
|
23
|
+
none: "none",
|
|
24
|
+
skip: "none",
|
|
25
|
+
no: "none",
|
|
26
|
+
prisma: "prisma",
|
|
27
|
+
mongoose: "mongoose",
|
|
28
|
+
mongo: "mongoose",
|
|
29
|
+
mongodb: "mongoose",
|
|
30
|
+
dynamodb: "dynamodb",
|
|
31
|
+
dynamo: "dynamodb",
|
|
32
|
+
documentclient: "dynamodb",
|
|
33
|
+
};
|
|
34
|
+
function parseAlias(value, aliases, label) {
|
|
35
|
+
if (!value) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const parsed = aliases[value.trim().toLowerCase()];
|
|
39
|
+
if (!parsed) {
|
|
40
|
+
throw new errors_js_1.CliError(`Unknown ${label} "${value}". Expected ${Object.keys(aliases).join(", ")}.`);
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
function parseYesNo(value, label) {
|
|
45
|
+
if (typeof value === "boolean") {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (value === undefined || value === "") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const normalized = String(value).trim().toLowerCase();
|
|
52
|
+
if (["yes", "y", "true", "1"].includes(normalized)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (["no", "n", "false", "0"].includes(normalized)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
throw new errors_js_1.CliError(`${label} must be yes or no.`);
|
|
59
|
+
}
|
|
60
|
+
function parseMemory(value) {
|
|
61
|
+
if (value === undefined || value === "") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const parsed = Number(String(value).replace(/mb$/i, "").trim());
|
|
65
|
+
if (!types_js_1.MEMORY_SIZES.includes(parsed)) {
|
|
66
|
+
throw new errors_js_1.CliError(`Unknown memory size "${value}". Choose ${types_js_1.MEMORY_SIZES.join(", ")}.`);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
function memoryLabel(size) {
|
|
71
|
+
if (size % 1024 === 0) {
|
|
72
|
+
return `${size} MB (${size / 1024} GB)`;
|
|
73
|
+
}
|
|
74
|
+
return `${size} MB`;
|
|
75
|
+
}
|
|
76
|
+
async function collectAnswers(options) {
|
|
77
|
+
let name = options.name?.trim();
|
|
78
|
+
let runtime = parseAlias(options.runtime, exports.RUNTIME_ALIASES, "runtime");
|
|
79
|
+
let database = parseAlias(options.database, DATABASE_ALIASES, "database");
|
|
80
|
+
let apiGateway = parseYesNo(options.apiGateway, "--api-gateway");
|
|
81
|
+
let sharedApi = parseYesNo(options.sharedApi, "--shared-api");
|
|
82
|
+
let layer = parseYesNo(options.layer, "--layer");
|
|
83
|
+
let memorySize = parseMemory(options.memory);
|
|
84
|
+
const needsPrompt = !name ||
|
|
85
|
+
!runtime ||
|
|
86
|
+
!database ||
|
|
87
|
+
apiGateway === undefined ||
|
|
88
|
+
layer === undefined ||
|
|
89
|
+
memorySize === undefined;
|
|
90
|
+
if (needsPrompt && !process.stdin.isTTY) {
|
|
91
|
+
throw new errors_js_1.CliError("Non-interactive init needs --name, --runtime, --database, --api-gateway, --layer, and --memory.");
|
|
92
|
+
}
|
|
93
|
+
if (needsPrompt) {
|
|
94
|
+
const { input, select, confirm } = await import("@inquirer/prompts");
|
|
95
|
+
name ?? (name = await input({
|
|
96
|
+
message: "Project Name:",
|
|
97
|
+
default: "my-lambda-app",
|
|
98
|
+
required: true,
|
|
99
|
+
}));
|
|
100
|
+
runtime ?? (runtime = await select({
|
|
101
|
+
message: "Runtime:",
|
|
102
|
+
choices: [
|
|
103
|
+
{ name: "TypeScript (Node.js)", value: "typescript" },
|
|
104
|
+
{ name: "JavaScript (Node.js)", value: "javascript" },
|
|
105
|
+
{ name: "Python", value: "python" },
|
|
106
|
+
],
|
|
107
|
+
}));
|
|
108
|
+
database ?? (database = await select({
|
|
109
|
+
message: "Database / ORM (Optional):",
|
|
110
|
+
choices: [
|
|
111
|
+
{ name: "None", value: "none" },
|
|
112
|
+
{ name: "Prisma", value: "prisma" },
|
|
113
|
+
{ name: "Mongoose", value: "mongoose" },
|
|
114
|
+
{ name: "DynamoDB DocumentClient", value: "dynamodb" },
|
|
115
|
+
],
|
|
116
|
+
}));
|
|
117
|
+
apiGateway ?? (apiGateway = await confirm({
|
|
118
|
+
message: "API Gateway? Expose your functions over HTTP",
|
|
119
|
+
default: true,
|
|
120
|
+
}));
|
|
121
|
+
if (apiGateway) {
|
|
122
|
+
// Defaults to no: a root stack nesting one template per service is the layout
|
|
123
|
+
// that keeps working as a project grows past CloudFormation's 500-resource cap.
|
|
124
|
+
sharedApi ?? (sharedApi = await confirm({
|
|
125
|
+
message: "One shared API Gateway for every function? (no = a template per service)",
|
|
126
|
+
default: false,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
layer ?? (layer = await confirm({
|
|
130
|
+
message: "Common Lambda layer? Use shared/ and attach it to every function",
|
|
131
|
+
default: true,
|
|
132
|
+
}));
|
|
133
|
+
memorySize ?? (memorySize = await select({
|
|
134
|
+
message: "Function memory size:",
|
|
135
|
+
choices: types_js_1.MEMORY_SIZES.map((size) => ({
|
|
136
|
+
name: memoryLabel(size),
|
|
137
|
+
value: size,
|
|
138
|
+
})),
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
const projectName = name?.trim();
|
|
142
|
+
if (!projectName) {
|
|
143
|
+
throw new errors_js_1.CliError("Project name is required.");
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
name: projectName,
|
|
147
|
+
runtime: runtime,
|
|
148
|
+
database: database,
|
|
149
|
+
apiGateway: Boolean(apiGateway),
|
|
150
|
+
sharedApi: Boolean(apiGateway) && sharedApi === true,
|
|
151
|
+
layer: Boolean(layer),
|
|
152
|
+
memorySize: memorySize,
|
|
153
|
+
force: Boolean(options.force),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InitAnswers } from "./types.js";
|
|
2
|
+
export declare function resolveProjectRoot(cwd: string, name: string): {
|
|
3
|
+
root: string;
|
|
4
|
+
folderName: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function ensureProjectRoot(root: string, force: boolean): void;
|
|
7
|
+
export declare function scaffoldProject(answers: InitAnswers, cwd?: string): Promise<string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveProjectRoot = resolveProjectRoot;
|
|
7
|
+
exports.ensureProjectRoot = ensureProjectRoot;
|
|
8
|
+
exports.scaffoldProject = scaffoldProject;
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
13
|
+
const logger_js_1 = require("../../core/logger.js");
|
|
14
|
+
const content_js_1 = require("./content.js");
|
|
15
|
+
function run(cwd, command, args, env) {
|
|
16
|
+
logger_js_1.logger.info(`> ${command} ${args.join(" ")}`);
|
|
17
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
18
|
+
cwd,
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
21
|
+
shell: process.platform === "win32",
|
|
22
|
+
});
|
|
23
|
+
if (result.error) {
|
|
24
|
+
throw new errors_js_1.CliError(`Failed to run ${command}: ${result.error.message}`);
|
|
25
|
+
}
|
|
26
|
+
if (result.status !== 0) {
|
|
27
|
+
throw new errors_js_1.CliError(`${command} exited with code ${result.status ?? 1}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// An environment file holds real credentials, so --force must never clobber one.
|
|
31
|
+
const isEnvironmentFile = (relativePath) => relativePath.startsWith(".env");
|
|
32
|
+
function writeFiles(root, files) {
|
|
33
|
+
for (const [relativePath, contents] of Object.entries(files)) {
|
|
34
|
+
const fullPath = node_path_1.default.join(root, relativePath);
|
|
35
|
+
if (isEnvironmentFile(relativePath) && node_fs_1.default.existsSync(fullPath)) {
|
|
36
|
+
logger_js_1.logger.info(`Kept existing ${relativePath} rather than overwriting its values`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
40
|
+
node_fs_1.default.writeFileSync(fullPath, contents);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveProjectRoot(cwd, name) {
|
|
44
|
+
if (name === ".") {
|
|
45
|
+
return { root: cwd, folderName: node_path_1.default.basename(cwd) };
|
|
46
|
+
}
|
|
47
|
+
return { root: node_path_1.default.join(cwd, name), folderName: name };
|
|
48
|
+
}
|
|
49
|
+
function ensureProjectRoot(root, force) {
|
|
50
|
+
if (!node_fs_1.default.existsSync(root)) {
|
|
51
|
+
node_fs_1.default.mkdirSync(root, { recursive: true });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const entries = node_fs_1.default.readdirSync(root).filter((entry) => entry !== ".git");
|
|
55
|
+
if (entries.length === 0 || force) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
throw new errors_js_1.CliError(`Directory already exists and is not empty: ${root}. Re-run with --force to overwrite generated files.`);
|
|
59
|
+
}
|
|
60
|
+
async function scaffoldProject(answers, cwd = process.cwd()) {
|
|
61
|
+
const { root, folderName } = resolveProjectRoot(cwd, answers.name);
|
|
62
|
+
const project = { ...answers, name: folderName };
|
|
63
|
+
ensureProjectRoot(root, project.force);
|
|
64
|
+
writeFiles(root, (0, content_js_1.buildFileMap)(project));
|
|
65
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, "package.json"), "utf8"));
|
|
66
|
+
const hasNpmDeps = Object.keys(pkg.dependencies ?? {}).length > 0 ||
|
|
67
|
+
Object.keys(pkg.devDependencies ?? {}).length > 0;
|
|
68
|
+
if (hasNpmDeps) {
|
|
69
|
+
logger_js_1.logger.info("\nInstalling npm dependencies...");
|
|
70
|
+
run(root, "npm", ["install"]);
|
|
71
|
+
}
|
|
72
|
+
if (project.runtime === "python" && node_fs_1.default.existsSync(node_path_1.default.join(root, "requirements.txt"))) {
|
|
73
|
+
logger_js_1.logger.info("\nCreating Python venv and installing requirements...");
|
|
74
|
+
run(root, "python3", ["-m", "venv", ".venv"]);
|
|
75
|
+
const pip = node_path_1.default.join(root, ".venv", "bin", "pip");
|
|
76
|
+
run(root, pip, ["install", "-r", "requirements.txt"]);
|
|
77
|
+
}
|
|
78
|
+
if (project.database === "prisma" && project.runtime !== "python") {
|
|
79
|
+
logger_js_1.logger.info("\nGenerating Prisma client...");
|
|
80
|
+
run(root, "npx", ["prisma", "generate"], {
|
|
81
|
+
DATABASE_URL: process.env.DATABASE_URL ?? "postgresql://user:password@localhost:5432/app",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return root;
|
|
85
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InitAnswers } from "../types.js";
|
|
2
|
+
export declare function pascal(value: string): string;
|
|
3
|
+
export declare function envParameterName(key: string): string;
|
|
4
|
+
export declare function loggerExt(runtime: InitAnswers["runtime"]): "ts" | "js" | "py";
|
|
5
|
+
export declare function nodeLayerImport(moduleName: string): string;
|
|
6
|
+
export declare function dynamoTableName(appName: string): {
|
|
7
|
+
resource: string;
|
|
8
|
+
table: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pascal = pascal;
|
|
4
|
+
exports.envParameterName = envParameterName;
|
|
5
|
+
exports.loggerExt = loggerExt;
|
|
6
|
+
exports.nodeLayerImport = nodeLayerImport;
|
|
7
|
+
exports.dynamoTableName = dynamoTableName;
|
|
8
|
+
function pascal(value) {
|
|
9
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
10
|
+
}
|
|
11
|
+
// CloudFormation parameter names are alphanumeric only, so LOG_LEVEL becomes
|
|
12
|
+
// EnvLogLevel. The Env prefix keeps stage variables from ever colliding with the
|
|
13
|
+
// database parameters the templates already declare.
|
|
14
|
+
function envParameterName(key) {
|
|
15
|
+
return `Env${key
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.split(/[^a-z0-9]+/)
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.map(pascal)
|
|
20
|
+
.join("")}`;
|
|
21
|
+
}
|
|
22
|
+
function loggerExt(runtime) {
|
|
23
|
+
if (runtime === "python") {
|
|
24
|
+
return "py";
|
|
25
|
+
}
|
|
26
|
+
return runtime === "typescript" ? "ts" : "js";
|
|
27
|
+
}
|
|
28
|
+
function nodeLayerImport(moduleName) {
|
|
29
|
+
return `/opt/nodejs/${moduleName}`;
|
|
30
|
+
}
|
|
31
|
+
// "auth"/"product" keep their original table names for backward compatibility with
|
|
32
|
+
// projects already deployed against them; any app added later gets a name derived
|
|
33
|
+
// from the app itself.
|
|
34
|
+
function dynamoTableName(appName) {
|
|
35
|
+
if (appName === "auth") {
|
|
36
|
+
return { resource: "UsersTable", table: "users" };
|
|
37
|
+
}
|
|
38
|
+
if (appName === "product") {
|
|
39
|
+
return { resource: "ProductsTable", table: "products" };
|
|
40
|
+
}
|
|
41
|
+
return { resource: `${pascal(appName)}Table`, table: appName };
|
|
42
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InitAnswers, ServiceFunction } from "../types.js";
|
|
2
|
+
export declare function nodeService(answers: InitAnswers, fn: ServiceFunction, typed: boolean): string;
|
|
3
|
+
export declare function standaloneNodeService(fn: ServiceFunction, typed: boolean): string;
|
|
4
|
+
export declare function nodeHandler(answers: InitAnswers, appName: string, fn: ServiceFunction, typed: boolean): string;
|