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,89 @@
|
|
|
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.rmAction = rmAction;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
10
|
+
const is_ci_js_1 = require("../../core/is-ci.js");
|
|
11
|
+
const environments_js_1 = require("../../core/environments.js");
|
|
12
|
+
const logger_js_1 = require("../../core/logger.js");
|
|
13
|
+
const templates_js_1 = require("../env/templates.js");
|
|
14
|
+
const manifest_js_1 = require("../function/manifest.js");
|
|
15
|
+
const manifest_js_2 = require("../init/manifest.js");
|
|
16
|
+
const plan_js_1 = require("./plan.js");
|
|
17
|
+
const prompts_js_1 = require("./prompts.js");
|
|
18
|
+
const COMMAND = "slskit rm";
|
|
19
|
+
function subject(plan) {
|
|
20
|
+
if (plan.kind === "service") {
|
|
21
|
+
const count = plan.functionsRemoved.length;
|
|
22
|
+
return `application "${plan.appName}" and its ${count} function${count === 1 ? "" : "s"}`;
|
|
23
|
+
}
|
|
24
|
+
return `function "${plan.functionName}" from application "${plan.appName}"`;
|
|
25
|
+
}
|
|
26
|
+
function describe(plan) {
|
|
27
|
+
logger_js_1.logger.info(`\nAbout to remove ${subject(plan)}.`);
|
|
28
|
+
for (const target of plan.paths) {
|
|
29
|
+
logger_js_1.logger.info(` delete ${target}`);
|
|
30
|
+
}
|
|
31
|
+
for (const route of plan.routes) {
|
|
32
|
+
logger_js_1.logger.info(` drop route ${route}`);
|
|
33
|
+
}
|
|
34
|
+
logger_js_1.logger.info(" update slskit.json and every template that referenced it");
|
|
35
|
+
if (plan.cascadesToService) {
|
|
36
|
+
logger_js_1.logger.info(`\nNote: "${plan.functionName}" is the last function in "${plan.appName}", so the application goes with it — a service template with no functions is not valid CloudFormation.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function rmAction(options) {
|
|
40
|
+
const root = process.cwd();
|
|
41
|
+
const manifest = (0, manifest_js_1.readProjectManifest)(root, COMMAND);
|
|
42
|
+
const answers = (0, manifest_js_1.toInitAnswers)(manifest);
|
|
43
|
+
const apps = (0, manifest_js_1.toServiceDefs)(manifest);
|
|
44
|
+
const target = await (0, prompts_js_1.resolveRemovalTarget)(manifest, options);
|
|
45
|
+
const plan = (0, plan_js_1.planRemoval)(answers, apps, target);
|
|
46
|
+
describe(plan);
|
|
47
|
+
if (!options.yes) {
|
|
48
|
+
if (!(0, is_ci_js_1.isInteractive)()) {
|
|
49
|
+
throw new errors_js_1.CliError(`Removing code cannot be undone. Re-run with --yes to remove ${subject(plan)}.`);
|
|
50
|
+
}
|
|
51
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
52
|
+
const proceed = await confirm({
|
|
53
|
+
message: `Remove ${subject(plan)}?`,
|
|
54
|
+
default: false,
|
|
55
|
+
});
|
|
56
|
+
if (!proceed) {
|
|
57
|
+
logger_js_1.logger.info("\nNothing was removed.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const deleted = [];
|
|
62
|
+
for (const relative of plan.paths) {
|
|
63
|
+
const full = node_path_1.default.join(root, relative);
|
|
64
|
+
if (node_fs_1.default.existsSync(full)) {
|
|
65
|
+
node_fs_1.default.rmSync(full, { recursive: true, force: true });
|
|
66
|
+
deleted.push(relative);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// The manifest is rebuilt from the pruned application list rather than edited in
|
|
70
|
+
// place, so every derived section -- routes, services, directories, layer
|
|
71
|
+
// attachments, the per-service template lists -- is regenerated consistently.
|
|
72
|
+
const generatedFiles = (0, plan_js_1.pruneGeneratedFiles)(manifest.structure?.files ?? [], plan.paths);
|
|
73
|
+
(0, environments_js_1.writeManifest)(root, (0, manifest_js_2.buildProjectManifest)(answers, plan.apps, generatedFiles, {
|
|
74
|
+
version: manifest.version,
|
|
75
|
+
environments: manifest.environments,
|
|
76
|
+
}));
|
|
77
|
+
const written = (0, templates_js_1.regenerateTemplates)(root, (0, environments_js_1.readManifest)(root, COMMAND));
|
|
78
|
+
logger_js_1.logger.info(`\nRemoved ${subject(plan)}.`);
|
|
79
|
+
for (const relative of deleted) {
|
|
80
|
+
logger_js_1.logger.info(` deleted ${relative}`);
|
|
81
|
+
}
|
|
82
|
+
logger_js_1.logger.info(" updated slskit.json");
|
|
83
|
+
if (written.length > 0) {
|
|
84
|
+
logger_js_1.logger.info(` updated ${written.length} template${written.length === 1 ? "" : "s"}`);
|
|
85
|
+
}
|
|
86
|
+
const remainingFunctions = plan.apps.reduce((count, app) => count + app.functions.length, 0);
|
|
87
|
+
logger_js_1.logger.info(`\n${plan.apps.length} application${plan.apps.length === 1 ? "" : "s"} and ${remainingFunctions} function${remainingFunctions === 1 ? "" : "s"} left.`);
|
|
88
|
+
logger_js_1.logger.info('Deployed resources are not touched — run "slskit deploy <environment> --all" to remove them from AWS.');
|
|
89
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerRmCommand = registerRmCommand;
|
|
4
|
+
const run_js_1 = require("../../core/run.js");
|
|
5
|
+
function registerRmCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("rm")
|
|
8
|
+
.alias("remove")
|
|
9
|
+
.description("Remove a function or an application, and everything that referenced it")
|
|
10
|
+
.argument("[name]", "Function or application name")
|
|
11
|
+
.option("--function <name>", "Remove this function")
|
|
12
|
+
.option("--service <name>", "Remove this application and every function in it")
|
|
13
|
+
.option("--app <name>", "Same as --service")
|
|
14
|
+
.option("-y, --yes", "Skip the confirmation prompt", false)
|
|
15
|
+
.action((name, options) => (0, run_js_1.runCommand)(async () => {
|
|
16
|
+
const { rmAction } = await import("./action.js");
|
|
17
|
+
await rmAction({
|
|
18
|
+
name: name ?? options.name,
|
|
19
|
+
function: options.function,
|
|
20
|
+
service: options.service,
|
|
21
|
+
app: options.app,
|
|
22
|
+
yes: options.yes,
|
|
23
|
+
});
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { InitAnswers, ServiceDef } from "../init/types.js";
|
|
2
|
+
export type RemovalKind = "function" | "service";
|
|
3
|
+
export interface RemovalTarget {
|
|
4
|
+
kind: RemovalKind;
|
|
5
|
+
appName: string;
|
|
6
|
+
functionName?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RemovalPlan {
|
|
9
|
+
kind: RemovalKind;
|
|
10
|
+
appName: string;
|
|
11
|
+
functionName?: string;
|
|
12
|
+
apps: ServiceDef[];
|
|
13
|
+
paths: string[];
|
|
14
|
+
functionsRemoved: string[];
|
|
15
|
+
routes: string[];
|
|
16
|
+
cascadesToService: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function planRemoval(answers: InitAnswers, apps: ServiceDef[], target: RemovalTarget): RemovalPlan;
|
|
19
|
+
export declare function pruneGeneratedFiles(files: string[], removed: string[]): string[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planRemoval = planRemoval;
|
|
4
|
+
exports.pruneGeneratedFiles = pruneGeneratedFiles;
|
|
5
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
6
|
+
const types_js_1 = require("../init/types.js");
|
|
7
|
+
function functionPaths(answers, appName, fn) {
|
|
8
|
+
return [
|
|
9
|
+
`${types_js_1.SRC_DIR}/functions/${appName}/${fn.name}`,
|
|
10
|
+
`${types_js_1.SRC_DIR}/services/${appName}/${fn.name}.${(0, types_js_1.sourceExt)(fn.runtime ?? answers.runtime)}`,
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
function servicePaths(appName) {
|
|
14
|
+
return [
|
|
15
|
+
`${types_js_1.SRC_DIR}/functions/${appName}`,
|
|
16
|
+
`${types_js_1.SRC_DIR}/services/${appName}`,
|
|
17
|
+
(0, types_js_1.serviceTemplatePath)(appName),
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
function routeLabel(fn) {
|
|
21
|
+
return `${fn.method} ${fn.httpPath}`;
|
|
22
|
+
}
|
|
23
|
+
function totalFunctions(apps) {
|
|
24
|
+
return apps.reduce((count, app) => count + app.functions.length, 0);
|
|
25
|
+
}
|
|
26
|
+
// Works out everything a removal touches without writing anything, so the command
|
|
27
|
+
// can describe the change before it is confirmed -- and so the whole decision is
|
|
28
|
+
// testable without a filesystem.
|
|
29
|
+
function planRemoval(answers, apps, target) {
|
|
30
|
+
const app = apps.find((each) => each.name === target.appName);
|
|
31
|
+
if (!app) {
|
|
32
|
+
const known = apps.map((each) => each.name).join(", ") || "(none)";
|
|
33
|
+
throw new errors_js_1.CliError(`Application "${target.appName}" was not found. Existing: ${known}.`);
|
|
34
|
+
}
|
|
35
|
+
if (target.kind === "service") {
|
|
36
|
+
const remaining = apps.filter((each) => each.name !== app.name);
|
|
37
|
+
if (totalFunctions(remaining) === 0) {
|
|
38
|
+
throw new errors_js_1.CliError(`Removing "${app.name}" would leave the project with no functions, and a stack with no resources cannot be deployed. Add another function first, or start over with "slskit init".`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
kind: "service",
|
|
42
|
+
appName: app.name,
|
|
43
|
+
apps: remaining,
|
|
44
|
+
paths: servicePaths(app.name),
|
|
45
|
+
functionsRemoved: app.functions.map((fn) => fn.name),
|
|
46
|
+
routes: app.functions.map(routeLabel),
|
|
47
|
+
cascadesToService: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const fn = app.functions.find((each) => each.name === target.functionName);
|
|
51
|
+
if (!fn) {
|
|
52
|
+
const known = app.functions.map((each) => each.name).join(", ") || "(none)";
|
|
53
|
+
throw new errors_js_1.CliError(`Function "${target.functionName}" was not found in application "${app.name}". Existing: ${known}.`);
|
|
54
|
+
}
|
|
55
|
+
const keptFunctions = app.functions.filter((each) => each.name !== fn.name);
|
|
56
|
+
const cascadesToService = keptFunctions.length === 0;
|
|
57
|
+
const remaining = cascadesToService
|
|
58
|
+
? apps.filter((each) => each.name !== app.name)
|
|
59
|
+
: apps.map((each) => each.name === app.name ? { ...each, functions: keptFunctions } : each);
|
|
60
|
+
if (totalFunctions(remaining) === 0) {
|
|
61
|
+
throw new errors_js_1.CliError(`"${fn.name}" is the only function in this project, and a stack with no resources cannot be deployed. Add another function first, or start over with "slskit init".`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
kind: "function",
|
|
65
|
+
appName: app.name,
|
|
66
|
+
functionName: fn.name,
|
|
67
|
+
apps: remaining,
|
|
68
|
+
paths: cascadesToService ? servicePaths(app.name) : functionPaths(answers, app.name, fn),
|
|
69
|
+
functionsRemoved: [fn.name],
|
|
70
|
+
routes: [routeLabel(fn)],
|
|
71
|
+
cascadesToService,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Drops every generated-file entry that the removal deletes, including anything
|
|
75
|
+
// nested under a removed directory (a Python function's __init__.py, say).
|
|
76
|
+
function pruneGeneratedFiles(files, removed) {
|
|
77
|
+
return files.filter((file) => !removed.some((path) => file === path || file.startsWith(`${path}/`)));
|
|
78
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProjectManifest } from "../function/types.js";
|
|
2
|
+
import type { RemovalTarget } from "./plan.js";
|
|
3
|
+
import type { RmOptions } from "./types.js";
|
|
4
|
+
export declare function resolveRemovalTarget(manifest: ProjectManifest, options: RmOptions): Promise<RemovalTarget>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRemovalTarget = resolveRemovalTarget;
|
|
4
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
5
|
+
// Function names are unique across the whole project, so a name is enough to find
|
|
6
|
+
// one -- the same property "slskit function" enforces when it creates them.
|
|
7
|
+
function findFunction(manifest, name) {
|
|
8
|
+
const wanted = name.trim().toLowerCase();
|
|
9
|
+
for (const app of manifest.applications) {
|
|
10
|
+
for (const fn of app.functions) {
|
|
11
|
+
if (fn.name.toLowerCase() === wanted) {
|
|
12
|
+
return { app: app.name, fn: fn.name };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function findService(manifest, name) {
|
|
19
|
+
const wanted = name.trim().toLowerCase();
|
|
20
|
+
return manifest.applications.find((app) => app.name.toLowerCase() === wanted)?.name;
|
|
21
|
+
}
|
|
22
|
+
function serviceNames(manifest) {
|
|
23
|
+
return manifest.applications.map((app) => app.name).join(", ") || "(none)";
|
|
24
|
+
}
|
|
25
|
+
function functionNames(manifest) {
|
|
26
|
+
return (manifest.applications.flatMap((app) => app.functions.map((fn) => fn.name)).join(", ") ||
|
|
27
|
+
"(none)");
|
|
28
|
+
}
|
|
29
|
+
function targetForFunction(manifest, name) {
|
|
30
|
+
const found = findFunction(manifest, name);
|
|
31
|
+
if (!found) {
|
|
32
|
+
throw new errors_js_1.CliError(`Function "${name}" was not found. Existing functions: ${functionNames(manifest)}.`);
|
|
33
|
+
}
|
|
34
|
+
return { kind: "function", appName: found.app, functionName: found.fn };
|
|
35
|
+
}
|
|
36
|
+
function targetForService(manifest, name) {
|
|
37
|
+
const found = findService(manifest, name);
|
|
38
|
+
if (!found) {
|
|
39
|
+
throw new errors_js_1.CliError(`Application "${name}" was not found. Existing applications: ${serviceNames(manifest)}.`);
|
|
40
|
+
}
|
|
41
|
+
return { kind: "service", appName: found };
|
|
42
|
+
}
|
|
43
|
+
// A bare name is resolved by looking it up. It is only ambiguous when a function and
|
|
44
|
+
// an application share a name, and then the flag settles it rather than a guess.
|
|
45
|
+
function targetForName(manifest, name) {
|
|
46
|
+
const asFunction = findFunction(manifest, name);
|
|
47
|
+
const asService = findService(manifest, name);
|
|
48
|
+
if (asFunction && asService) {
|
|
49
|
+
throw new errors_js_1.CliError(`"${name}" is both a function and an application. Pass --function ${name} or --service ${name} to say which.`);
|
|
50
|
+
}
|
|
51
|
+
if (asFunction) {
|
|
52
|
+
return { kind: "function", appName: asFunction.app, functionName: asFunction.fn };
|
|
53
|
+
}
|
|
54
|
+
if (asService) {
|
|
55
|
+
return { kind: "service", appName: asService };
|
|
56
|
+
}
|
|
57
|
+
throw new errors_js_1.CliError(`Nothing named "${name}" in this project.\n functions: ${functionNames(manifest)}\n applications: ${serviceNames(manifest)}`);
|
|
58
|
+
}
|
|
59
|
+
async function resolveRemovalTarget(manifest, options) {
|
|
60
|
+
const service = options.service ?? options.app;
|
|
61
|
+
if (options.function && service) {
|
|
62
|
+
throw new errors_js_1.CliError('Pass either "--function" or "--service", not both.');
|
|
63
|
+
}
|
|
64
|
+
if (options.function) {
|
|
65
|
+
return targetForFunction(manifest, options.function);
|
|
66
|
+
}
|
|
67
|
+
if (service) {
|
|
68
|
+
return targetForService(manifest, service);
|
|
69
|
+
}
|
|
70
|
+
if (options.name) {
|
|
71
|
+
return targetForName(manifest, options.name);
|
|
72
|
+
}
|
|
73
|
+
if (!process.stdin.isTTY) {
|
|
74
|
+
throw new errors_js_1.CliError('Non-interactive mode needs a name, "--function <name>" or "--service <name>".');
|
|
75
|
+
}
|
|
76
|
+
const { select } = await import("@inquirer/prompts");
|
|
77
|
+
const kind = await select({
|
|
78
|
+
message: "What do you want to remove?",
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: "A function", value: "function" },
|
|
81
|
+
{ name: "An application — and every function in it", value: "service" },
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
if (manifest.applications.length === 0) {
|
|
85
|
+
throw new errors_js_1.CliError("This project has no applications to remove.");
|
|
86
|
+
}
|
|
87
|
+
if (kind === "service") {
|
|
88
|
+
const appName = await select({
|
|
89
|
+
message: "Application:",
|
|
90
|
+
choices: manifest.applications.map((app) => ({
|
|
91
|
+
name: `${app.name} (${app.functions.length} function${app.functions.length === 1 ? "" : "s"})`,
|
|
92
|
+
value: app.name,
|
|
93
|
+
})),
|
|
94
|
+
});
|
|
95
|
+
return { kind: "service", appName };
|
|
96
|
+
}
|
|
97
|
+
const choices = manifest.applications.flatMap((app) => app.functions.map((fn) => ({
|
|
98
|
+
name: `${app.name}/${fn.name}${fn.apiGateway.enabled ? ` ${fn.apiGateway.method ?? "GET"} ${fn.apiGateway.path ?? ""}` : ""}`,
|
|
99
|
+
value: `${app.name}.${fn.name}`,
|
|
100
|
+
})));
|
|
101
|
+
if (choices.length === 0) {
|
|
102
|
+
throw new errors_js_1.CliError("This project has no functions to remove.");
|
|
103
|
+
}
|
|
104
|
+
const picked = await select({ message: "Function:", choices });
|
|
105
|
+
const [appName, functionName] = picked.split(".");
|
|
106
|
+
return { kind: "function", appName, functionName };
|
|
107
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAction = runAction;
|
|
4
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
5
|
+
const logger_js_1 = require("../../core/logger.js");
|
|
6
|
+
const environments_js_1 = require("../../core/environments.js");
|
|
7
|
+
const parameters_js_1 = require("../env/parameters.js");
|
|
8
|
+
const templates_js_1 = require("../env/templates.js");
|
|
9
|
+
const manifest_js_1 = require("./manifest.js");
|
|
10
|
+
const sam_cli_js_1 = require("./sam-cli.js");
|
|
11
|
+
const watch_js_1 = require("./watch.js");
|
|
12
|
+
const local_template_js_1 = require("./local-template.js");
|
|
13
|
+
const scope_js_1 = require("../../core/scope.js");
|
|
14
|
+
const DEFAULT_PORT = 3000;
|
|
15
|
+
function parsePort(value) {
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
return DEFAULT_PORT;
|
|
18
|
+
}
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
21
|
+
throw new errors_js_1.CliError(`Invalid port "${value}".`);
|
|
22
|
+
}
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
function applicationsOf(manifest) {
|
|
26
|
+
return manifest.applications ?? [];
|
|
27
|
+
}
|
|
28
|
+
async function resolveRunScope(manifest, options) {
|
|
29
|
+
const fromFlags = (0, scope_js_1.scopeFromFlags)(manifest, options);
|
|
30
|
+
if (fromFlags) {
|
|
31
|
+
return fromFlags;
|
|
32
|
+
}
|
|
33
|
+
if (!process.stdin.isTTY) {
|
|
34
|
+
return scope_js_1.allScope;
|
|
35
|
+
}
|
|
36
|
+
return (0, scope_js_1.promptForScope)(manifest, "run", "Everything — every service");
|
|
37
|
+
}
|
|
38
|
+
function syncAndResolve(cwd, environment) {
|
|
39
|
+
const project = (0, environments_js_1.readManifest)(cwd, "slskit run");
|
|
40
|
+
// A variable can appear just by being typed into a .env file, so the templates are
|
|
41
|
+
// brought back in step before sam reads them.
|
|
42
|
+
const rewritten = (0, templates_js_1.syncTemplates)(cwd, project);
|
|
43
|
+
if (rewritten.length > 0) {
|
|
44
|
+
logger_js_1.logger.info(`Updated ${rewritten.length} template(s) for the current variables.`);
|
|
45
|
+
}
|
|
46
|
+
return (0, parameters_js_1.toCliArguments)((0, parameters_js_1.parameterOverrides)(cwd, project, environment));
|
|
47
|
+
}
|
|
48
|
+
function exited(child) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function runAction(options) {
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
const manifest = (0, manifest_js_1.readManifest)(cwd);
|
|
56
|
+
const project = (0, environments_js_1.readManifest)(cwd, "slskit run");
|
|
57
|
+
const environment = (0, environments_js_1.resolveEnvironmentName)(project, options.env);
|
|
58
|
+
const port = parsePort(options.port);
|
|
59
|
+
// Resolved before sam runs so a missing secret fails fast rather than after a build.
|
|
60
|
+
let overrides = syncAndResolve(cwd, environment);
|
|
61
|
+
const scope = await resolveRunScope(project, options);
|
|
62
|
+
// Two reasons to serve a flattened copy rather than the real templates: a shared API
|
|
63
|
+
// Gateway lives in the root stack, which sam local cannot resolve from a nested one;
|
|
64
|
+
// and running part of the project means serving a template with only those functions
|
|
65
|
+
// in it, so sam builds only those.
|
|
66
|
+
const flattened = (0, local_template_js_1.needsLocalTemplate)(project) || scope.kind !== "all";
|
|
67
|
+
const buildTarget = flattened
|
|
68
|
+
? { template: (0, local_template_js_1.writeLocalTemplate)(cwd, project, scope), buildDir: local_template_js_1.LOCAL_BUILD_DIR }
|
|
69
|
+
: {};
|
|
70
|
+
const servedTemplate = flattened ? (0, local_template_js_1.localBuiltTemplate)() : undefined;
|
|
71
|
+
await (0, sam_cli_js_1.ensureSamCliInstalled)();
|
|
72
|
+
if (options.build !== false) {
|
|
73
|
+
(0, sam_cli_js_1.samBuild)(cwd, buildTarget);
|
|
74
|
+
}
|
|
75
|
+
logger_js_1.logger.info(`\nRunning ${scope.label} of "${manifest.name}" locally on a single API Gateway port.`);
|
|
76
|
+
logger_js_1.logger.info(` ${environments_js_1.APP_ENVIRONMENT_KEY}=${environment}`);
|
|
77
|
+
logger_js_1.logger.info(`Every application's routes are served from one local API at http://127.0.0.1:${port}`);
|
|
78
|
+
const watching = options.watch !== false;
|
|
79
|
+
if (watching) {
|
|
80
|
+
logger_js_1.logger.info("Watching src/ — save a file and only that function is rebuilt.");
|
|
81
|
+
}
|
|
82
|
+
logger_js_1.logger.info("Press Ctrl+C to stop.\n");
|
|
83
|
+
let child = (0, sam_cli_js_1.startLocalApi)(cwd, port, overrides, servedTemplate);
|
|
84
|
+
let stopping = false;
|
|
85
|
+
let restarting = false;
|
|
86
|
+
// Anything a save triggers has to survive a broken file: a rebuild that fails
|
|
87
|
+
// leaves the previous build serving and waits for the next save.
|
|
88
|
+
const rebuildFunctions = (resourceIds, labels) => {
|
|
89
|
+
logger_js_1.logger.info(`\n> ${(0, watch_js_1.describeChange)(labels)} changed — rebuilding`);
|
|
90
|
+
for (const resourceId of resourceIds) {
|
|
91
|
+
if (!(0, sam_cli_js_1.samBuildResource)(cwd, resourceId, buildTarget)) {
|
|
92
|
+
logger_js_1.logger.error(`Build failed for ${resourceId}. Fix it and save again.`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
logger_js_1.logger.info("Rebuilt. The next request uses the new code.\n");
|
|
97
|
+
};
|
|
98
|
+
const rebuildAll = (labels) => {
|
|
99
|
+
logger_js_1.logger.info(`\n> ${(0, watch_js_1.describeChange)(labels)} changed — rebuilding everything`);
|
|
100
|
+
if (!(0, sam_cli_js_1.samRebuild)(cwd, buildTarget)) {
|
|
101
|
+
logger_js_1.logger.error("Build failed. Fix it and save again.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
logger_js_1.logger.info("Rebuilt. The next request uses the new code.\n");
|
|
105
|
+
};
|
|
106
|
+
// Routes and parameter values are only read when sam starts, so these changes
|
|
107
|
+
// cannot be picked up by swapping an artifact.
|
|
108
|
+
const restart = (labels) => {
|
|
109
|
+
logger_js_1.logger.info(`\n> ${(0, watch_js_1.describeChange)(labels)} changed — restarting the local API`);
|
|
110
|
+
let next;
|
|
111
|
+
try {
|
|
112
|
+
next = syncAndResolve(cwd, environment);
|
|
113
|
+
if (flattened) {
|
|
114
|
+
(0, local_template_js_1.writeLocalTemplate)(cwd, (0, environments_js_1.readManifest)(cwd, "slskit run"), scope);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger_js_1.logger.error(error instanceof Error ? error.message : String(error));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!(0, sam_cli_js_1.samRebuild)(cwd, buildTarget)) {
|
|
122
|
+
logger_js_1.logger.error("Build failed. Fix it and save again.");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
overrides = next;
|
|
126
|
+
restarting = true;
|
|
127
|
+
child.kill("SIGINT");
|
|
128
|
+
};
|
|
129
|
+
const watcher = watching
|
|
130
|
+
? (0, watch_js_1.watchProject)(cwd, applicationsOf(project).filter((app) => scope.kind === "all" || app.name === scope.service), {
|
|
131
|
+
onFunctions: rebuildFunctions,
|
|
132
|
+
onFull: rebuildAll,
|
|
133
|
+
onRestart: restart,
|
|
134
|
+
}, flattened)
|
|
135
|
+
: undefined;
|
|
136
|
+
const stop = () => {
|
|
137
|
+
if (stopping) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
stopping = true;
|
|
141
|
+
watcher?.close();
|
|
142
|
+
child.kill("SIGINT");
|
|
143
|
+
};
|
|
144
|
+
process.on("SIGINT", stop);
|
|
145
|
+
process.on("SIGTERM", stop);
|
|
146
|
+
try {
|
|
147
|
+
for (;;) {
|
|
148
|
+
const { code, signal } = await exited(child);
|
|
149
|
+
if (stopping) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (restarting) {
|
|
153
|
+
restarting = false;
|
|
154
|
+
child = (0, sam_cli_js_1.startLocalApi)(cwd, port, overrides, servedTemplate);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (signal) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (code !== 0) {
|
|
161
|
+
throw new errors_js_1.CliError(`sam local start-api exited with code ${code ?? 1}`);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
watcher?.close();
|
|
168
|
+
process.removeListener("SIGINT", stop);
|
|
169
|
+
process.removeListener("SIGTERM", stop);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerRunCommand = registerRunCommand;
|
|
4
|
+
const run_js_1 = require("../../core/run.js");
|
|
5
|
+
function registerRunCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("run")
|
|
8
|
+
.description("Run the generated SAM project locally (every application served from one API Gateway port)")
|
|
9
|
+
.argument("[environment]", "Environment to run with (example: dev, staging) — defaults to the default environment")
|
|
10
|
+
.option("-p, --port <port>", "Local API Gateway port", "3000")
|
|
11
|
+
.option("-s, --service <name>", "Run every function in one service")
|
|
12
|
+
.option("--function <name>", "Run one function on its own")
|
|
13
|
+
.option("--all", "Run everything without being asked what to run", false)
|
|
14
|
+
.option("--no-build", 'Skip "sam build" before starting the local API')
|
|
15
|
+
.option("--no-watch", "Do not rebuild on file changes (stop and re-run to pick up edits)")
|
|
16
|
+
.option("-e, --env <name>", "Environment whose variables to run with (default: the default environment)")
|
|
17
|
+
.action((environment, options) => (0, run_js_1.runCommand)(async () => {
|
|
18
|
+
const { runAction } = await import("./action.js");
|
|
19
|
+
await runAction({ ...options, env: environment ?? options.env });
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProjectManifest } from "../../core/environments.js";
|
|
2
|
+
import type { Scope } from "../../core/scope.js";
|
|
3
|
+
export declare const LOCAL_TEMPLATE_FILE = ".slskit-local.yaml";
|
|
4
|
+
export declare const LOCAL_BUILD_DIR: string;
|
|
5
|
+
export declare function needsLocalTemplate(manifest: ProjectManifest): boolean;
|
|
6
|
+
export declare function writeLocalTemplate(cwd: string, manifest: ProjectManifest, scope?: Scope): string;
|
|
7
|
+
export declare function localBuiltTemplate(): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
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.LOCAL_BUILD_DIR = exports.LOCAL_TEMPLATE_FILE = void 0;
|
|
7
|
+
exports.needsLocalTemplate = needsLocalTemplate;
|
|
8
|
+
exports.writeLocalTemplate = writeLocalTemplate;
|
|
9
|
+
exports.localBuiltTemplate = localBuiltTemplate;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const keys_js_1 = require("../env/keys.js");
|
|
13
|
+
const manifest_js_1 = require("../function/manifest.js");
|
|
14
|
+
const sam_js_1 = require("../init/templates/sam.js");
|
|
15
|
+
const scope_js_1 = require("../../core/scope.js");
|
|
16
|
+
// Written at the project root so the flat template's "CodeUri: ./" resolves the same
|
|
17
|
+
// way the deployed templates' paths do.
|
|
18
|
+
exports.LOCAL_TEMPLATE_FILE = ".slskit-local.yaml";
|
|
19
|
+
exports.LOCAL_BUILD_DIR = node_path_1.default.join(".aws-sam", "local");
|
|
20
|
+
// A shared API Gateway lives in the root stack, and SAM local cannot resolve an API
|
|
21
|
+
// that a nested stack only holds an id for -- every route comes back 502. So local
|
|
22
|
+
// runs get a flattened copy of the same project instead. It is generated from the
|
|
23
|
+
// manifest on every run, so it can never drift from what gets deployed.
|
|
24
|
+
function needsLocalTemplate(manifest) {
|
|
25
|
+
const api = manifest.apiGateway;
|
|
26
|
+
return Boolean(api?.enabled) && api?.perService === false;
|
|
27
|
+
}
|
|
28
|
+
// Narrowing the template to one service or one function is what makes a scoped run
|
|
29
|
+
// fast: sam only builds the functions the template still contains.
|
|
30
|
+
function narrow(apps, scope) {
|
|
31
|
+
if (scope.kind === "all") {
|
|
32
|
+
return apps;
|
|
33
|
+
}
|
|
34
|
+
return apps
|
|
35
|
+
.filter((app) => app.name === scope.service)
|
|
36
|
+
.map((app) => ({
|
|
37
|
+
...app,
|
|
38
|
+
functions: app.functions.filter((fn) => scope.kind !== "function" || fn.name === scope.functionName),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
function writeLocalTemplate(cwd, manifest, scope = scope_js_1.allScope) {
|
|
42
|
+
const functionView = manifest;
|
|
43
|
+
const answers = (0, manifest_js_1.toInitAnswers)(functionView);
|
|
44
|
+
const apps = narrow((0, manifest_js_1.toServiceDefs)(functionView), scope);
|
|
45
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(cwd, exports.LOCAL_TEMPLATE_FILE), (0, sam_js_1.samFlatTemplate)(answers, apps, (0, keys_js_1.projectEnvKeys)(cwd, manifest)));
|
|
46
|
+
return exports.LOCAL_TEMPLATE_FILE;
|
|
47
|
+
}
|
|
48
|
+
// The built template sam local serves, rather than the source one.
|
|
49
|
+
function localBuiltTemplate() {
|
|
50
|
+
return node_path_1.default.join(exports.LOCAL_BUILD_DIR, "template.yaml");
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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.readManifest = readManifest;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
9
|
+
const environments_js_1 = require("../../core/environments.js");
|
|
10
|
+
function readManifest(cwd) {
|
|
11
|
+
const manifestPath = (0, environments_js_1.manifestPathFor)(cwd);
|
|
12
|
+
if (!manifestPath) {
|
|
13
|
+
throw new errors_js_1.CliError(`No slskit.json found in ${cwd}. Run "slskit init" first, or run "slskit run" from your project root.`);
|
|
14
|
+
}
|
|
15
|
+
const manifest = JSON.parse(node_fs_1.default.readFileSync(manifestPath, "utf8"));
|
|
16
|
+
if (manifest.framework?.id !== "sam") {
|
|
17
|
+
throw new errors_js_1.CliError(`"slskit run" supports AWS SAM projects only (this project uses "${manifest.framework?.id ?? "unknown"}").`);
|
|
18
|
+
}
|
|
19
|
+
return manifest;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ChangeKind = "function" | "full" | "restart";
|
|
2
|
+
export interface Change {
|
|
3
|
+
kind: ChangeKind;
|
|
4
|
+
/** Set only for "function": the resource id sam build takes, e.g. AuthStack/LoginFunction. */
|
|
5
|
+
resourceId?: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ApplicationView {
|
|
9
|
+
name: string;
|
|
10
|
+
functions?: {
|
|
11
|
+
name: string;
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
export declare function resourceIdFor(appName: string, functionName: string, sharedApi?: boolean): string;
|
|
15
|
+
export declare function classifyChange(applications: ApplicationView[], relativePath: string, sharedApi?: boolean): Change;
|
|
16
|
+
export declare function planRebuild(changes: Change[]): {
|
|
17
|
+
kind: ChangeKind;
|
|
18
|
+
resourceIds: string[];
|
|
19
|
+
labels: string[];
|
|
20
|
+
};
|