local-serverless-stack 0.17.2 → 1.0.1
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 +1155 -221
- package/assets/icon.svg +37 -0
- package/assets/logo.svg +38 -0
- package/bin/cli.js +519 -228
- package/bin/i18n.js +525 -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/errors.js +1 -1
- package/dist/server/engine/http/errors.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 +31 -52
- 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 +77 -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 +288 -24
- package/dist/server/routes/services.js.map +1 -1
- package/dist/server/runtime/runtime-worker.js +7 -0
- package/dist/server/runtime/runtime-worker.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 +7 -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/cloudformation-parser.js +3 -3
- package/dist/server/services/cloudformation-parser.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +53 -35
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +600 -185
- 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 +8 -1
- package/dist/server/services/lambda-runtime-manager.d.ts.map +1 -1
- package/dist/server/services/lambda-runtime-manager.js +131 -11
- 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 +19 -3
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +153 -26
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/s3-explorer.js +4 -4
- 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 +28 -14
- 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 +13 -1
- package/dist/server/services/service-registrar.d.ts.map +1 -1
- package/dist/server/services/service-registrar.js +80 -16
- package/dist/server/services/service-registrar.js.map +1 -1
- package/dist/server/services/service-scanner.d.ts +25 -0
- package/dist/server/services/service-scanner.d.ts.map +1 -0
- package/dist/server/services/service-scanner.js +219 -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-CTOcNna-.js +1 -0
- package/dist/ui/assets/BucketDetailPage-BXW8-FrE.js +1 -0
- package/dist/ui/assets/BucketsPage-DY_ITepb.js +1 -0
- package/dist/ui/assets/DynamoPage-BnFFKnIU.js +1 -0
- package/dist/ui/assets/DynamoTablePage-BLCuyJHU.js +3 -0
- package/dist/ui/assets/LambdaDetailPage-D4rZNLRT.js +4 -0
- package/dist/ui/assets/LambdasPage-Bg-MWFBb.js +1 -0
- package/dist/ui/assets/OnboardingPage-nnl313Fm.js +1 -0
- package/dist/ui/assets/OpenSearchCollectionPage-Dn15lH_F.js +1 -0
- package/dist/ui/assets/OpenSearchPage-CgD5isok.js +1 -0
- package/dist/ui/assets/{OverviewPage-DS6XJ7kG.css → OverviewPage-CSWY71vZ.css} +1 -1
- package/dist/ui/assets/OverviewPage-m5sUr8IN.js +1 -0
- package/dist/ui/assets/QueueDetailPage-Ch_FDDoN.js +1 -0
- package/dist/ui/assets/QueuesPage-C_yDuGDu.js +1 -0
- package/dist/ui/assets/SecretsPage-BSGMSoqy.js +1 -0
- package/dist/ui/assets/ServiceDetailPage-6KyohyjI.js +2 -0
- package/dist/ui/assets/ServicesPage-BbSetOMo.js +2 -0
- package/dist/ui/assets/SettingsPage-BgF1jFr6.js +1 -0
- package/dist/ui/assets/engine-D0mFFNbL.js +1 -0
- package/dist/ui/assets/index-D627ZyYU.css +1 -0
- package/dist/ui/assets/index-DuSPnI19.js +37 -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 +11 -11
- 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-CXYcWDhm.js +0 -1
- package/dist/ui/assets/BucketDetailPage-BwS2--Pe.js +0 -1
- package/dist/ui/assets/BucketsPage-khuiYc9C.js +0 -1
- package/dist/ui/assets/DynamoPage-aYo0QC0N.js +0 -1
- package/dist/ui/assets/DynamoTablePage-rFuV2pgd.js +0 -3
- package/dist/ui/assets/LambdaDetailPage-CJmu8M_N.js +0 -4
- package/dist/ui/assets/LambdasPage-DniNyKMe.js +0 -1
- package/dist/ui/assets/OpenSearchCollectionPage-BBwjgbB9.js +0 -1
- package/dist/ui/assets/OpenSearchPage-D5RxJGRo.js +0 -1
- package/dist/ui/assets/OverviewPage-Cyt1Xdp8.js +0 -1
- package/dist/ui/assets/QueueDetailPage-3FPsowIp.js +0 -1
- package/dist/ui/assets/QueuesPage-CUoDDgHr.js +0 -1
- package/dist/ui/assets/SecretsPage-B_mTnUf3.js +0 -1
- package/dist/ui/assets/ServiceDetailPage-BcdSjA6e.js +0 -2
- package/dist/ui/assets/ServicesPage-CU6fb3f8.js +0 -2
- package/dist/ui/assets/SettingsPage-BG8cYxgC.js +0 -1
- package/dist/ui/assets/index-BxwA-UT0.css +0 -1
- package/dist/ui/assets/index-DGsXIPJ-.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
|
|
|
@@ -141,7 +159,51 @@ function getOrchestratorPath() {
|
|
|
141
159
|
return null;
|
|
142
160
|
}
|
|
143
161
|
|
|
144
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Who is answering on our serverPort, if anyone: the `projectRoot` reported by
|
|
164
|
+
* `GET /api/config`. Under `--net=host` the port belongs to the whole machine,
|
|
165
|
+
* so "someone answers but it is not us" is a routine outcome — and the one the
|
|
166
|
+
* old `NOT RUNNING` turned into a wild goose chase. Returns null when nothing
|
|
167
|
+
* answers (or answers something else), because this only ever adds detail to a
|
|
168
|
+
* message; it must never become a reason to fail.
|
|
169
|
+
*/
|
|
170
|
+
async function probePortOwner() {
|
|
171
|
+
try {
|
|
172
|
+
const config = await getJson('/api/config');
|
|
173
|
+
return typeof config?.projectRoot === 'string' ? config.projectRoot : null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The last `lines` lines of the orchestrator log — where the reason a boot died
|
|
180
|
+
// (EADDRINUSE, a bad config) is written, seconds before the CLI notices.
|
|
181
|
+
function tailLog(logFile, lines = 15) {
|
|
182
|
+
try {
|
|
183
|
+
const content = fs.readFileSync(logFile, 'utf8');
|
|
184
|
+
return content.split('\n').filter(Boolean).slice(-lines).join('\n');
|
|
185
|
+
} catch {
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Report a port held by a different project's orchestrator. Returns true when
|
|
191
|
+
// it printed something, so callers can skip their own vaguer message.
|
|
192
|
+
function reportPortOwner(owner, port, log = console.log) {
|
|
193
|
+
if (!owner) return false;
|
|
194
|
+
const here = process.cwd();
|
|
195
|
+
const mine = owner === here || here.startsWith(`${owner}${path.sep}`);
|
|
196
|
+
log(mine
|
|
197
|
+
? t('status.portAnsweringHere', { port })
|
|
198
|
+
: t('status.portOwnedByOther', { port, root: owner }));
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function startOrchestrator() {
|
|
203
|
+
// Before anything else — including the already-running short-circuit — so a
|
|
204
|
+
// stale 0.x flag or config value always fails loudly instead of looking like
|
|
205
|
+
// a successful no-op.
|
|
206
|
+
|
|
145
207
|
const { pidFile, logFile } = runtimePaths();
|
|
146
208
|
|
|
147
209
|
if (fs.existsSync(pidFile)) {
|
|
@@ -150,15 +212,11 @@ function startOrchestrator() {
|
|
|
150
212
|
process.kill(pid, 0);
|
|
151
213
|
const config = loadConfig();
|
|
152
214
|
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
|
-
}
|
|
215
|
+
console.log(t('start.already', { pid }));
|
|
216
|
+
console.log(t('start.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
217
|
+
console.log(t('start.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
160
218
|
if (cfg.enableDynamoProxy) {
|
|
161
|
-
console.log(
|
|
219
|
+
console.log(t('start.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
162
220
|
}
|
|
163
221
|
return;
|
|
164
222
|
} catch (e) {
|
|
@@ -169,12 +227,12 @@ function startOrchestrator() {
|
|
|
169
227
|
const orchestratorPath = getOrchestratorPath();
|
|
170
228
|
|
|
171
229
|
if (!orchestratorPath) {
|
|
172
|
-
console.error('
|
|
230
|
+
console.error(t('start.notBuilt'));
|
|
173
231
|
console.error('');
|
|
174
|
-
console.error('
|
|
232
|
+
console.error(t('start.notBuiltDev'));
|
|
175
233
|
console.error(' cd /path/to/local-serverless-stack && npm run build');
|
|
176
234
|
console.error('');
|
|
177
|
-
console.error('
|
|
235
|
+
console.error(t('start.notBuiltBug'));
|
|
178
236
|
process.exit(1);
|
|
179
237
|
}
|
|
180
238
|
|
|
@@ -184,33 +242,14 @@ function startOrchestrator() {
|
|
|
184
242
|
const config = loadConfig();
|
|
185
243
|
const cfg = getConfig(config);
|
|
186
244
|
|
|
187
|
-
// Check for flags
|
|
188
245
|
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
246
|
|
|
204
247
|
// Build environment variables from config
|
|
205
248
|
const env = { ...process.env };
|
|
206
|
-
/* istanbul ignore else: getConfig() always defaults serverPort
|
|
249
|
+
/* istanbul ignore else: getConfig() always defaults serverPort, so the else is unreachable */
|
|
207
250
|
if (cfg.serverPort) {
|
|
208
251
|
env.PORT = cfg.serverPort;
|
|
209
252
|
}
|
|
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
253
|
if (enableDynamoProxy) {
|
|
215
254
|
env.LSS_ENABLE_DYNAMO_PROXY = 'true';
|
|
216
255
|
}
|
|
@@ -218,30 +257,9 @@ function startOrchestrator() {
|
|
|
218
257
|
if (cfg.dynamoProxyPort) {
|
|
219
258
|
env.LSS_DYNAMO_PROXY_PORT = cfg.dynamoProxyPort;
|
|
220
259
|
}
|
|
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
260
|
// Hand the same config file to the server so its ConfigManager reads the
|
|
243
|
-
// identical serverPort/
|
|
244
|
-
//
|
|
261
|
+
// identical serverPort/seedsDir/region (not just the hand-translated subset
|
|
262
|
+
// above). Keeps the two config loaders in agreement.
|
|
245
263
|
if (EXPLICIT_CONFIG) {
|
|
246
264
|
env.LSS_CONFIG_PATH = EXPLICIT_CONFIG;
|
|
247
265
|
}
|
|
@@ -257,29 +275,96 @@ function startOrchestrator() {
|
|
|
257
275
|
fs.closeSync(logFd);
|
|
258
276
|
fs.writeFileSync(pidFile, child.pid.toString());
|
|
259
277
|
|
|
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
|
-
}
|
|
278
|
+
console.log(t('start.started', { pid: child.pid }));
|
|
279
|
+
console.log(t('start.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
280
|
+
console.log(t('start.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
267
281
|
if (enableDynamoProxy) {
|
|
268
|
-
console.log(
|
|
282
|
+
console.log(t('start.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
283
|
+
}
|
|
284
|
+
console.log(t('start.logs', { path: logFile }));
|
|
285
|
+
|
|
286
|
+
// `--wait` blocks until the orchestrator actually answers, which is what a
|
|
287
|
+
// sequential chain (a VS Code task, a CI step, a boot script) needs: without
|
|
288
|
+
// it the next step's `register` fails fifteen times for a reason that is not
|
|
289
|
+
// the cause — the cause is in the first line of the log, minutes earlier.
|
|
290
|
+
if (process.argv.includes('--wait') || getArgValue('--wait') !== undefined) {
|
|
291
|
+
const seconds = Number.parseInt(getArgValue('--wait') || '', 10);
|
|
292
|
+
return waitForHealthy(child.pid, pidFile, logFile, Number.isInteger(seconds) && seconds > 0 ? seconds : 30);
|
|
269
293
|
}
|
|
270
|
-
console.log('📝 Logs:', logFile);
|
|
271
294
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
// Without --wait the CLI still has to answer honestly. Daemonizing means the
|
|
296
|
+
// child discovers a busy port AFTER this process has printed a PID, so the
|
|
297
|
+
// liveness check below is the only place a non-zero exit can come from — and
|
|
298
|
+
// it used to print the failure and exit 0 anyway, making a dead orchestrator
|
|
299
|
+
// count as a successful step.
|
|
300
|
+
return scheduleLivenessCheck(child.pid, pidFile, logFile);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Two seconds later: is the child still there? Resolves when it is, exits
|
|
304
|
+
// non-zero when it is not. Returned as a promise so the caller (and the tests)
|
|
305
|
+
// can observe the outcome instead of it happening in a detached timer.
|
|
306
|
+
function scheduleLivenessCheck(pid, pidFile, logFile) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
try {
|
|
310
|
+
process.kill(pid, 0);
|
|
311
|
+
} catch (e) {
|
|
312
|
+
// process.exit never returns in production; under test it throws, and
|
|
313
|
+
// the rejection is how a test asserts the exit code.
|
|
314
|
+
reportStartFailure(pidFile, logFile).then(() => process.exit(1)).then(resolve, reject);
|
|
315
|
+
return;
|
|
280
316
|
}
|
|
317
|
+
console.log(t('start.running'));
|
|
318
|
+
resolve();
|
|
319
|
+
}, 2000);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Everything known about a boot that did not survive: the log tail, and whether
|
|
324
|
+
// the port it wanted is held by another project's orchestrator.
|
|
325
|
+
async function reportStartFailure(pidFile, logFile) {
|
|
326
|
+
console.error(t('start.failed', { path: logFile }));
|
|
327
|
+
if (fs.existsSync(pidFile)) {
|
|
328
|
+
fs.unlinkSync(pidFile);
|
|
329
|
+
}
|
|
330
|
+
const tail = tailLog(logFile);
|
|
331
|
+
if (tail) {
|
|
332
|
+
console.error(t('start.logTail'));
|
|
333
|
+
console.error(tail);
|
|
334
|
+
}
|
|
335
|
+
reportPortOwner(await probePortOwner(), getServerPort(), console.error);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Poll `GET /api/health` until the orchestrator answers, the child dies, or the
|
|
340
|
+
* deadline passes. Resolves on success; exits non-zero (after printing the log
|
|
341
|
+
* tail) on either failure — a `lss start --wait` that returns 0 means the stack
|
|
342
|
+
* is up and can be registered against.
|
|
343
|
+
*/
|
|
344
|
+
async function waitForHealthy(pid, pidFile, logFile, timeoutSeconds) {
|
|
345
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
346
|
+
console.log(t('start.waiting', { seconds: timeoutSeconds }));
|
|
347
|
+
for (;;) {
|
|
348
|
+
try {
|
|
349
|
+
process.kill(pid, 0);
|
|
350
|
+
} catch {
|
|
351
|
+
await reportStartFailure(pidFile, logFile);
|
|
352
|
+
process.exit(1);
|
|
281
353
|
}
|
|
282
|
-
|
|
354
|
+
try {
|
|
355
|
+
await getJson('/api/health');
|
|
356
|
+
console.log(t('start.running'));
|
|
357
|
+
return;
|
|
358
|
+
} catch {
|
|
359
|
+
// Not up yet — the connection is refused until the listener binds.
|
|
360
|
+
}
|
|
361
|
+
if (Date.now() >= deadline) {
|
|
362
|
+
console.error(t('start.waitTimeout', { seconds: timeoutSeconds }));
|
|
363
|
+
await reportStartFailure(pidFile, logFile);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
367
|
+
}
|
|
283
368
|
}
|
|
284
369
|
|
|
285
370
|
// Poll kill(pid, 0) until the process is gone. SIGTERM alone isn't enough to
|
|
@@ -318,7 +403,7 @@ function waitForExit(pid, timeoutMs = 10000, intervalMs = 200) {
|
|
|
318
403
|
async function stopOrchestrator() {
|
|
319
404
|
const { pidFile } = runtimePaths();
|
|
320
405
|
if (!fs.existsSync(pidFile)) {
|
|
321
|
-
console.log('
|
|
406
|
+
console.log(t('stop.notRunning'));
|
|
322
407
|
return;
|
|
323
408
|
}
|
|
324
409
|
|
|
@@ -327,7 +412,7 @@ async function stopOrchestrator() {
|
|
|
327
412
|
try {
|
|
328
413
|
process.kill(pid, 'SIGTERM');
|
|
329
414
|
} catch (e) {
|
|
330
|
-
console.error('
|
|
415
|
+
console.error(t('stop.failed', { error: e.message }));
|
|
331
416
|
if (fs.existsSync(pidFile)) {
|
|
332
417
|
fs.unlinkSync(pidFile);
|
|
333
418
|
}
|
|
@@ -337,16 +422,29 @@ async function stopOrchestrator() {
|
|
|
337
422
|
const exited = await waitForExit(pid);
|
|
338
423
|
fs.unlinkSync(pidFile);
|
|
339
424
|
if (exited) {
|
|
340
|
-
console.log('
|
|
425
|
+
console.log(t('stop.stopped', { pid }));
|
|
341
426
|
} else {
|
|
342
|
-
console.warn(
|
|
427
|
+
console.warn(t('stop.timeout', { pid }));
|
|
343
428
|
}
|
|
344
429
|
}
|
|
345
430
|
|
|
346
|
-
|
|
431
|
+
/**
|
|
432
|
+
* `lss status` — is THIS project's orchestrator running?
|
|
433
|
+
*
|
|
434
|
+
* The pid file answers that, and it used to answer nothing else: with
|
|
435
|
+
* `--net=host` the port belongs to the whole machine, so another project's
|
|
436
|
+
* orchestrator holding it produced `NOT RUNNING` from a port that answers 200 —
|
|
437
|
+
* a status that is not merely unhelpful but points the operator at the wrong
|
|
438
|
+
* place. Every "no" is now followed by asking WHO is on the port, which
|
|
439
|
+
* `GET /api/config` answers exactly (`projectRoot`).
|
|
440
|
+
*/
|
|
441
|
+
async function showStatus() {
|
|
347
442
|
const { pidFile, logFile } = runtimePaths();
|
|
443
|
+
const cfg = getConfig(loadConfig());
|
|
348
444
|
if (!fs.existsSync(pidFile)) {
|
|
349
|
-
|
|
445
|
+
if (!reportPortOwner(await probePortOwner(), cfg.serverPort)) {
|
|
446
|
+
console.log(t('status.notRunning'));
|
|
447
|
+
}
|
|
350
448
|
return;
|
|
351
449
|
}
|
|
352
450
|
|
|
@@ -354,18 +452,17 @@ function showStatus() {
|
|
|
354
452
|
|
|
355
453
|
try {
|
|
356
454
|
process.kill(pid, 0);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log('
|
|
360
|
-
console.log(`📊 Server: http://localhost:${cfg.serverPort}`);
|
|
361
|
-
console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort}`);
|
|
455
|
+
console.log(t('status.running', { pid }));
|
|
456
|
+
console.log(t('status.server', { url: `http://localhost:${cfg.serverPort}` }));
|
|
457
|
+
console.log(t('status.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
362
458
|
if (cfg.enableDynamoProxy) {
|
|
363
|
-
console.log(
|
|
459
|
+
console.log(t('status.dynamoProxy', { url: `http://localhost:${cfg.dynamoProxyPort}` }));
|
|
364
460
|
}
|
|
365
|
-
console.log('
|
|
461
|
+
console.log(t('status.logs', { path: logFile }));
|
|
366
462
|
} catch (e) {
|
|
367
|
-
console.log('
|
|
463
|
+
console.log(t('status.stale'));
|
|
368
464
|
fs.unlinkSync(pidFile);
|
|
465
|
+
reportPortOwner(await probePortOwner(), cfg.serverPort);
|
|
369
466
|
}
|
|
370
467
|
}
|
|
371
468
|
|
|
@@ -379,13 +476,13 @@ function getServerPort() {
|
|
|
379
476
|
// orchestrator startup window) — fall back through every signal we have so
|
|
380
477
|
// `formatError` is guaranteed not to return an empty string.
|
|
381
478
|
function formatError(e) {
|
|
382
|
-
if (!e) return '
|
|
383
|
-
if (typeof e === 'string') return e.trim() || '
|
|
479
|
+
if (!e) return t('error.unknown');
|
|
480
|
+
if (typeof e === 'string') return e.trim() || t('error.unknown');
|
|
384
481
|
if (e.message && String(e.message).trim()) return String(e.message).trim();
|
|
385
|
-
if (e.code) return
|
|
482
|
+
if (e.code) return t('error.io', { code: e.code });
|
|
386
483
|
if (e.name) return e.name;
|
|
387
484
|
const s = String(e);
|
|
388
|
-
return s && s !== '[object Object]' ? s : '
|
|
485
|
+
return s && s !== '[object Object]' ? s : t('error.unknown');
|
|
389
486
|
}
|
|
390
487
|
|
|
391
488
|
function buildHttpError(res, data) {
|
|
@@ -395,7 +492,9 @@ function buildHttpError(res, data) {
|
|
|
395
492
|
if (fromBody && String(fromBody).trim()) return new Error(String(fromBody).trim());
|
|
396
493
|
const snippet = data && data.length < 300 ? data.trim() : '';
|
|
397
494
|
const statusText = res.statusMessage ? `${res.statusCode} ${res.statusMessage}` : `${res.statusCode}`;
|
|
398
|
-
|
|
495
|
+
// `HTTP <status>: <snippet>` is pure wire detail, so only the "no body at
|
|
496
|
+
// all" variant carries translatable prose.
|
|
497
|
+
return new Error(snippet ? `HTTP ${statusText}: ${snippet}` : t('error.httpNoBody', { status: statusText }));
|
|
399
498
|
}
|
|
400
499
|
|
|
401
500
|
function postJson(path, body) {
|
|
@@ -427,7 +526,7 @@ function postJson(path, body) {
|
|
|
427
526
|
});
|
|
428
527
|
},
|
|
429
528
|
);
|
|
430
|
-
req.on('error', err => reject(err && err.message ? err : new Error(
|
|
529
|
+
req.on('error', err => reject(err && err.message ? err : new Error(t('error.httpConnection', { error: formatError(err) }))));
|
|
431
530
|
req.write(payload);
|
|
432
531
|
req.end();
|
|
433
532
|
});
|
|
@@ -436,7 +535,7 @@ function postJson(path, body) {
|
|
|
436
535
|
function ensureRunningOrExit() {
|
|
437
536
|
const { pidFile } = runtimePaths();
|
|
438
537
|
if (!fs.existsSync(pidFile)) {
|
|
439
|
-
console.error('
|
|
538
|
+
console.error(t('error.notRunning'));
|
|
440
539
|
process.exit(1);
|
|
441
540
|
}
|
|
442
541
|
}
|
|
@@ -445,19 +544,19 @@ function printSeedRunResults(results) {
|
|
|
445
544
|
let missingTables = 0;
|
|
446
545
|
for (const r of results) {
|
|
447
546
|
if (r.skipped) {
|
|
448
|
-
console.log(
|
|
449
|
-
if (r.reason && r.reason.includes('does not exist in
|
|
547
|
+
console.log(t('seed.skipped', { table: r.tableName, reason: r.reason }));
|
|
548
|
+
if (r.reason && r.reason.includes('does not exist in the engine')) {
|
|
450
549
|
missingTables++;
|
|
451
550
|
}
|
|
452
551
|
} else {
|
|
453
|
-
console.log(
|
|
552
|
+
console.log(t('seed.inserted', { table: r.tableName, count: r.inserted }));
|
|
454
553
|
}
|
|
455
554
|
}
|
|
456
555
|
return { missingTables };
|
|
457
556
|
}
|
|
458
557
|
|
|
459
558
|
// Show the user both sides of the comparison: the seed files we found, and
|
|
460
|
-
// the DynamoDB tables actually living in
|
|
559
|
+
// the DynamoDB tables actually living in the engine. This is the difference
|
|
461
560
|
// between "I didn't deploy yet" (no live tables at all) and "my seed file
|
|
462
561
|
// name doesn't match the CFN TableName" (live tables exist, just not those).
|
|
463
562
|
function printSeedMismatchDiagnostic({ entries, liveTables, focusTable }) {
|
|
@@ -466,37 +565,39 @@ function printSeedMismatchDiagnostic({ entries, liveTables, focusTable }) {
|
|
|
466
565
|
|
|
467
566
|
console.log('');
|
|
468
567
|
if (focusTable) {
|
|
469
|
-
console.log(
|
|
568
|
+
console.log(t('seed.diagFile', { table: focusTable }));
|
|
470
569
|
} else if (focusList.length > 0) {
|
|
471
|
-
console.log('
|
|
570
|
+
console.log(t('seed.diagFiles'));
|
|
472
571
|
for (const name of focusList) console.log(` - ${name}`);
|
|
473
572
|
} else {
|
|
474
|
-
console.log('
|
|
573
|
+
console.log(t('seed.diagNoFiles'));
|
|
475
574
|
}
|
|
476
575
|
|
|
477
576
|
if (liveTables && liveTables.length > 0) {
|
|
478
577
|
console.log('');
|
|
479
|
-
console.log(
|
|
578
|
+
console.log(t('seed.diagLiveTables', { count: liveTables.length }));
|
|
480
579
|
for (const name of liveTables) console.log(` - ${name}`);
|
|
481
580
|
console.log('');
|
|
482
|
-
console.log('
|
|
483
|
-
console.log('
|
|
581
|
+
console.log(t('seed.diagMatchHint'));
|
|
582
|
+
console.log(t('seed.diagPrefixHint'));
|
|
484
583
|
} else {
|
|
485
584
|
console.log('');
|
|
486
|
-
console.log('
|
|
487
|
-
console.log('
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
console.log(
|
|
585
|
+
console.log(t('seed.diagNoLiveTables'));
|
|
586
|
+
console.log(t('seed.diagNotProvisioned'));
|
|
587
|
+
// The commands themselves are never translated — only the comment that
|
|
588
|
+
// explains what each one buys you.
|
|
589
|
+
console.log(` npx lss start # ${t('seed.diagStepStart')}`);
|
|
590
|
+
console.log(` npx serverless deploy # ${t('seed.diagStepDeploy')}`);
|
|
591
|
+
console.log(t('seed.diagRetry'));
|
|
491
592
|
}
|
|
492
593
|
}
|
|
493
594
|
|
|
494
595
|
function printSeedClearResults(results) {
|
|
495
596
|
for (const r of results) {
|
|
496
597
|
if (r.skipped) {
|
|
497
|
-
console.log(
|
|
598
|
+
console.log(t('seed.skipped', { table: r.tableName, reason: r.reason }));
|
|
498
599
|
} else {
|
|
499
|
-
console.log(
|
|
600
|
+
console.log(t('seed.deleted', { table: r.tableName, count: r.deleted }));
|
|
500
601
|
}
|
|
501
602
|
}
|
|
502
603
|
}
|
|
@@ -504,7 +605,7 @@ function printSeedClearResults(results) {
|
|
|
504
605
|
async function runSeed(tableName) {
|
|
505
606
|
ensureRunningOrExit();
|
|
506
607
|
try {
|
|
507
|
-
console.log(tableName ?
|
|
608
|
+
console.log(tableName ? t('seed.runningTable', { table: tableName }) : t('seed.running'));
|
|
508
609
|
const res = await postJson('/api/seeds/run', tableName ? { tableName } : {});
|
|
509
610
|
const { missingTables } = printSeedRunResults(res.results || []);
|
|
510
611
|
if (missingTables > 0) {
|
|
@@ -517,11 +618,11 @@ async function runSeed(tableName) {
|
|
|
517
618
|
});
|
|
518
619
|
} catch (diagErr) {
|
|
519
620
|
// Diagnostic is best-effort; don't fail the seed because the hint failed.
|
|
520
|
-
console.log(
|
|
621
|
+
console.log(t('seed.diagFailed', { error: formatError(diagErr) }));
|
|
521
622
|
}
|
|
522
623
|
}
|
|
523
624
|
} catch (e) {
|
|
524
|
-
console.error('
|
|
625
|
+
console.error(t('seed.failed'), formatError(e));
|
|
525
626
|
process.exit(1);
|
|
526
627
|
}
|
|
527
628
|
}
|
|
@@ -550,7 +651,7 @@ function getJson(path) {
|
|
|
550
651
|
});
|
|
551
652
|
},
|
|
552
653
|
);
|
|
553
|
-
req.on('error', err => reject(err && err.message ? err : new Error(
|
|
654
|
+
req.on('error', err => reject(err && err.message ? err : new Error(t('error.httpConnection', { error: formatError(err) }))));
|
|
554
655
|
req.end();
|
|
555
656
|
});
|
|
556
657
|
}
|
|
@@ -562,7 +663,7 @@ function promptConfirmation(expectedWord) {
|
|
|
562
663
|
input: process.stdin,
|
|
563
664
|
output: process.stdout,
|
|
564
665
|
});
|
|
565
|
-
rl.question(
|
|
666
|
+
rl.question(t('seed.confirmPrompt', { word: expectedWord }), answer => {
|
|
566
667
|
rl.close();
|
|
567
668
|
resolve(answer.trim() === expectedWord);
|
|
568
669
|
});
|
|
@@ -577,8 +678,8 @@ async function clearSeed(tableName) {
|
|
|
577
678
|
|
|
578
679
|
// Show the user exactly what's about to be wiped before they confirm.
|
|
579
680
|
// Hitting the orchestrator's GET /api/seeds also implicitly proves we're
|
|
580
|
-
// talking to
|
|
581
|
-
// to the configured local
|
|
681
|
+
// talking to the local engine and not AWS — the orchestrator only ever
|
|
682
|
+
// connects to the configured local engine endpoint.
|
|
582
683
|
let scopeDescription;
|
|
583
684
|
try {
|
|
584
685
|
const list = await getJson('/api/seeds');
|
|
@@ -589,138 +690,301 @@ async function clearSeed(tableName) {
|
|
|
589
690
|
|
|
590
691
|
if (liveTargets.length === 0) {
|
|
591
692
|
if (tableName) {
|
|
592
|
-
console.log(
|
|
693
|
+
console.log(t('clear.noSuchTable', { table: tableName }));
|
|
593
694
|
} else if (entries.length === 0) {
|
|
594
|
-
console.log('
|
|
695
|
+
console.log(t('clear.noSeedFiles'));
|
|
595
696
|
} else {
|
|
596
|
-
console.log(
|
|
697
|
+
console.log(t('clear.noneLive', { count: entries.length }));
|
|
597
698
|
}
|
|
598
699
|
printSeedMismatchDiagnostic({ entries, liveTables, focusTable: tableName });
|
|
599
700
|
return;
|
|
600
701
|
}
|
|
601
702
|
|
|
602
703
|
scopeDescription = tableName
|
|
603
|
-
?
|
|
604
|
-
:
|
|
704
|
+
? t('clear.scopeTable', { table: tableName })
|
|
705
|
+
: t('clear.scopeTables', {
|
|
706
|
+
count: liveTargets.length,
|
|
707
|
+
tables: liveTargets.map(e => e.tableName).join(', '),
|
|
708
|
+
});
|
|
605
709
|
|
|
606
710
|
console.log('');
|
|
607
|
-
console.log('
|
|
608
|
-
console.log(
|
|
609
|
-
console.log(
|
|
610
|
-
console.log('
|
|
711
|
+
console.log(t('clear.warning'));
|
|
712
|
+
console.log(t('clear.target', { scope: scopeDescription }));
|
|
713
|
+
console.log(t('clear.engine', { url: `http://localhost:${cfg.selfEnginePort}` }));
|
|
714
|
+
console.log(t('clear.noAws'));
|
|
611
715
|
console.log('');
|
|
612
716
|
} catch (e) {
|
|
613
|
-
console.error('
|
|
717
|
+
console.error(t('clear.listFailed'), formatError(e));
|
|
614
718
|
process.exit(1);
|
|
615
719
|
}
|
|
616
720
|
|
|
617
721
|
if (!skipConfirm) {
|
|
722
|
+
// The word itself is NOT translated: it is a magic token CI scripts and the
|
|
723
|
+
// docs already depend on, so it stays `confirmar` in every locale.
|
|
618
724
|
const ok = await promptConfirmation('confirmar');
|
|
619
725
|
if (!ok) {
|
|
620
|
-
console.log('
|
|
726
|
+
console.log(t('seed.aborted'));
|
|
621
727
|
return;
|
|
622
728
|
}
|
|
623
729
|
} else {
|
|
624
|
-
console.log('
|
|
730
|
+
console.log(t('clear.skipPrompt'));
|
|
625
731
|
}
|
|
626
732
|
|
|
627
733
|
try {
|
|
628
|
-
console.log(tableName ?
|
|
734
|
+
console.log(tableName ? t('seed.clearingTable', { table: tableName }) : t('seed.clearing'));
|
|
629
735
|
const res = await postJson('/api/seeds/clear', tableName ? { tableName } : {});
|
|
630
736
|
printSeedClearResults(res.results || []);
|
|
631
737
|
} catch (e) {
|
|
632
|
-
console.error('
|
|
738
|
+
console.error(t('clear.failed'), formatError(e));
|
|
633
739
|
process.exit(1);
|
|
634
740
|
}
|
|
635
741
|
}
|
|
636
742
|
|
|
743
|
+
/**
|
|
744
|
+
* `lss mcp` — run the Model Context Protocol server on stdio.
|
|
745
|
+
*
|
|
746
|
+
* Started by an MCP client (Claude Code reads .mcp.json), never by a human at a
|
|
747
|
+
* prompt: stdout is the JSON-RPC frame stream, so nothing else may be written
|
|
748
|
+
* there. This process only talks HTTP to an already-running orchestrator — it
|
|
749
|
+
* never boots one — so `lss start` has to have happened first.
|
|
750
|
+
*/
|
|
751
|
+
/**
|
|
752
|
+
* `lss register [path...]` — register services with the running orchestrator.
|
|
753
|
+
*
|
|
754
|
+
* This replaces the retired serverless-lss plugin: registration is a plain
|
|
755
|
+
* POST of { servicePath }, and the orchestrator resolves everything else
|
|
756
|
+
* (packaging via autoPackage when needed, then name/region/custom.lss ports
|
|
757
|
+
* from the packaged serverless-state.json).
|
|
758
|
+
*/
|
|
759
|
+
async function registerServices(paths) {
|
|
760
|
+
ensureRunningOrExit();
|
|
761
|
+
const targets = paths.length > 0 ? paths : ['.'];
|
|
762
|
+
let failed = 0;
|
|
763
|
+
for (const target of targets) {
|
|
764
|
+
const servicePath = path.resolve(target);
|
|
765
|
+
if (!fs.existsSync(servicePath)) {
|
|
766
|
+
console.error(t('register.notFound', { target }));
|
|
767
|
+
failed++;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
const result = await postJson('/api/services/register', { servicePath });
|
|
772
|
+
console.log(t('register.ok', {
|
|
773
|
+
name: result.serviceName,
|
|
774
|
+
resources: result.resourcesCount,
|
|
775
|
+
functions: result.functionsCount,
|
|
776
|
+
routes: result.routesCount,
|
|
777
|
+
}));
|
|
778
|
+
// Warnings come from the orchestrator already worded; only the bullet is ours.
|
|
779
|
+
for (const warning of result.warnings || []) {
|
|
780
|
+
console.log(` ⚠ ${warning}`);
|
|
781
|
+
}
|
|
782
|
+
} catch (e) {
|
|
783
|
+
console.error(t('register.failed', { target, error: formatError(e) }));
|
|
784
|
+
failed++;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (failed > 0) {
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* `lss scan` — list every Serverless/osls service found under the project
|
|
794
|
+
* root, with the same flags onboarding shows.
|
|
795
|
+
*
|
|
796
|
+
* The three flags mirror the checklist: `registered` (known to this
|
|
797
|
+
* orchestrator), `installed` (node_modules resolvable, so packaging can run
|
|
798
|
+
* without an install first) and `packaged` (a template already exists). The
|
|
799
|
+
* effective package command gets its own line because it is per-service
|
|
800
|
+
* configurable (`servicePackaging`) and is what the install→package→register
|
|
801
|
+
* buttons would run.
|
|
802
|
+
*/
|
|
803
|
+
/**
|
|
804
|
+
* Render one scan warning. The orchestrator sends a stable `code` plus the
|
|
805
|
+
* English `message` it produced; the code is what gets localised, and the
|
|
806
|
+
* message is the fallback for a code this CLI has not been taught yet — a
|
|
807
|
+
* newer server's warning still prints instead of disappearing.
|
|
808
|
+
*/
|
|
809
|
+
function scanWarningText(warning) {
|
|
810
|
+
if (!warning || typeof warning !== 'object') return String(warning);
|
|
811
|
+
const key = `scan.warning.${warning.code}`;
|
|
812
|
+
const translated = t(key, warning.params);
|
|
813
|
+
return translated === key ? warning.message : translated;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async function scanServices() {
|
|
817
|
+
ensureRunningOrExit();
|
|
818
|
+
try {
|
|
819
|
+
const result = await getJson('/api/services/scan');
|
|
820
|
+
const services = result.services || [];
|
|
821
|
+
if (services.length === 0) {
|
|
822
|
+
console.log(t('scan.none', { root: result.projectRoot }));
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
console.log(`${t('scan.header', { count: services.length, root: result.projectRoot })}\n`);
|
|
826
|
+
for (const svc of services) {
|
|
827
|
+
const flags = [
|
|
828
|
+
svc.registered ? t('scan.registered') : t('scan.notRegistered'),
|
|
829
|
+
svc.installed ? t('scan.installed') : t('scan.notInstalled'),
|
|
830
|
+
svc.packaged ? t('scan.packaged') : t('scan.notPackaged'),
|
|
831
|
+
].join(', ');
|
|
832
|
+
const ports = svc.apiPort ? ` api:${svc.apiPort}${svc.invokePort ? ` invoke:${svc.invokePort}` : ''}` : '';
|
|
833
|
+
console.log(` ${svc.registered ? '✓' : '·'} ${svc.name} (${svc.relPath}) — ${flags}${ports}`);
|
|
834
|
+
if (svc.packageCommand) {
|
|
835
|
+
console.log(` ${t('scan.packageCommand', { command: svc.packageCommand })}`);
|
|
836
|
+
}
|
|
837
|
+
for (const warning of svc.warnings || []) {
|
|
838
|
+
console.log(` ⚠ ${scanWarningText(warning)}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
console.log(t('scan.hint'));
|
|
842
|
+
} catch (e) {
|
|
843
|
+
console.error(t('scan.failed'), formatError(e));
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function getMcpServerPath() {
|
|
849
|
+
const candidates = [
|
|
850
|
+
path.join(__dirname, '../dist/mcp/server.js'),
|
|
851
|
+
path.join(__dirname, '..', 'dist', 'mcp', 'server.js'),
|
|
852
|
+
];
|
|
853
|
+
return candidates.find(candidate => fs.existsSync(candidate)) || null;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function getPackageVersion() {
|
|
857
|
+
try {
|
|
858
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')).version || '0.0.0';
|
|
859
|
+
} catch {
|
|
860
|
+
return '0.0.0';
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function runMcpServer() {
|
|
865
|
+
const serverPath = getMcpServerPath();
|
|
866
|
+
const version = getPackageVersion();
|
|
867
|
+
/* 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 */
|
|
868
|
+
if (!serverPath) {
|
|
869
|
+
console.error(t('mcp.missing'));
|
|
870
|
+
process.exit(1); // never returns
|
|
871
|
+
}
|
|
872
|
+
// Dynamic import: the MCP build is ESM, and loading it only for this command
|
|
873
|
+
// keeps every other CLI invocation free of it.
|
|
874
|
+
/* istanbul ignore next: process entry point — loads the ESM build and binds this process's stdio */
|
|
875
|
+
import(pathToFileURL(serverPath).href)
|
|
876
|
+
.then(mod => mod.main(version))
|
|
877
|
+
.catch(error => {
|
|
878
|
+
console.error(t('mcp.startFailed'), error && error.message ? error.message : error);
|
|
879
|
+
process.exit(1);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// One ` <name> <description>` row of the help screen. The description is
|
|
884
|
+
// wrapped and hanging-indented under itself so a translation can be longer than
|
|
885
|
+
// the English original without shredding the column — the reason the help text
|
|
886
|
+
// is assembled here instead of living in the catalogue as one pre-formatted blob.
|
|
887
|
+
function helpRow(name, description, nameWidth, textWidth) {
|
|
888
|
+
const gutter = ' '.repeat(2 + nameWidth);
|
|
889
|
+
const wrapped = [];
|
|
890
|
+
let current = '';
|
|
891
|
+
for (const word of description.split(' ')) {
|
|
892
|
+
if (current && current.length + 1 + word.length > textWidth) {
|
|
893
|
+
wrapped.push(current);
|
|
894
|
+
current = word;
|
|
895
|
+
} else {
|
|
896
|
+
current = current ? `${current} ${word}` : word;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
wrapped.push(current);
|
|
900
|
+
return [` ${name.padEnd(nameWidth)}${wrapped[0]}`, ...wrapped.slice(1).map(line => gutter + line)]
|
|
901
|
+
.join('\n');
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// A ` npx lss … # what it does` example line. The command is never translated.
|
|
905
|
+
function helpExample(command, comment) {
|
|
906
|
+
return ` ${command.padEnd(43)}# ${comment}`;
|
|
907
|
+
}
|
|
908
|
+
|
|
637
909
|
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
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
683
|
-
"
|
|
684
|
-
"persistence": true,
|
|
685
|
-
"debug": false,
|
|
686
|
-
"stateDir": ".lss"
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
`);
|
|
910
|
+
// Command names, flags, env var names, paths and the JSON template are
|
|
911
|
+
// identifiers — they stay here, untranslated. Only prose goes through t().
|
|
912
|
+
const cmd = (name, key) => helpRow(name, t(key), 19, 56);
|
|
913
|
+
const opt = (name, key) => helpRow(name, t(key), 29, 46);
|
|
914
|
+
|
|
915
|
+
console.log([
|
|
916
|
+
'',
|
|
917
|
+
'Local Serverless Stack (LSS) CLI',
|
|
918
|
+
'',
|
|
919
|
+
t('help.usage'),
|
|
920
|
+
'',
|
|
921
|
+
t('help.commands'),
|
|
922
|
+
cmd('start', 'help.cmd.start'),
|
|
923
|
+
cmd('stop', 'help.cmd.stop'),
|
|
924
|
+
cmd('status', 'help.cmd.status'),
|
|
925
|
+
cmd('logs', 'help.cmd.logs'),
|
|
926
|
+
cmd('seed [table]', 'help.cmd.seed'),
|
|
927
|
+
cmd('scan', 'help.cmd.scan'),
|
|
928
|
+
cmd('register [path...]', 'help.cmd.register'),
|
|
929
|
+
cmd('seed:clear [table]', 'help.cmd.seedClear'),
|
|
930
|
+
cmd('mcp', 'help.cmd.mcp'),
|
|
931
|
+
cmd('help', 'help.cmd.help'),
|
|
932
|
+
'',
|
|
933
|
+
t('help.options'),
|
|
934
|
+
opt('--config <path>', 'help.opt.config'),
|
|
935
|
+
opt('--enable-dynamo-proxy', 'help.opt.dynamoProxy'),
|
|
936
|
+
opt('--wait[=<seconds>]', 'help.opt.wait'),
|
|
937
|
+
opt('--yes, -y', 'help.opt.yes'),
|
|
938
|
+
'',
|
|
939
|
+
t('help.environment'),
|
|
940
|
+
opt('LSS_DASHBOARD_PORT', 'help.env.dashboardPort'),
|
|
941
|
+
opt('LSS_ENGINE_PORT', 'help.env.enginePort'),
|
|
942
|
+
opt('LSS_ENGINE_DATA_DIR', 'help.env.engineDataDir'),
|
|
943
|
+
opt('AWS_REGION', 'help.env.awsRegion'),
|
|
944
|
+
opt('LSS_LANG', 'help.env.lang'),
|
|
945
|
+
'',
|
|
946
|
+
t('help.configuration'),
|
|
947
|
+
` ${t('help.config.intro')}`,
|
|
948
|
+
'',
|
|
949
|
+
` ${t('help.config.example')}`,
|
|
950
|
+
' {',
|
|
951
|
+
' "serverPort": 3100,',
|
|
952
|
+
' "selfEngine": { "port": 14566 },',
|
|
953
|
+
' "enableDynamoProxy": false,',
|
|
954
|
+
' "dynamoProxyPort": 8000,',
|
|
955
|
+
' "region": "us-east-1",',
|
|
956
|
+
' "persistence": true,',
|
|
957
|
+
' "debug": false,',
|
|
958
|
+
' "stateDir": ".lss"',
|
|
959
|
+
' }',
|
|
960
|
+
'',
|
|
961
|
+
helpRow('', t('help.config.stateDir'), 0, 76),
|
|
962
|
+
'',
|
|
963
|
+
t('help.examples'),
|
|
964
|
+
helpExample('npx lss start', t('help.ex.start')),
|
|
965
|
+
helpExample('npx lss start --enable-dynamo-proxy', t('help.ex.startProxy')),
|
|
966
|
+
helpExample('npx lss mcp', t('help.ex.mcp')),
|
|
967
|
+
helpExample('npx lss stop', t('help.ex.stop')),
|
|
968
|
+
helpExample('npx lss status', t('help.ex.status')),
|
|
969
|
+
helpExample('npx lss logs', t('help.ex.logs')),
|
|
970
|
+
helpExample('npx lss scan', t('help.ex.scan')),
|
|
971
|
+
helpExample('npx lss register', t('help.ex.register')),
|
|
972
|
+
helpExample('npx lss seed', t('help.ex.seed')),
|
|
973
|
+
helpExample('npx lss seed users', t('help.ex.seedTable')),
|
|
974
|
+
helpExample('npx lss seed:clear users', t('help.ex.seedClear')),
|
|
975
|
+
helpExample('npx lss seed:clear users --yes', t('help.ex.seedClearYes')),
|
|
976
|
+
'',
|
|
977
|
+
].join('\n'));
|
|
714
978
|
}
|
|
715
979
|
|
|
716
980
|
function showLogs() {
|
|
717
981
|
const { logFile } = runtimePaths();
|
|
718
982
|
if (!fs.existsSync(logFile)) {
|
|
719
|
-
console.log('
|
|
983
|
+
console.log(t('logs.missing', { path: logFile }));
|
|
720
984
|
return;
|
|
721
985
|
}
|
|
722
986
|
|
|
723
|
-
console.log('
|
|
987
|
+
console.log(t('logs.from', { path: logFile }));
|
|
724
988
|
console.log('---');
|
|
725
989
|
const logs = fs.readFileSync(logFile, 'utf8');
|
|
726
990
|
const lines = logs.split('\n');
|
|
@@ -732,11 +996,23 @@ function showLogs() {
|
|
|
732
996
|
// Skip anything that looks like a flag so `seed:clear --yes` doesn't pass
|
|
733
997
|
// "--yes" as the table name.
|
|
734
998
|
function firstPositional() {
|
|
999
|
+
return allPositionals()[0];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Every non-flag argument after the command, skipping flag VALUES too
|
|
1003
|
+
// (`--config <path>` consumes the next token).
|
|
1004
|
+
function allPositionals() {
|
|
1005
|
+
const flagsWithValue = new Set(['--config']);
|
|
1006
|
+
const out = [];
|
|
735
1007
|
for (let i = 3; i < process.argv.length; i++) {
|
|
736
1008
|
const arg = process.argv[i];
|
|
737
|
-
if (
|
|
1009
|
+
if (arg.startsWith('-')) {
|
|
1010
|
+
if (flagsWithValue.has(arg)) i++;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
out.push(arg);
|
|
738
1014
|
}
|
|
739
|
-
return
|
|
1015
|
+
return out;
|
|
740
1016
|
}
|
|
741
1017
|
|
|
742
1018
|
// Exported so the pure/in-process helpers can be unit-tested by requiring this
|
|
@@ -746,6 +1022,12 @@ function firstPositional() {
|
|
|
746
1022
|
module.exports = {
|
|
747
1023
|
loadConfig,
|
|
748
1024
|
getConfig,
|
|
1025
|
+
allPositionals,
|
|
1026
|
+
registerServices,
|
|
1027
|
+
scanServices,
|
|
1028
|
+
getMcpServerPath,
|
|
1029
|
+
getPackageVersion,
|
|
1030
|
+
runMcpServer,
|
|
749
1031
|
getArgValue,
|
|
750
1032
|
runtimePaths,
|
|
751
1033
|
getOrchestratorPath,
|
|
@@ -792,13 +1074,22 @@ if (require.main === module) {
|
|
|
792
1074
|
case 'seed:clear':
|
|
793
1075
|
clearSeed(firstPositional());
|
|
794
1076
|
break;
|
|
1077
|
+
case 'register':
|
|
1078
|
+
registerServices(allPositionals());
|
|
1079
|
+
break;
|
|
1080
|
+
case 'scan':
|
|
1081
|
+
scanServices();
|
|
1082
|
+
break;
|
|
1083
|
+
case 'mcp':
|
|
1084
|
+
runMcpServer();
|
|
1085
|
+
break;
|
|
795
1086
|
case 'help':
|
|
796
1087
|
case '--help':
|
|
797
1088
|
case '-h':
|
|
798
1089
|
showHelp();
|
|
799
1090
|
break;
|
|
800
1091
|
default:
|
|
801
|
-
console.log('
|
|
1092
|
+
console.log(t('help.unknown', { command }));
|
|
802
1093
|
showHelp();
|
|
803
1094
|
process.exit(1);
|
|
804
1095
|
}
|