local-serverless-stack 0.17.1 → 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 +327 -0
- package/README.md +1157 -224
- package/assets/icon.svg +37 -0
- package/assets/logo.svg +38 -0
- package/bin/cli.js +384 -217
- package/bin/i18n.js +498 -0
- package/dist/client/config-discovery.d.ts.map +1 -1
- package/dist/client/config-discovery.js +2 -1
- package/dist/client/config-discovery.js.map +1 -1
- package/dist/client/http.d.ts +7 -0
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +3 -2
- package/dist/client/http.js.map +1 -1
- package/dist/client/lifecycle.d.ts +1 -4
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +2 -8
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/namespaces/health.d.ts +1 -1
- package/dist/client/namespaces/health.d.ts.map +1 -1
- package/dist/client/namespaces/queues.d.ts.map +1 -1
- package/dist/client/namespaces/queues.js +9 -0
- package/dist/client/namespaces/queues.js.map +1 -1
- package/dist/client/namespaces/seeds.d.ts +1 -1
- package/dist/client/namespaces/services.d.ts +34 -0
- package/dist/client/namespaces/services.d.ts.map +1 -1
- package/dist/client/namespaces/services.js +3 -0
- package/dist/client/namespaces/services.js.map +1 -1
- package/dist/client/types.d.ts +17 -5
- package/dist/client/types.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +15 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +86 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/protocol.d.ts +36 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +96 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +41 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +18 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +323 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/server/dev/dynamo-proxy.d.ts.map +1 -1
- package/dist/server/dev/dynamo-proxy.js +16 -2
- package/dist/server/dev/dynamo-proxy.js.map +1 -1
- package/dist/server/engine/backends/self-backend.d.ts +11 -2
- package/dist/server/engine/backends/self-backend.d.ts.map +1 -1
- package/dist/server/engine/backends/self-backend.js +42 -1
- package/dist/server/engine/backends/self-backend.js.map +1 -1
- package/dist/server/engine/dispatch/dispatcher.js +2 -2
- package/dist/server/engine/dispatch/dispatcher.js.map +1 -1
- package/dist/server/engine/engine-manager.d.ts +6 -1
- package/dist/server/engine/engine-manager.d.ts.map +1 -1
- package/dist/server/engine/engine-manager.js +30 -27
- package/dist/server/engine/engine-manager.js.map +1 -1
- package/dist/server/engine/http/is-aws-request.d.ts +8 -0
- package/dist/server/engine/http/is-aws-request.d.ts.map +1 -0
- package/dist/server/engine/http/is-aws-request.js +53 -0
- package/dist/server/engine/http/is-aws-request.js.map +1 -0
- package/dist/server/engine/http/router.d.ts.map +1 -1
- package/dist/server/engine/http/router.js +7 -1
- package/dist/server/engine/http/router.js.map +1 -1
- package/dist/server/engine/store/engine-store.d.ts +1 -0
- package/dist/server/engine/store/engine-store.d.ts.map +1 -1
- package/dist/server/engine/store/engine-store.js +2 -1
- package/dist/server/engine/store/engine-store.js.map +1 -1
- package/dist/server/engine/store/memory-store.d.ts +16 -0
- package/dist/server/engine/store/memory-store.d.ts.map +1 -0
- package/dist/server/engine/store/memory-store.js +167 -0
- package/dist/server/engine/store/memory-store.js.map +1 -0
- package/dist/server/engine/store/wal.d.ts +3 -0
- package/dist/server/engine/store/wal.d.ts.map +1 -1
- package/dist/server/engine/store/wal.js +35 -1
- package/dist/server/engine/store/wal.js.map +1 -1
- package/dist/server/index.js +159 -44
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/config.d.ts.map +1 -1
- package/dist/server/routes/config.js +164 -15
- package/dist/server/routes/config.js.map +1 -1
- package/dist/server/routes/lambdas.d.ts.map +1 -1
- package/dist/server/routes/lambdas.js +65 -0
- package/dist/server/routes/lambdas.js.map +1 -1
- package/dist/server/routes/services.d.ts.map +1 -1
- package/dist/server/routes/services.js +275 -24
- package/dist/server/routes/services.js.map +1 -1
- package/dist/server/services/bind-host.d.ts +30 -0
- package/dist/server/services/bind-host.d.ts.map +1 -0
- package/dist/server/services/bind-host.js +182 -0
- package/dist/server/services/bind-host.js.map +1 -0
- package/dist/server/services/cache-manager.d.ts +2 -1
- package/dist/server/services/cache-manager.d.ts.map +1 -1
- package/dist/server/services/cache-manager.js +4 -14
- package/dist/server/services/cache-manager.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +59 -35
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +787 -131
- package/dist/server/services/config-manager.js.map +1 -1
- package/dist/server/services/dynamo-explorer.d.ts +1 -0
- package/dist/server/services/dynamo-explorer.d.ts.map +1 -1
- package/dist/server/services/dynamo-explorer.js +28 -6
- package/dist/server/services/dynamo-explorer.js.map +1 -1
- package/dist/server/services/explorer-region.d.ts +2 -0
- package/dist/server/services/explorer-region.d.ts.map +1 -0
- package/dist/server/services/explorer-region.js +23 -0
- package/dist/server/services/explorer-region.js.map +1 -0
- package/dist/server/services/gateway-manager.d.ts.map +1 -1
- package/dist/server/services/gateway-manager.js +18 -5
- package/dist/server/services/gateway-manager.js.map +1 -1
- package/dist/server/services/invocation-activity.d.ts +45 -0
- package/dist/server/services/invocation-activity.d.ts.map +1 -0
- package/dist/server/services/invocation-activity.js +102 -0
- package/dist/server/services/invocation-activity.js.map +1 -0
- package/dist/server/services/lambda-runtime-manager.d.ts +7 -1
- package/dist/server/services/lambda-runtime-manager.d.ts.map +1 -1
- package/dist/server/services/lambda-runtime-manager.js +118 -10
- package/dist/server/services/lambda-runtime-manager.js.map +1 -1
- package/dist/server/services/opensearch-explorer.d.ts.map +1 -1
- package/dist/server/services/opensearch-explorer.js +14 -10
- package/dist/server/services/opensearch-explorer.js.map +1 -1
- package/dist/server/services/project-scope.d.ts +2 -0
- package/dist/server/services/project-scope.d.ts.map +1 -0
- package/dist/server/services/project-scope.js +23 -0
- package/dist/server/services/project-scope.js.map +1 -0
- package/dist/server/services/queue-inspector.d.ts.map +1 -1
- package/dist/server/services/queue-inspector.js +15 -6
- package/dist/server/services/queue-inspector.js.map +1 -1
- package/dist/server/services/resource-provisioner.d.ts +0 -2
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +24 -14
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/s3-explorer.js +3 -3
- package/dist/server/services/s3-explorer.js.map +1 -1
- package/dist/server/services/secrets-explorer.d.ts.map +1 -1
- package/dist/server/services/secrets-explorer.js +10 -3
- package/dist/server/services/secrets-explorer.js.map +1 -1
- package/dist/server/services/seed-manager.d.ts.map +1 -1
- package/dist/server/services/seed-manager.js +17 -8
- package/dist/server/services/seed-manager.js.map +1 -1
- package/dist/server/services/serverless-state-parser.d.ts +10 -0
- package/dist/server/services/serverless-state-parser.d.ts.map +1 -1
- package/dist/server/services/serverless-state-parser.js +20 -1
- package/dist/server/services/serverless-state-parser.js.map +1 -1
- package/dist/server/services/service-registrar.d.ts.map +1 -1
- package/dist/server/services/service-registrar.js +19 -5
- package/dist/server/services/service-registrar.js.map +1 -1
- package/dist/server/services/service-scanner.d.ts +20 -0
- package/dist/server/services/service-scanner.d.ts.map +1 -0
- package/dist/server/services/service-scanner.js +173 -0
- package/dist/server/services/service-scanner.js.map +1 -0
- package/dist/server/services/source-watcher.d.ts.map +1 -1
- package/dist/server/services/source-watcher.js +31 -6
- package/dist/server/services/source-watcher.js.map +1 -1
- package/dist/ui/assets/ApisPage-BOuimwWz.js +1 -0
- package/dist/ui/assets/BucketDetailPage-vLYwhoQt.js +1 -0
- package/dist/ui/assets/BucketsPage-Cu5lU1Hp.js +1 -0
- package/dist/ui/assets/DynamoPage-y3TTEPUL.js +1 -0
- package/dist/ui/assets/DynamoTablePage-BnmSRZjc.js +3 -0
- package/dist/ui/assets/LambdaDetailPage-B5q_d27t.js +4 -0
- package/dist/ui/assets/LambdasPage-CJpRoEDx.js +1 -0
- package/dist/ui/assets/OnboardingPage-D02dymQi.js +1 -0
- package/dist/ui/assets/OpenSearchCollectionPage-B_wWTlfL.js +1 -0
- package/dist/ui/assets/OpenSearchPage-BLEgfF_g.js +1 -0
- package/dist/ui/assets/{OverviewPage-C7JlCyW1.css → OverviewPage-C1pBJ8aP.css} +1 -1
- package/dist/ui/assets/OverviewPage-EdJWiAt6.js +1 -0
- package/dist/ui/assets/QueueDetailPage-ByEYHvHe.js +1 -0
- package/dist/ui/assets/QueuesPage-CvwYHSll.js +1 -0
- package/dist/ui/assets/SecretsPage-DASw8Ntf.js +1 -0
- package/dist/ui/assets/ServiceDetailPage-BMsUDg6L.js +2 -0
- package/dist/ui/assets/ServicesPage-2JuiIiRr.js +2 -0
- package/dist/ui/assets/SettingsPage-DStO4bgN.js +1 -0
- package/dist/ui/assets/engine-D0mFFNbL.js +1 -0
- package/dist/ui/assets/index-BIEznsi4.js +37 -0
- package/dist/ui/assets/index-BRMvNP3Y.css +1 -0
- package/dist/ui/assets/onboarding-DQmlvyWU.js +1 -0
- package/dist/ui/assets/resourceIcons-baecADXA.js +1 -0
- package/dist/ui/assets/triggerIcons-DIPWQkJ3.js +1 -0
- package/dist/ui/favicon.svg +37 -0
- package/dist/ui/index.html +5 -3
- package/package.json +12 -12
- package/dist/server/engine/aoss-sidecar.d.ts +0 -13
- package/dist/server/engine/aoss-sidecar.d.ts.map +0 -1
- package/dist/server/engine/aoss-sidecar.js +0 -93
- package/dist/server/engine/aoss-sidecar.js.map +0 -1
- package/dist/server/engine/backends/localstack-backend.d.ts +0 -25
- package/dist/server/engine/backends/localstack-backend.d.ts.map +0 -1
- package/dist/server/engine/backends/localstack-backend.js +0 -199
- package/dist/server/engine/backends/localstack-backend.js.map +0 -1
- package/dist/server/engine/engine-backend.d.ts +0 -17
- package/dist/server/engine/engine-backend.d.ts.map +0 -1
- package/dist/server/engine/engine-backend.js +0 -7
- package/dist/server/engine/engine-backend.js.map +0 -1
- package/dist/server/services/localstack-manager.d.ts +0 -17
- package/dist/server/services/localstack-manager.d.ts.map +0 -1
- package/dist/server/services/localstack-manager.js +0 -31
- package/dist/server/services/localstack-manager.js.map +0 -1
- package/dist/ui/assets/ApisPage-9Lk0_ZLj.js +0 -1
- package/dist/ui/assets/BucketDetailPage-CdLGW5em.js +0 -1
- package/dist/ui/assets/BucketsPage-CDP95vp8.js +0 -1
- package/dist/ui/assets/DynamoPage-CV7omZpi.js +0 -1
- package/dist/ui/assets/DynamoTablePage-C8v42fy7.js +0 -3
- package/dist/ui/assets/LambdaDetailPage-DkGxePSD.js +0 -4
- package/dist/ui/assets/LambdasPage-Bg7_taxW.js +0 -1
- package/dist/ui/assets/OpenSearchCollectionPage-Ci1hHyy2.js +0 -1
- package/dist/ui/assets/OpenSearchPage-CBKqZeim.js +0 -1
- package/dist/ui/assets/OverviewPage-D8lrE9rQ.js +0 -1
- package/dist/ui/assets/QueueDetailPage-D0DSIGZ2.js +0 -1
- package/dist/ui/assets/QueuesPage-BXebf6st.js +0 -1
- package/dist/ui/assets/SecretsPage-Be2Nw9BM.js +0 -1
- package/dist/ui/assets/SecretsPage-D3XYogv-.css +0 -1
- package/dist/ui/assets/ServiceDetailPage-DZpczZ-G.js +0 -2
- package/dist/ui/assets/ServicesPage-CdzxEqs3.js +0 -2
- package/dist/ui/assets/_plugin-vue_export-helper-DlAUqK2U.js +0 -1
- package/dist/ui/assets/index-C1Wb35Y8.css +0 -1
- package/dist/ui/assets/index-CGpxa55R.js +0 -37
- package/packages/serverless-plugin/README.md +0 -115
- package/packages/serverless-plugin/dist/index.d.ts +0 -2
- package/packages/serverless-plugin/dist/index.d.ts.map +0 -1
- package/packages/serverless-plugin/dist/index.js +0 -107
- package/packages/serverless-plugin/dist/index.js.map +0 -1
package/bin/cli.js
CHANGED
|
@@ -4,10 +4,17 @@ const { spawn } = require('child_process');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const { pathToFileURL } = require('url');
|
|
8
|
+
// Every user-facing string goes through t(). The locale is resolved once, at
|
|
9
|
+
// require time, from LSS_LANG / LC_ALL / LC_MESSAGES / LANG — English by
|
|
10
|
+
// default. See bin/i18n.js for what is deliberately left untranslated.
|
|
11
|
+
const { t } = require('./i18n');
|
|
7
12
|
|
|
8
13
|
// Default paths used when no config is present. The actual paths are derived
|
|
9
14
|
// per-invocation by runtimePaths() below, scoped to the configured serverPort
|
|
10
15
|
// so multiple examples (each with their own lss.config.json) can run in parallel.
|
|
16
|
+
// The dashboard, the REST API and the AWS wire all answer here by default.
|
|
17
|
+
const DEFAULT_PORT = 14566;
|
|
11
18
|
const DEFAULT_PID_FILE = path.join(os.tmpdir(), 'lss-orchestrator.pid');
|
|
12
19
|
const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'lss-orchestrator.log');
|
|
13
20
|
|
|
@@ -32,7 +39,7 @@ function runtimePaths() {
|
|
|
32
39
|
const port = cfg.serverPort;
|
|
33
40
|
// Keep the legacy global path when the default port is in use so existing
|
|
34
41
|
// installations don't lose their running process across an upgrade.
|
|
35
|
-
if (!port || port ===
|
|
42
|
+
if (!port || port === DEFAULT_PORT) {
|
|
36
43
|
return { pidFile: DEFAULT_PID_FILE, logFile: DEFAULT_LOG_FILE };
|
|
37
44
|
}
|
|
38
45
|
return {
|
|
@@ -53,10 +60,10 @@ function loadConfig() {
|
|
|
53
60
|
try {
|
|
54
61
|
return JSON.parse(fs.readFileSync(EXPLICIT_CONFIG, 'utf-8'));
|
|
55
62
|
} catch (error) {
|
|
56
|
-
console.warn(
|
|
63
|
+
console.warn(t('config.parseFailed', { path: EXPLICIT_CONFIG }));
|
|
57
64
|
}
|
|
58
65
|
} else {
|
|
59
|
-
console.warn(
|
|
66
|
+
console.warn(t('config.notFound', { path: EXPLICIT_CONFIG }));
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -73,7 +80,7 @@ function loadConfig() {
|
|
|
73
80
|
const content = fs.readFileSync(candidate, 'utf-8');
|
|
74
81
|
return JSON.parse(content);
|
|
75
82
|
} catch (error) {
|
|
76
|
-
console.warn(
|
|
83
|
+
console.warn(t('config.parseFailed', { path: candidate }));
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
}
|
|
@@ -81,23 +88,34 @@ function loadConfig() {
|
|
|
81
88
|
return {};
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
// Positive integer from an env var, or undefined when unset/garbage — so a
|
|
92
|
+
// typo falls back to the file value instead of yielding NaN.
|
|
93
|
+
function envPort(name) {
|
|
94
|
+
const parsed = parseInt(process.env[name] || '', 10);
|
|
95
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
/**
|
|
85
|
-
* Get configuration values with defaults
|
|
99
|
+
* Get configuration values with defaults.
|
|
100
|
+
*
|
|
101
|
+
* Applies the same environment overrides the server does. Without this the CLI
|
|
102
|
+
* printed the file's ports while the server bound the env's — and, worse,
|
|
103
|
+
* runtimePaths() derives the PID/log path from serverPort when no stateDir is
|
|
104
|
+
* set, so `LSS_DASHBOARD_PORT=… lss start` followed by `lss stop` addressed two
|
|
105
|
+
* different files. Running a second instance purely from the environment is a
|
|
106
|
+
* documented workflow (docs/CONFIGURATION.md), so it has to agree end to end.
|
|
86
107
|
*/
|
|
87
108
|
function getConfig(config) {
|
|
88
109
|
return {
|
|
89
|
-
serverPort: config.serverPort ||
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
serverPort: envPort('LSS_DASHBOARD_PORT') || envPort('PORT') || config.serverPort || DEFAULT_PORT,
|
|
111
|
+
enableDynamoProxy: process.env.LSS_ENABLE_DYNAMO_PROXY
|
|
112
|
+
? process.env.LSS_ENABLE_DYNAMO_PROXY === 'true' || process.env.LSS_ENABLE_DYNAMO_PROXY === '1'
|
|
113
|
+
: config.enableDynamoProxy || false,
|
|
114
|
+
dynamoProxyPort: envPort('LSS_DYNAMO_PROXY_PORT') || config.dynamoProxyPort || 8000,
|
|
93
115
|
mode: config.mode || 'managed',
|
|
94
|
-
localstackEdition: config.localstackEdition || 'community',
|
|
95
|
-
localstackVersion: config.localstackVersion || 'latest',
|
|
96
|
-
localstackImage: config.localstackImage,
|
|
97
|
-
localstackAuthToken: config.localstackAuthToken,
|
|
98
116
|
stateDir: config.stateDir,
|
|
99
|
-
engine: config.engine
|
|
100
|
-
selfEnginePort: (config.selfEngine && config.selfEngine.port) ||
|
|
117
|
+
engine: config.engine,
|
|
118
|
+
selfEnginePort: envPort('LSS_ENGINE_PORT') || (config.selfEngine && config.selfEngine.port) || DEFAULT_PORT,
|
|
101
119
|
};
|
|
102
120
|
}
|
|
103
121
|
|
|
@@ -142,6 +160,10 @@ function getOrchestratorPath() {
|
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
function startOrchestrator() {
|
|
163
|
+
// Before anything else — including the already-running short-circuit — so a
|
|
164
|
+
// stale 0.x flag or config value always fails loudly instead of looking like
|
|
165
|
+
// a successful no-op.
|
|
166
|
+
|
|
145
167
|
const { pidFile, logFile } = runtimePaths();
|
|
146
168
|
|
|
147
169
|
if (fs.existsSync(pidFile)) {
|
|
@@ -150,15 +172,11 @@ function startOrchestrator() {
|
|
|
150
172
|
process.kill(pid, 0);
|
|
151
173
|
const config = loadConfig();
|
|
152
174
|
const cfg = getConfig(config);
|
|
153
|
-
console.log('
|
|
154
|
-
console.log(
|
|
155
|
-
|
|
156
|
-
console.log(`🔧 Self Engine: http://localhost:${cfg.selfEnginePort}`);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort}`);
|
|
159
|
-
}
|
|
175
|
+
console.log(t('start.already', { pid }));
|
|
176
|
+
console.log(t('start.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
177
|
+
console.log(t('start.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
160
178
|
if (cfg.enableDynamoProxy) {
|
|
161
|
-
console.log(
|
|
179
|
+
console.log(t('start.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
162
180
|
}
|
|
163
181
|
return;
|
|
164
182
|
} catch (e) {
|
|
@@ -169,12 +187,12 @@ function startOrchestrator() {
|
|
|
169
187
|
const orchestratorPath = getOrchestratorPath();
|
|
170
188
|
|
|
171
189
|
if (!orchestratorPath) {
|
|
172
|
-
console.error('
|
|
190
|
+
console.error(t('start.notBuilt'));
|
|
173
191
|
console.error('');
|
|
174
|
-
console.error('
|
|
192
|
+
console.error(t('start.notBuiltDev'));
|
|
175
193
|
console.error(' cd /path/to/local-serverless-stack && npm run build');
|
|
176
194
|
console.error('');
|
|
177
|
-
console.error('
|
|
195
|
+
console.error(t('start.notBuiltBug'));
|
|
178
196
|
process.exit(1);
|
|
179
197
|
}
|
|
180
198
|
|
|
@@ -184,33 +202,14 @@ function startOrchestrator() {
|
|
|
184
202
|
const config = loadConfig();
|
|
185
203
|
const cfg = getConfig(config);
|
|
186
204
|
|
|
187
|
-
// Check for flags
|
|
188
205
|
const enableDynamoProxy = process.argv.includes('--enable-dynamo-proxy') || cfg.enableDynamoProxy;
|
|
189
|
-
const useExternal = process.argv.includes('--external');
|
|
190
|
-
const usePro = process.argv.includes('--pro');
|
|
191
|
-
const cliToken = getArgValue('--localstack-token');
|
|
192
|
-
const useSelfEngine = process.argv.includes('--self-engine');
|
|
193
|
-
|
|
194
|
-
const engine = useSelfEngine ? 'self' : cfg.engine;
|
|
195
|
-
if (engine === 'self' && (useExternal || usePro || cliToken)) {
|
|
196
|
-
console.error('❌ --self-engine cannot be combined with --external, --pro or --localstack-token (those are LocalStack options).');
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const mode = useExternal ? 'external' : cfg.mode;
|
|
201
|
-
const edition = usePro ? 'pro' : cfg.localstackEdition;
|
|
202
|
-
const authToken = cliToken || cfg.localstackAuthToken || process.env.LOCALSTACK_AUTH_TOKEN;
|
|
203
206
|
|
|
204
207
|
// Build environment variables from config
|
|
205
208
|
const env = { ...process.env };
|
|
206
|
-
/* istanbul ignore else: getConfig() always defaults serverPort
|
|
209
|
+
/* istanbul ignore else: getConfig() always defaults serverPort, so the else is unreachable */
|
|
207
210
|
if (cfg.serverPort) {
|
|
208
211
|
env.PORT = cfg.serverPort;
|
|
209
212
|
}
|
|
210
|
-
/* istanbul ignore else: getConfig() always defaults localstackPort to 4566, so the else is unreachable */
|
|
211
|
-
if (cfg.localstackPort) {
|
|
212
|
-
env.LSS_LOCALSTACK_PORT = cfg.localstackPort;
|
|
213
|
-
}
|
|
214
213
|
if (enableDynamoProxy) {
|
|
215
214
|
env.LSS_ENABLE_DYNAMO_PROXY = 'true';
|
|
216
215
|
}
|
|
@@ -218,30 +217,9 @@ function startOrchestrator() {
|
|
|
218
217
|
if (cfg.dynamoProxyPort) {
|
|
219
218
|
env.LSS_DYNAMO_PROXY_PORT = cfg.dynamoProxyPort;
|
|
220
219
|
}
|
|
221
|
-
/* istanbul ignore else: mode resolves to cfg.mode which getConfig() defaults to 'managed', so the else is unreachable */
|
|
222
|
-
if (mode) {
|
|
223
|
-
env.LSS_LOCALSTACK_MODE = mode;
|
|
224
|
-
}
|
|
225
|
-
/* istanbul ignore else: edition resolves to localstackEdition which getConfig() defaults to 'community', so the else is unreachable */
|
|
226
|
-
if (edition) {
|
|
227
|
-
env.LSS_LOCALSTACK_EDITION = edition;
|
|
228
|
-
}
|
|
229
|
-
/* istanbul ignore else: getConfig() always defaults localstackVersion to 'latest', so the else is unreachable */
|
|
230
|
-
if (cfg.localstackVersion) {
|
|
231
|
-
env.LSS_LOCALSTACK_VERSION = cfg.localstackVersion;
|
|
232
|
-
}
|
|
233
|
-
if (cfg.localstackImage) {
|
|
234
|
-
env.LSS_LOCALSTACK_IMAGE = cfg.localstackImage;
|
|
235
|
-
}
|
|
236
|
-
if (authToken) {
|
|
237
|
-
env.LOCALSTACK_AUTH_TOKEN = authToken;
|
|
238
|
-
}
|
|
239
|
-
if (engine === 'self') {
|
|
240
|
-
env.LSS_ENGINE = 'self';
|
|
241
|
-
}
|
|
242
220
|
// Hand the same config file to the server so its ConfigManager reads the
|
|
243
|
-
// identical serverPort/
|
|
244
|
-
//
|
|
221
|
+
// identical serverPort/seedsDir/region (not just the hand-translated subset
|
|
222
|
+
// above). Keeps the two config loaders in agreement.
|
|
245
223
|
if (EXPLICIT_CONFIG) {
|
|
246
224
|
env.LSS_CONFIG_PATH = EXPLICIT_CONFIG;
|
|
247
225
|
}
|
|
@@ -257,24 +235,20 @@ function startOrchestrator() {
|
|
|
257
235
|
fs.closeSync(logFd);
|
|
258
236
|
fs.writeFileSync(pidFile, child.pid.toString());
|
|
259
237
|
|
|
260
|
-
console.log('
|
|
261
|
-
console.log(
|
|
262
|
-
|
|
263
|
-
console.log(`🔧 Self Engine: http://localhost:${cfg.selfEnginePort} (no Docker)`);
|
|
264
|
-
} else {
|
|
265
|
-
console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort} (mode: ${mode}, edition: ${edition})`);
|
|
266
|
-
}
|
|
238
|
+
console.log(t('start.started', { pid: child.pid }));
|
|
239
|
+
console.log(t('start.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
240
|
+
console.log(t('start.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
267
241
|
if (enableDynamoProxy) {
|
|
268
|
-
console.log(
|
|
242
|
+
console.log(t('start.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
269
243
|
}
|
|
270
|
-
console.log('
|
|
244
|
+
console.log(t('start.logs', { path: logFile }));
|
|
271
245
|
|
|
272
246
|
setTimeout(() => {
|
|
273
247
|
try {
|
|
274
248
|
process.kill(child.pid, 0);
|
|
275
|
-
console.log('
|
|
249
|
+
console.log(t('start.running'));
|
|
276
250
|
} catch (e) {
|
|
277
|
-
console.error('
|
|
251
|
+
console.error(t('start.failed', { path: logFile }));
|
|
278
252
|
if (fs.existsSync(pidFile)) {
|
|
279
253
|
fs.unlinkSync(pidFile);
|
|
280
254
|
}
|
|
@@ -318,7 +292,7 @@ function waitForExit(pid, timeoutMs = 10000, intervalMs = 200) {
|
|
|
318
292
|
async function stopOrchestrator() {
|
|
319
293
|
const { pidFile } = runtimePaths();
|
|
320
294
|
if (!fs.existsSync(pidFile)) {
|
|
321
|
-
console.log('
|
|
295
|
+
console.log(t('stop.notRunning'));
|
|
322
296
|
return;
|
|
323
297
|
}
|
|
324
298
|
|
|
@@ -327,7 +301,7 @@ async function stopOrchestrator() {
|
|
|
327
301
|
try {
|
|
328
302
|
process.kill(pid, 'SIGTERM');
|
|
329
303
|
} catch (e) {
|
|
330
|
-
console.error('
|
|
304
|
+
console.error(t('stop.failed', { error: e.message }));
|
|
331
305
|
if (fs.existsSync(pidFile)) {
|
|
332
306
|
fs.unlinkSync(pidFile);
|
|
333
307
|
}
|
|
@@ -337,16 +311,16 @@ async function stopOrchestrator() {
|
|
|
337
311
|
const exited = await waitForExit(pid);
|
|
338
312
|
fs.unlinkSync(pidFile);
|
|
339
313
|
if (exited) {
|
|
340
|
-
console.log('
|
|
314
|
+
console.log(t('stop.stopped', { pid }));
|
|
341
315
|
} else {
|
|
342
|
-
console.warn(
|
|
316
|
+
console.warn(t('stop.timeout', { pid }));
|
|
343
317
|
}
|
|
344
318
|
}
|
|
345
319
|
|
|
346
320
|
function showStatus() {
|
|
347
321
|
const { pidFile, logFile } = runtimePaths();
|
|
348
322
|
if (!fs.existsSync(pidFile)) {
|
|
349
|
-
console.log('
|
|
323
|
+
console.log(t('status.notRunning'));
|
|
350
324
|
return;
|
|
351
325
|
}
|
|
352
326
|
|
|
@@ -356,15 +330,15 @@ function showStatus() {
|
|
|
356
330
|
process.kill(pid, 0);
|
|
357
331
|
const config = loadConfig();
|
|
358
332
|
const cfg = getConfig(config);
|
|
359
|
-
console.log(
|
|
360
|
-
console.log(
|
|
361
|
-
console.log(
|
|
333
|
+
console.log(t('status.running', { pid }));
|
|
334
|
+
console.log(t('status.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
335
|
+
console.log(t('status.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
362
336
|
if (cfg.enableDynamoProxy) {
|
|
363
|
-
console.log(
|
|
337
|
+
console.log(t('status.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
364
338
|
}
|
|
365
|
-
console.log('
|
|
339
|
+
console.log(t('status.logs', { path: logFile }));
|
|
366
340
|
} catch (e) {
|
|
367
|
-
console.log('
|
|
341
|
+
console.log(t('status.stale'));
|
|
368
342
|
fs.unlinkSync(pidFile);
|
|
369
343
|
}
|
|
370
344
|
}
|
|
@@ -379,13 +353,13 @@ function getServerPort() {
|
|
|
379
353
|
// orchestrator startup window) — fall back through every signal we have so
|
|
380
354
|
// `formatError` is guaranteed not to return an empty string.
|
|
381
355
|
function formatError(e) {
|
|
382
|
-
if (!e) return '
|
|
383
|
-
if (typeof e === 'string') return e.trim() || '
|
|
356
|
+
if (!e) return t('error.unknown');
|
|
357
|
+
if (typeof e === 'string') return e.trim() || t('error.unknown');
|
|
384
358
|
if (e.message && String(e.message).trim()) return String(e.message).trim();
|
|
385
|
-
if (e.code) return
|
|
359
|
+
if (e.code) return t('error.io', { code: e.code });
|
|
386
360
|
if (e.name) return e.name;
|
|
387
361
|
const s = String(e);
|
|
388
|
-
return s && s !== '[object Object]' ? s : '
|
|
362
|
+
return s && s !== '[object Object]' ? s : t('error.unknown');
|
|
389
363
|
}
|
|
390
364
|
|
|
391
365
|
function buildHttpError(res, data) {
|
|
@@ -395,7 +369,9 @@ function buildHttpError(res, data) {
|
|
|
395
369
|
if (fromBody && String(fromBody).trim()) return new Error(String(fromBody).trim());
|
|
396
370
|
const snippet = data && data.length < 300 ? data.trim() : '';
|
|
397
371
|
const statusText = res.statusMessage ? `${res.statusCode} ${res.statusMessage}` : `${res.statusCode}`;
|
|
398
|
-
|
|
372
|
+
// `HTTP <status>: <snippet>` is pure wire detail, so only the "no body at
|
|
373
|
+
// all" variant carries translatable prose.
|
|
374
|
+
return new Error(snippet ? `HTTP ${statusText}: ${snippet}` : t('error.httpNoBody', { status: statusText }));
|
|
399
375
|
}
|
|
400
376
|
|
|
401
377
|
function postJson(path, body) {
|
|
@@ -427,7 +403,7 @@ function postJson(path, body) {
|
|
|
427
403
|
});
|
|
428
404
|
},
|
|
429
405
|
);
|
|
430
|
-
req.on('error', err => reject(err && err.message ? err : new Error(
|
|
406
|
+
req.on('error', err => reject(err && err.message ? err : new Error(t('error.httpConnection', { error: formatError(err) }))));
|
|
431
407
|
req.write(payload);
|
|
432
408
|
req.end();
|
|
433
409
|
});
|
|
@@ -436,7 +412,7 @@ function postJson(path, body) {
|
|
|
436
412
|
function ensureRunningOrExit() {
|
|
437
413
|
const { pidFile } = runtimePaths();
|
|
438
414
|
if (!fs.existsSync(pidFile)) {
|
|
439
|
-
console.error('
|
|
415
|
+
console.error(t('error.notRunning'));
|
|
440
416
|
process.exit(1);
|
|
441
417
|
}
|
|
442
418
|
}
|
|
@@ -445,19 +421,19 @@ function printSeedRunResults(results) {
|
|
|
445
421
|
let missingTables = 0;
|
|
446
422
|
for (const r of results) {
|
|
447
423
|
if (r.skipped) {
|
|
448
|
-
console.log(
|
|
449
|
-
if (r.reason && r.reason.includes('does not exist in
|
|
424
|
+
console.log(t('seed.skipped', { table: r.tableName, reason: r.reason }));
|
|
425
|
+
if (r.reason && r.reason.includes('does not exist in the engine')) {
|
|
450
426
|
missingTables++;
|
|
451
427
|
}
|
|
452
428
|
} else {
|
|
453
|
-
console.log(
|
|
429
|
+
console.log(t('seed.inserted', { table: r.tableName, count: r.inserted }));
|
|
454
430
|
}
|
|
455
431
|
}
|
|
456
432
|
return { missingTables };
|
|
457
433
|
}
|
|
458
434
|
|
|
459
435
|
// Show the user both sides of the comparison: the seed files we found, and
|
|
460
|
-
// the DynamoDB tables actually living in
|
|
436
|
+
// the DynamoDB tables actually living in the engine. This is the difference
|
|
461
437
|
// between "I didn't deploy yet" (no live tables at all) and "my seed file
|
|
462
438
|
// name doesn't match the CFN TableName" (live tables exist, just not those).
|
|
463
439
|
function printSeedMismatchDiagnostic({ entries, liveTables, focusTable }) {
|
|
@@ -466,37 +442,39 @@ function printSeedMismatchDiagnostic({ entries, liveTables, focusTable }) {
|
|
|
466
442
|
|
|
467
443
|
console.log('');
|
|
468
444
|
if (focusTable) {
|
|
469
|
-
console.log(
|
|
445
|
+
console.log(t('seed.diagFile', { table: focusTable }));
|
|
470
446
|
} else if (focusList.length > 0) {
|
|
471
|
-
console.log('
|
|
447
|
+
console.log(t('seed.diagFiles'));
|
|
472
448
|
for (const name of focusList) console.log(` - ${name}`);
|
|
473
449
|
} else {
|
|
474
|
-
console.log('
|
|
450
|
+
console.log(t('seed.diagNoFiles'));
|
|
475
451
|
}
|
|
476
452
|
|
|
477
453
|
if (liveTables && liveTables.length > 0) {
|
|
478
454
|
console.log('');
|
|
479
|
-
console.log(
|
|
455
|
+
console.log(t('seed.diagLiveTables', { count: liveTables.length }));
|
|
480
456
|
for (const name of liveTables) console.log(` - ${name}`);
|
|
481
457
|
console.log('');
|
|
482
|
-
console.log('
|
|
483
|
-
console.log('
|
|
458
|
+
console.log(t('seed.diagMatchHint'));
|
|
459
|
+
console.log(t('seed.diagPrefixHint'));
|
|
484
460
|
} else {
|
|
485
461
|
console.log('');
|
|
486
|
-
console.log('
|
|
487
|
-
console.log('
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
console.log(
|
|
462
|
+
console.log(t('seed.diagNoLiveTables'));
|
|
463
|
+
console.log(t('seed.diagNotProvisioned'));
|
|
464
|
+
// The commands themselves are never translated — only the comment that
|
|
465
|
+
// explains what each one buys you.
|
|
466
|
+
console.log(` npx lss start # ${t('seed.diagStepStart')}`);
|
|
467
|
+
console.log(` npx serverless deploy # ${t('seed.diagStepDeploy')}`);
|
|
468
|
+
console.log(t('seed.diagRetry'));
|
|
491
469
|
}
|
|
492
470
|
}
|
|
493
471
|
|
|
494
472
|
function printSeedClearResults(results) {
|
|
495
473
|
for (const r of results) {
|
|
496
474
|
if (r.skipped) {
|
|
497
|
-
console.log(
|
|
475
|
+
console.log(t('seed.skipped', { table: r.tableName, reason: r.reason }));
|
|
498
476
|
} else {
|
|
499
|
-
console.log(
|
|
477
|
+
console.log(t('seed.deleted', { table: r.tableName, count: r.deleted }));
|
|
500
478
|
}
|
|
501
479
|
}
|
|
502
480
|
}
|
|
@@ -504,7 +482,7 @@ function printSeedClearResults(results) {
|
|
|
504
482
|
async function runSeed(tableName) {
|
|
505
483
|
ensureRunningOrExit();
|
|
506
484
|
try {
|
|
507
|
-
console.log(tableName ?
|
|
485
|
+
console.log(tableName ? t('seed.runningTable', { table: tableName }) : t('seed.running'));
|
|
508
486
|
const res = await postJson('/api/seeds/run', tableName ? { tableName } : {});
|
|
509
487
|
const { missingTables } = printSeedRunResults(res.results || []);
|
|
510
488
|
if (missingTables > 0) {
|
|
@@ -517,11 +495,11 @@ async function runSeed(tableName) {
|
|
|
517
495
|
});
|
|
518
496
|
} catch (diagErr) {
|
|
519
497
|
// Diagnostic is best-effort; don't fail the seed because the hint failed.
|
|
520
|
-
console.log(
|
|
498
|
+
console.log(t('seed.diagFailed', { error: formatError(diagErr) }));
|
|
521
499
|
}
|
|
522
500
|
}
|
|
523
501
|
} catch (e) {
|
|
524
|
-
console.error('
|
|
502
|
+
console.error(t('seed.failed'), formatError(e));
|
|
525
503
|
process.exit(1);
|
|
526
504
|
}
|
|
527
505
|
}
|
|
@@ -550,7 +528,7 @@ function getJson(path) {
|
|
|
550
528
|
});
|
|
551
529
|
},
|
|
552
530
|
);
|
|
553
|
-
req.on('error', err => reject(err && err.message ? err : new Error(
|
|
531
|
+
req.on('error', err => reject(err && err.message ? err : new Error(t('error.httpConnection', { error: formatError(err) }))));
|
|
554
532
|
req.end();
|
|
555
533
|
});
|
|
556
534
|
}
|
|
@@ -562,7 +540,7 @@ function promptConfirmation(expectedWord) {
|
|
|
562
540
|
input: process.stdin,
|
|
563
541
|
output: process.stdout,
|
|
564
542
|
});
|
|
565
|
-
rl.question(
|
|
543
|
+
rl.question(t('seed.confirmPrompt', { word: expectedWord }), answer => {
|
|
566
544
|
rl.close();
|
|
567
545
|
resolve(answer.trim() === expectedWord);
|
|
568
546
|
});
|
|
@@ -577,8 +555,8 @@ async function clearSeed(tableName) {
|
|
|
577
555
|
|
|
578
556
|
// Show the user exactly what's about to be wiped before they confirm.
|
|
579
557
|
// Hitting the orchestrator's GET /api/seeds also implicitly proves we're
|
|
580
|
-
// talking to
|
|
581
|
-
// to the configured local
|
|
558
|
+
// talking to the local engine and not AWS — the orchestrator only ever
|
|
559
|
+
// connects to the configured local engine endpoint.
|
|
582
560
|
let scopeDescription;
|
|
583
561
|
try {
|
|
584
562
|
const list = await getJson('/api/seeds');
|
|
@@ -589,138 +567,300 @@ async function clearSeed(tableName) {
|
|
|
589
567
|
|
|
590
568
|
if (liveTargets.length === 0) {
|
|
591
569
|
if (tableName) {
|
|
592
|
-
console.log(
|
|
570
|
+
console.log(t('clear.noSuchTable', { table: tableName }));
|
|
593
571
|
} else if (entries.length === 0) {
|
|
594
|
-
console.log('
|
|
572
|
+
console.log(t('clear.noSeedFiles'));
|
|
595
573
|
} else {
|
|
596
|
-
console.log(
|
|
574
|
+
console.log(t('clear.noneLive', { count: entries.length }));
|
|
597
575
|
}
|
|
598
576
|
printSeedMismatchDiagnostic({ entries, liveTables, focusTable: tableName });
|
|
599
577
|
return;
|
|
600
578
|
}
|
|
601
579
|
|
|
602
580
|
scopeDescription = tableName
|
|
603
|
-
?
|
|
604
|
-
:
|
|
581
|
+
? t('clear.scopeTable', { table: tableName })
|
|
582
|
+
: t('clear.scopeTables', {
|
|
583
|
+
count: liveTargets.length,
|
|
584
|
+
tables: liveTargets.map(e => e.tableName).join(', '),
|
|
585
|
+
});
|
|
605
586
|
|
|
606
587
|
console.log('');
|
|
607
|
-
console.log('
|
|
608
|
-
console.log(
|
|
609
|
-
console.log(
|
|
610
|
-
console.log('
|
|
588
|
+
console.log(t('clear.warning'));
|
|
589
|
+
console.log(t('clear.target', { scope: scopeDescription }));
|
|
590
|
+
console.log(t('clear.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
591
|
+
console.log(t('clear.noAws'));
|
|
611
592
|
console.log('');
|
|
612
593
|
} catch (e) {
|
|
613
|
-
console.error('
|
|
594
|
+
console.error(t('clear.listFailed'), formatError(e));
|
|
614
595
|
process.exit(1);
|
|
615
596
|
}
|
|
616
597
|
|
|
617
598
|
if (!skipConfirm) {
|
|
599
|
+
// The word itself is NOT translated: it is a magic token CI scripts and the
|
|
600
|
+
// docs already depend on, so it stays `confirmar` in every locale.
|
|
618
601
|
const ok = await promptConfirmation('confirmar');
|
|
619
602
|
if (!ok) {
|
|
620
|
-
console.log('
|
|
603
|
+
console.log(t('seed.aborted'));
|
|
621
604
|
return;
|
|
622
605
|
}
|
|
623
606
|
} else {
|
|
624
|
-
console.log('
|
|
607
|
+
console.log(t('clear.skipPrompt'));
|
|
625
608
|
}
|
|
626
609
|
|
|
627
610
|
try {
|
|
628
|
-
console.log(tableName ?
|
|
611
|
+
console.log(tableName ? t('seed.clearingTable', { table: tableName }) : t('seed.clearing'));
|
|
629
612
|
const res = await postJson('/api/seeds/clear', tableName ? { tableName } : {});
|
|
630
613
|
printSeedClearResults(res.results || []);
|
|
631
614
|
} catch (e) {
|
|
632
|
-
console.error('
|
|
615
|
+
console.error(t('clear.failed'), formatError(e));
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* `lss mcp` — run the Model Context Protocol server on stdio.
|
|
622
|
+
*
|
|
623
|
+
* Started by an MCP client (Claude Code reads .mcp.json), never by a human at a
|
|
624
|
+
* prompt: stdout is the JSON-RPC frame stream, so nothing else may be written
|
|
625
|
+
* there. This process only talks HTTP to an already-running orchestrator — it
|
|
626
|
+
* never boots one — so `lss start` has to have happened first.
|
|
627
|
+
*/
|
|
628
|
+
/**
|
|
629
|
+
* `lss register [path...]` — register services with the running orchestrator.
|
|
630
|
+
*
|
|
631
|
+
* This replaces the retired serverless-lss plugin: registration is a plain
|
|
632
|
+
* POST of { servicePath }, and the orchestrator resolves everything else
|
|
633
|
+
* (packaging via autoPackage when needed, then name/region/custom.lss ports
|
|
634
|
+
* from the packaged serverless-state.json).
|
|
635
|
+
*/
|
|
636
|
+
async function registerServices(paths) {
|
|
637
|
+
ensureRunningOrExit();
|
|
638
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
639
|
+
let failed = 0;
|
|
640
|
+
for (const target of targets) {
|
|
641
|
+
const servicePath = path.resolve(target);
|
|
642
|
+
if (!fs.existsSync(servicePath)) {
|
|
643
|
+
console.error(t('register.notFound', { target }));
|
|
644
|
+
failed++;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const result = await postJson('/api/services/register', { servicePath });
|
|
649
|
+
console.log(t('register.ok', {
|
|
650
|
+
name: result.serviceName,
|
|
651
|
+
resources: result.resourcesCount,
|
|
652
|
+
functions: result.functionsCount,
|
|
653
|
+
routes: result.routesCount,
|
|
654
|
+
}));
|
|
655
|
+
// Warnings come from the orchestrator already worded; only the bullet is ours.
|
|
656
|
+
for (const warning of result.warnings || []) {
|
|
657
|
+
console.log(` ⚠ ${warning}`);
|
|
658
|
+
}
|
|
659
|
+
} catch (e) {
|
|
660
|
+
console.error(t('register.failed', { target, error: formatError(e) }));
|
|
661
|
+
failed++;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (failed > 0) {
|
|
633
665
|
process.exit(1);
|
|
634
666
|
}
|
|
635
667
|
}
|
|
636
668
|
|
|
669
|
+
/**
|
|
670
|
+
* `lss scan` — list every Serverless/osls service found under the project
|
|
671
|
+
* root, with the same flags onboarding shows.
|
|
672
|
+
*
|
|
673
|
+
* The three flags mirror the checklist: `registered` (known to this
|
|
674
|
+
* orchestrator), `installed` (node_modules resolvable, so packaging can run
|
|
675
|
+
* without an install first) and `packaged` (a template already exists). The
|
|
676
|
+
* effective package command gets its own line because it is per-service
|
|
677
|
+
* configurable (`servicePackaging`) and is what the install→package→register
|
|
678
|
+
* buttons would run.
|
|
679
|
+
*/
|
|
680
|
+
/**
|
|
681
|
+
* Render one scan warning. The orchestrator sends a stable `code` plus the
|
|
682
|
+
* English `message` it produced; the code is what gets localised, and the
|
|
683
|
+
* message is the fallback for a code this CLI has not been taught yet — a
|
|
684
|
+
* newer server's warning still prints instead of disappearing.
|
|
685
|
+
*/
|
|
686
|
+
function scanWarningText(warning) {
|
|
687
|
+
if (!warning || typeof warning !== 'object') return String(warning);
|
|
688
|
+
const key = `scan.warning.${warning.code}`;
|
|
689
|
+
const translated = t(key, warning.params);
|
|
690
|
+
return translated === key ? warning.message : translated;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function scanServices() {
|
|
694
|
+
ensureRunningOrExit();
|
|
695
|
+
try {
|
|
696
|
+
const result = await getJson('/api/services/scan');
|
|
697
|
+
const services = result.services || [];
|
|
698
|
+
if (services.length === 0) {
|
|
699
|
+
console.log(t('scan.none', { root: result.projectRoot }));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
console.log(`${t('scan.header', { count: services.length, root: result.projectRoot })}\n`);
|
|
703
|
+
for (const svc of services) {
|
|
704
|
+
const flags = [
|
|
705
|
+
svc.registered ? t('scan.registered') : t('scan.notRegistered'),
|
|
706
|
+
svc.installed ? t('scan.installed') : t('scan.notInstalled'),
|
|
707
|
+
svc.packaged ? t('scan.packaged') : t('scan.notPackaged'),
|
|
708
|
+
].join(', ');
|
|
709
|
+
const ports = svc.apiPort ? ` api:${svc.apiPort}${svc.invokePort ? ` invoke:${svc.invokePort}` : ''}` : '';
|
|
710
|
+
console.log(` ${svc.registered ? '✓' : '·'} ${svc.name} (${svc.relPath}) — ${flags}${ports}`);
|
|
711
|
+
if (svc.packageCommand) {
|
|
712
|
+
console.log(` ${t('scan.packageCommand', { command: svc.packageCommand })}`);
|
|
713
|
+
}
|
|
714
|
+
for (const warning of svc.warnings || []) {
|
|
715
|
+
console.log(` ⚠ ${scanWarningText(warning)}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
console.log(t('scan.hint'));
|
|
719
|
+
} catch (e) {
|
|
720
|
+
console.error(t('scan.failed'), formatError(e));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function getMcpServerPath() {
|
|
726
|
+
const candidates = [
|
|
727
|
+
path.join(__dirname, '../dist/mcp/server.js'),
|
|
728
|
+
path.join(__dirname, '..', 'dist', 'mcp', 'server.js'),
|
|
729
|
+
];
|
|
730
|
+
return candidates.find(candidate => fs.existsSync(candidate)) || null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function getPackageVersion() {
|
|
734
|
+
try {
|
|
735
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')).version || '0.0.0';
|
|
736
|
+
} catch {
|
|
737
|
+
return '0.0.0';
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function runMcpServer() {
|
|
742
|
+
const serverPath = getMcpServerPath();
|
|
743
|
+
const version = getPackageVersion();
|
|
744
|
+
/* istanbul ignore else: the "found" path falls through to the dynamic import below, which is the process entry point (it binds this process's stdio) and so is not unit-testable */
|
|
745
|
+
if (!serverPath) {
|
|
746
|
+
console.error(t('mcp.missing'));
|
|
747
|
+
process.exit(1); // never returns
|
|
748
|
+
}
|
|
749
|
+
// Dynamic import: the MCP build is ESM, and loading it only for this command
|
|
750
|
+
// keeps every other CLI invocation free of it.
|
|
751
|
+
/* istanbul ignore next: process entry point — loads the ESM build and binds this process's stdio */
|
|
752
|
+
import(pathToFileURL(serverPath).href)
|
|
753
|
+
.then(mod => mod.main(version))
|
|
754
|
+
.catch(error => {
|
|
755
|
+
console.error(t('mcp.startFailed'), error && error.message ? error.message : error);
|
|
756
|
+
process.exit(1);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// One ` <name> <description>` row of the help screen. The description is
|
|
761
|
+
// wrapped and hanging-indented under itself so a translation can be longer than
|
|
762
|
+
// the English original without shredding the column — the reason the help text
|
|
763
|
+
// is assembled here instead of living in the catalogue as one pre-formatted blob.
|
|
764
|
+
function helpRow(name, description, nameWidth, textWidth) {
|
|
765
|
+
const gutter = ' '.repeat(2 + nameWidth);
|
|
766
|
+
const wrapped = [];
|
|
767
|
+
let current = '';
|
|
768
|
+
for (const word of description.split(' ')) {
|
|
769
|
+
if (current && current.length + 1 + word.length > textWidth) {
|
|
770
|
+
wrapped.push(current);
|
|
771
|
+
current = word;
|
|
772
|
+
} else {
|
|
773
|
+
current = current ? `${current} ${word}` : word;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
wrapped.push(current);
|
|
777
|
+
return [` ${name.padEnd(nameWidth)}${wrapped[0]}`, ...wrapped.slice(1).map(line => gutter + line)]
|
|
778
|
+
.join('\n');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// A ` npx lss … # what it does` example line. The command is never translated.
|
|
782
|
+
function helpExample(command, comment) {
|
|
783
|
+
return ` ${command.padEnd(43)}# ${comment}`;
|
|
784
|
+
}
|
|
785
|
+
|
|
637
786
|
function showHelp() {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
{
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"enableDynamoProxy": false,
|
|
681
|
-
"dynamoProxyPort": 8000,
|
|
682
|
-
"region": "us-east-1",
|
|
683
|
-
"
|
|
684
|
-
"
|
|
685
|
-
"
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
LOCALSTACK_AUTH_TOKEN=xxx npx lss start # Inject a token via env var
|
|
706
|
-
npx lss stop # Stop the orchestrator
|
|
707
|
-
npx lss status # Check status
|
|
708
|
-
npx lss logs # View logs
|
|
709
|
-
npx lss seed # Seed every table that has a {name}.json file
|
|
710
|
-
npx lss seed users # Seed only the "users" table
|
|
711
|
-
npx lss seed:clear users # Delete all items from "users" (com confirmação)
|
|
712
|
-
npx lss seed:clear users --yes # Mesma coisa, sem prompt (CI)
|
|
713
|
-
`);
|
|
787
|
+
// Command names, flags, env var names, paths and the JSON template are
|
|
788
|
+
// identifiers — they stay here, untranslated. Only prose goes through t().
|
|
789
|
+
const cmd = (name, key) => helpRow(name, t(key), 19, 56);
|
|
790
|
+
const opt = (name, key) => helpRow(name, t(key), 29, 46);
|
|
791
|
+
|
|
792
|
+
console.log([
|
|
793
|
+
'',
|
|
794
|
+
'Local Serverless Stack (LSS) CLI',
|
|
795
|
+
'',
|
|
796
|
+
t('help.usage'),
|
|
797
|
+
'',
|
|
798
|
+
t('help.commands'),
|
|
799
|
+
cmd('start', 'help.cmd.start'),
|
|
800
|
+
cmd('stop', 'help.cmd.stop'),
|
|
801
|
+
cmd('status', 'help.cmd.status'),
|
|
802
|
+
cmd('logs', 'help.cmd.logs'),
|
|
803
|
+
cmd('seed [table]', 'help.cmd.seed'),
|
|
804
|
+
cmd('scan', 'help.cmd.scan'),
|
|
805
|
+
cmd('register [path...]', 'help.cmd.register'),
|
|
806
|
+
cmd('seed:clear [table]', 'help.cmd.seedClear'),
|
|
807
|
+
cmd('mcp', 'help.cmd.mcp'),
|
|
808
|
+
cmd('help', 'help.cmd.help'),
|
|
809
|
+
'',
|
|
810
|
+
t('help.options'),
|
|
811
|
+
opt('--config <path>', 'help.opt.config'),
|
|
812
|
+
opt('--enable-dynamo-proxy', 'help.opt.dynamoProxy'),
|
|
813
|
+
opt('--yes, -y', 'help.opt.yes'),
|
|
814
|
+
'',
|
|
815
|
+
t('help.environment'),
|
|
816
|
+
opt('LSS_DASHBOARD_PORT', 'help.env.dashboardPort'),
|
|
817
|
+
opt('LSS_ENGINE_PORT', 'help.env.enginePort'),
|
|
818
|
+
opt('LSS_ENGINE_DATA_DIR', 'help.env.engineDataDir'),
|
|
819
|
+
opt('AWS_REGION', 'help.env.awsRegion'),
|
|
820
|
+
opt('LSS_LANG', 'help.env.lang'),
|
|
821
|
+
'',
|
|
822
|
+
t('help.configuration'),
|
|
823
|
+
` ${t('help.config.intro')}`,
|
|
824
|
+
'',
|
|
825
|
+
` ${t('help.config.example')}`,
|
|
826
|
+
' {',
|
|
827
|
+
' "serverPort": 3100,',
|
|
828
|
+
' "selfEngine": { "port": 14566 },',
|
|
829
|
+
' "enableDynamoProxy": false,',
|
|
830
|
+
' "dynamoProxyPort": 8000,',
|
|
831
|
+
' "region": "us-east-1",',
|
|
832
|
+
' "persistence": true,',
|
|
833
|
+
' "debug": false,',
|
|
834
|
+
' "stateDir": ".lss"',
|
|
835
|
+
' }',
|
|
836
|
+
'',
|
|
837
|
+
helpRow('', t('help.config.stateDir'), 0, 76),
|
|
838
|
+
'',
|
|
839
|
+
t('help.examples'),
|
|
840
|
+
helpExample('npx lss start', t('help.ex.start')),
|
|
841
|
+
helpExample('npx lss start --enable-dynamo-proxy', t('help.ex.startProxy')),
|
|
842
|
+
helpExample('npx lss mcp', t('help.ex.mcp')),
|
|
843
|
+
helpExample('npx lss stop', t('help.ex.stop')),
|
|
844
|
+
helpExample('npx lss status', t('help.ex.status')),
|
|
845
|
+
helpExample('npx lss logs', t('help.ex.logs')),
|
|
846
|
+
helpExample('npx lss scan', t('help.ex.scan')),
|
|
847
|
+
helpExample('npx lss register', t('help.ex.register')),
|
|
848
|
+
helpExample('npx lss seed', t('help.ex.seed')),
|
|
849
|
+
helpExample('npx lss seed users', t('help.ex.seedTable')),
|
|
850
|
+
helpExample('npx lss seed:clear users', t('help.ex.seedClear')),
|
|
851
|
+
helpExample('npx lss seed:clear users --yes', t('help.ex.seedClearYes')),
|
|
852
|
+
'',
|
|
853
|
+
].join('\n'));
|
|
714
854
|
}
|
|
715
855
|
|
|
716
856
|
function showLogs() {
|
|
717
857
|
const { logFile } = runtimePaths();
|
|
718
858
|
if (!fs.existsSync(logFile)) {
|
|
719
|
-
console.log('
|
|
859
|
+
console.log(t('logs.missing', { path: logFile }));
|
|
720
860
|
return;
|
|
721
861
|
}
|
|
722
862
|
|
|
723
|
-
console.log('
|
|
863
|
+
console.log(t('logs.from', { path: logFile }));
|
|
724
864
|
console.log('---');
|
|
725
865
|
const logs = fs.readFileSync(logFile, 'utf8');
|
|
726
866
|
const lines = logs.split('\n');
|
|
@@ -732,11 +872,23 @@ function showLogs() {
|
|
|
732
872
|
// Skip anything that looks like a flag so `seed:clear --yes` doesn't pass
|
|
733
873
|
// "--yes" as the table name.
|
|
734
874
|
function firstPositional() {
|
|
875
|
+
return allPositionals()[0];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Every non-flag argument after the command, skipping flag VALUES too
|
|
879
|
+
// (`--config <path>` consumes the next token).
|
|
880
|
+
function allPositionals() {
|
|
881
|
+
const flagsWithValue = new Set(['--config']);
|
|
882
|
+
const out = [];
|
|
735
883
|
for (let i = 3; i < process.argv.length; i++) {
|
|
736
884
|
const arg = process.argv[i];
|
|
737
|
-
if (
|
|
885
|
+
if (arg.startsWith('-')) {
|
|
886
|
+
if (flagsWithValue.has(arg)) i++;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
out.push(arg);
|
|
738
890
|
}
|
|
739
|
-
return
|
|
891
|
+
return out;
|
|
740
892
|
}
|
|
741
893
|
|
|
742
894
|
// Exported so the pure/in-process helpers can be unit-tested by requiring this
|
|
@@ -746,6 +898,12 @@ function firstPositional() {
|
|
|
746
898
|
module.exports = {
|
|
747
899
|
loadConfig,
|
|
748
900
|
getConfig,
|
|
901
|
+
allPositionals,
|
|
902
|
+
registerServices,
|
|
903
|
+
scanServices,
|
|
904
|
+
getMcpServerPath,
|
|
905
|
+
getPackageVersion,
|
|
906
|
+
runMcpServer,
|
|
749
907
|
getArgValue,
|
|
750
908
|
runtimePaths,
|
|
751
909
|
getOrchestratorPath,
|
|
@@ -792,13 +950,22 @@ if (require.main === module) {
|
|
|
792
950
|
case 'seed:clear':
|
|
793
951
|
clearSeed(firstPositional());
|
|
794
952
|
break;
|
|
953
|
+
case 'register':
|
|
954
|
+
registerServices(allPositionals());
|
|
955
|
+
break;
|
|
956
|
+
case 'scan':
|
|
957
|
+
scanServices();
|
|
958
|
+
break;
|
|
959
|
+
case 'mcp':
|
|
960
|
+
runMcpServer();
|
|
961
|
+
break;
|
|
795
962
|
case 'help':
|
|
796
963
|
case '--help':
|
|
797
964
|
case '-h':
|
|
798
965
|
showHelp();
|
|
799
966
|
break;
|
|
800
967
|
default:
|
|
801
|
-
console.log('
|
|
968
|
+
console.log(t('help.unknown', { command }));
|
|
802
969
|
showHelp();
|
|
803
970
|
process.exit(1);
|
|
804
971
|
}
|