local-serverless-stack 1.0.0 → 1.1.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 +125 -3
- package/README.md +92 -38
- package/bin/cli.js +218 -28
- package/bin/i18n.js +57 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/namespaces/services.d.ts +46 -0
- package/dist/client/namespaces/services.d.ts.map +1 -1
- package/dist/client/namespaces/services.js +1 -0
- package/dist/client/namespaces/services.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +17 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/engine/backends/self-backend.d.ts +26 -1
- package/dist/server/engine/backends/self-backend.d.ts.map +1 -1
- package/dist/server/engine/backends/self-backend.js +104 -11
- package/dist/server/engine/backends/self-backend.js.map +1 -1
- package/dist/server/engine/emulators/dynamodb/expressions/parser.d.ts.map +1 -1
- package/dist/server/engine/emulators/dynamodb/expressions/parser.js +12 -1
- package/dist/server/engine/emulators/dynamodb/expressions/parser.js.map +1 -1
- package/dist/server/engine/emulators/dynamodb/expressions/reserved-words.d.ts +3 -0
- package/dist/server/engine/emulators/dynamodb/expressions/reserved-words.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/reserved-words.js +78 -0
- package/dist/server/engine/emulators/dynamodb/expressions/reserved-words.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/index.d.ts +11 -0
- package/dist/server/engine/emulators/dynamodb/index.d.ts.map +1 -1
- package/dist/server/engine/emulators/dynamodb/index.js +101 -7
- package/dist/server/engine/emulators/dynamodb/index.js.map +1 -1
- package/dist/server/engine/emulators/dynamodb/query.d.ts +1 -0
- package/dist/server/engine/emulators/dynamodb/query.d.ts.map +1 -1
- package/dist/server/engine/emulators/dynamodb/query.js +34 -6
- package/dist/server/engine/emulators/dynamodb/query.js.map +1 -1
- package/dist/server/engine/emulators/s3/index.d.ts +10 -0
- package/dist/server/engine/emulators/s3/index.d.ts.map +1 -1
- package/dist/server/engine/emulators/s3/index.js +369 -25
- package/dist/server/engine/emulators/s3/index.js.map +1 -1
- package/dist/server/engine/emulators/sqs/index.d.ts +2 -0
- package/dist/server/engine/emulators/sqs/index.d.ts.map +1 -1
- package/dist/server/engine/emulators/sqs/index.js +70 -7
- package/dist/server/engine/emulators/sqs/index.js.map +1 -1
- package/dist/server/engine/emulators/sqs/queue.d.ts +2 -1
- package/dist/server/engine/emulators/sqs/queue.d.ts.map +1 -1
- package/dist/server/engine/emulators/sqs/queue.js +68 -35
- package/dist/server/engine/emulators/sqs/queue.js.map +1 -1
- package/dist/server/engine/emulators/ssm/index.d.ts +31 -0
- package/dist/server/engine/emulators/ssm/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/ssm/index.js +670 -0
- package/dist/server/engine/emulators/ssm/index.js.map +1 -0
- package/dist/server/engine/http/errors.d.ts +1 -0
- package/dist/server/engine/http/errors.d.ts.map +1 -1
- package/dist/server/engine/http/errors.js +13 -5
- package/dist/server/engine/http/errors.js.map +1 -1
- package/dist/server/engine/http/is-aws-request.d.ts.map +1 -1
- package/dist/server/engine/http/is-aws-request.js +13 -3
- package/dist/server/engine/http/is-aws-request.js.map +1 -1
- package/dist/server/engine/http/protocols/aws-json.d.ts +1 -0
- package/dist/server/engine/http/protocols/aws-json.d.ts.map +1 -1
- package/dist/server/engine/http/protocols/aws-json.js +13 -0
- package/dist/server/engine/http/protocols/aws-json.js.map +1 -1
- package/dist/server/engine/http/reset.d.ts +4 -0
- package/dist/server/engine/http/reset.d.ts.map +1 -0
- package/dist/server/engine/http/reset.js +17 -0
- package/dist/server/engine/http/reset.js.map +1 -0
- package/dist/server/engine/http/router.d.ts +1 -0
- package/dist/server/engine/http/router.d.ts.map +1 -1
- package/dist/server/engine/http/router.js +185 -34
- package/dist/server/engine/http/router.js.map +1 -1
- package/dist/server/engine/types.d.ts +2 -1
- package/dist/server/engine/types.d.ts.map +1 -1
- package/dist/server/routes/apis.d.ts.map +1 -1
- package/dist/server/routes/apis.js +6 -0
- package/dist/server/routes/apis.js.map +1 -1
- package/dist/server/routes/config.d.ts.map +1 -1
- package/dist/server/routes/config.js +25 -1
- 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 +12 -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 +66 -5
- package/dist/server/routes/services.js.map +1 -1
- package/dist/server/runtime/runtime-worker.js +40 -3
- package/dist/server/runtime/runtime-worker.js.map +1 -1
- package/dist/server/services/api-gateway-events.d.ts +14 -0
- package/dist/server/services/api-gateway-events.d.ts.map +1 -1
- package/dist/server/services/api-gateway-events.js +114 -8
- package/dist/server/services/api-gateway-events.js.map +1 -1
- package/dist/server/services/artifact-staleness.d.ts +20 -0
- package/dist/server/services/artifact-staleness.d.ts.map +1 -0
- package/dist/server/services/artifact-staleness.js +115 -0
- package/dist/server/services/artifact-staleness.js.map +1 -0
- package/dist/server/services/authorizer-service.d.ts +34 -5
- package/dist/server/services/authorizer-service.d.ts.map +1 -1
- package/dist/server/services/authorizer-service.js +0 -0
- package/dist/server/services/authorizer-service.js.map +1 -1
- package/dist/server/services/cache-manager.d.ts +5 -0
- package/dist/server/services/cache-manager.d.ts.map +1 -1
- package/dist/server/services/cache-manager.js.map +1 -1
- package/dist/server/services/cfn-intrinsics.d.ts +63 -0
- package/dist/server/services/cfn-intrinsics.d.ts.map +1 -0
- package/dist/server/services/cfn-intrinsics.js +403 -0
- package/dist/server/services/cfn-intrinsics.js.map +1 -0
- package/dist/server/services/cfn-physical-values.d.ts +51 -0
- package/dist/server/services/cfn-physical-values.d.ts.map +1 -0
- package/dist/server/services/cfn-physical-values.js +186 -0
- package/dist/server/services/cfn-physical-values.js.map +1 -0
- package/dist/server/services/cloudformation-parser.d.ts +26 -3
- package/dist/server/services/cloudformation-parser.d.ts.map +1 -1
- package/dist/server/services/cloudformation-parser.js +79 -7
- package/dist/server/services/cloudformation-parser.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +49 -2
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +206 -24
- package/dist/server/services/config-manager.js.map +1 -1
- package/dist/server/services/gateway-manager.d.ts.map +1 -1
- package/dist/server/services/gateway-manager.js +52 -20
- package/dist/server/services/gateway-manager.js.map +1 -1
- package/dist/server/services/lambda-runtime-manager.d.ts +2 -0
- package/dist/server/services/lambda-runtime-manager.d.ts.map +1 -1
- package/dist/server/services/lambda-runtime-manager.js +49 -15
- package/dist/server/services/lambda-runtime-manager.js.map +1 -1
- package/dist/server/services/raw-api-assembler.d.ts +1 -1
- package/dist/server/services/raw-api-assembler.d.ts.map +1 -1
- package/dist/server/services/raw-api-assembler.js +18 -8
- package/dist/server/services/raw-api-assembler.js.map +1 -1
- package/dist/server/services/resource-provisioner.d.ts +31 -1
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +257 -25
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/s3-explorer.js +1 -1
- package/dist/server/services/seed-manager.d.ts.map +1 -1
- package/dist/server/services/seed-manager.js +11 -6
- package/dist/server/services/seed-manager.js.map +1 -1
- package/dist/server/services/serverless-state-parser.d.ts +45 -2
- package/dist/server/services/serverless-state-parser.d.ts.map +1 -1
- package/dist/server/services/serverless-state-parser.js +217 -29
- package/dist/server/services/serverless-state-parser.js.map +1 -1
- package/dist/server/services/service-graph.d.ts +76 -0
- package/dist/server/services/service-graph.d.ts.map +1 -0
- package/dist/server/services/service-graph.js +914 -0
- package/dist/server/services/service-graph.js.map +1 -0
- package/dist/server/services/service-registrar.d.ts +17 -1
- package/dist/server/services/service-registrar.d.ts.map +1 -1
- package/dist/server/services/service-registrar.js +190 -66
- package/dist/server/services/service-registrar.js.map +1 -1
- package/dist/server/services/service-scanner.d.ts +7 -2
- package/dist/server/services/service-scanner.d.ts.map +1 -1
- package/dist/server/services/service-scanner.js +53 -7
- package/dist/server/services/service-scanner.js.map +1 -1
- package/dist/server/services/source-watcher.d.ts +1 -0
- package/dist/server/services/source-watcher.d.ts.map +1 -1
- package/dist/server/services/source-watcher.js +19 -1
- package/dist/server/services/source-watcher.js.map +1 -1
- package/dist/server/services/worker-runtime.d.ts +82 -0
- package/dist/server/services/worker-runtime.d.ts.map +1 -0
- package/dist/server/services/worker-runtime.js +303 -0
- package/dist/server/services/worker-runtime.js.map +1 -0
- package/dist/ui/assets/ApisPage-BkW-QZ0X.js +1 -0
- package/dist/ui/assets/BucketDetailPage-CJwTQJh7.js +1 -0
- package/dist/ui/assets/BucketsPage-B1xPVhZC.js +1 -0
- package/dist/ui/assets/DynamoPage-DvbpwZDc.js +1 -0
- package/dist/ui/assets/DynamoTablePage-xn3Lq3so.js +3 -0
- package/dist/ui/assets/LambdaDetailPage-BhUyO77H.js +4 -0
- package/dist/ui/assets/LambdasPage-CUF_4rOH.js +1 -0
- package/dist/ui/assets/OnboardingPage-U49THUzu.js +1 -0
- package/dist/ui/assets/OpenSearchCollectionPage-BJTVNCq4.js +1 -0
- package/dist/ui/assets/OpenSearchPage-4s-egkB9.js +1 -0
- package/dist/ui/assets/OverviewPage-DJeJQktG.js +1 -0
- package/dist/ui/assets/{OverviewPage-C1pBJ8aP.css → OverviewPage-DjQETmb_.css} +1 -1
- package/dist/ui/assets/QueueDetailPage-DWsy5Yt8.js +1 -0
- package/dist/ui/assets/QueuesPage-BZJSqman.js +1 -0
- package/dist/ui/assets/SecretsPage-YC9ua54G.js +1 -0
- package/dist/ui/assets/ServiceDetailPage-Cum5frP4.js +2 -0
- package/dist/ui/assets/ServicesPage-IpaPDuqx.js +2 -0
- package/dist/ui/assets/SettingsPage-BiCRU8O6.js +1 -0
- package/dist/ui/assets/index-BDlrAJhi.css +1 -0
- package/dist/ui/assets/index-BouKSS43.js +38 -0
- package/dist/ui/assets/resourceIcons-DZs28nbs.js +1 -0
- package/dist/ui/index.html +5 -2
- package/package.json +1 -1
- package/dist/ui/assets/ApisPage-BOuimwWz.js +0 -1
- package/dist/ui/assets/BucketDetailPage-vLYwhoQt.js +0 -1
- package/dist/ui/assets/BucketsPage-Cu5lU1Hp.js +0 -1
- package/dist/ui/assets/DynamoPage-y3TTEPUL.js +0 -1
- package/dist/ui/assets/DynamoTablePage-BnmSRZjc.js +0 -3
- package/dist/ui/assets/LambdaDetailPage-B5q_d27t.js +0 -4
- package/dist/ui/assets/LambdasPage-CJpRoEDx.js +0 -1
- package/dist/ui/assets/OnboardingPage-D02dymQi.js +0 -1
- package/dist/ui/assets/OpenSearchCollectionPage-B_wWTlfL.js +0 -1
- package/dist/ui/assets/OpenSearchPage-BLEgfF_g.js +0 -1
- package/dist/ui/assets/OverviewPage-EdJWiAt6.js +0 -1
- package/dist/ui/assets/QueueDetailPage-ByEYHvHe.js +0 -1
- package/dist/ui/assets/QueuesPage-CvwYHSll.js +0 -1
- package/dist/ui/assets/SecretsPage-DASw8Ntf.js +0 -1
- package/dist/ui/assets/ServiceDetailPage-BMsUDg6L.js +0 -2
- package/dist/ui/assets/ServicesPage-2JuiIiRr.js +0 -2
- package/dist/ui/assets/SettingsPage-DStO4bgN.js +0 -1
- package/dist/ui/assets/index-BIEznsi4.js +0 -37
- package/dist/ui/assets/index-BRMvNP3Y.css +0 -1
- package/dist/ui/assets/resourceIcons-baecADXA.js +0 -1
|
@@ -7,6 +7,7 @@ export interface SelfEngineConfig {
|
|
|
7
7
|
memoryBudgetMb?: number;
|
|
8
8
|
fsync?: boolean;
|
|
9
9
|
fallbackEndpoint?: string | null;
|
|
10
|
+
fallbackEndpoints?: Record<string, string>;
|
|
10
11
|
}
|
|
11
12
|
export interface ResolvedSelfEngineConfig {
|
|
12
13
|
port: number;
|
|
@@ -16,6 +17,7 @@ export interface ResolvedSelfEngineConfig {
|
|
|
16
17
|
memoryBudgetMb: number;
|
|
17
18
|
fsync: boolean;
|
|
18
19
|
fallbackEndpoint: string | null;
|
|
20
|
+
fallbackEndpoints: Record<string, string>;
|
|
19
21
|
persistence: boolean;
|
|
20
22
|
region: string;
|
|
21
23
|
}
|
|
@@ -24,12 +26,14 @@ export interface ServicePackageConfig {
|
|
|
24
26
|
packageArgs?: string[];
|
|
25
27
|
packageEnv?: Record<string, string>;
|
|
26
28
|
packageTimeoutMs?: number;
|
|
29
|
+
packageCwd?: string;
|
|
27
30
|
}
|
|
28
31
|
export interface ResolvedPackageConfig {
|
|
29
32
|
command: string;
|
|
30
33
|
args: string[];
|
|
31
34
|
env: Record<string, string>;
|
|
32
35
|
timeoutMs: number;
|
|
36
|
+
cwd: string;
|
|
33
37
|
}
|
|
34
38
|
export type LambdaExecutionMode = 'auto' | 'artifact' | 'source';
|
|
35
39
|
export interface LambdaRuntimeConfig {
|
|
@@ -48,6 +52,18 @@ export interface ServiceRuntimeConfig {
|
|
|
48
52
|
invokePort?: number;
|
|
49
53
|
execution?: LambdaExecutionMode;
|
|
50
54
|
watch?: boolean;
|
|
55
|
+
env?: Record<string, string>;
|
|
56
|
+
execArgv?: string[];
|
|
57
|
+
functions?: Record<string, {
|
|
58
|
+
env?: Record<string, string>;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
export interface ResolvedWorkerOverrides {
|
|
62
|
+
env: Record<string, string>;
|
|
63
|
+
execArgv: string[];
|
|
64
|
+
functions: Record<string, {
|
|
65
|
+
env?: Record<string, string>;
|
|
66
|
+
}>;
|
|
51
67
|
}
|
|
52
68
|
export interface ResolvedRuntimeConfig {
|
|
53
69
|
enabled: boolean;
|
|
@@ -97,6 +113,7 @@ export interface LSSConfig {
|
|
|
97
113
|
packageEnv?: Record<string, string>;
|
|
98
114
|
servicePackaging?: Record<string, ServicePackageConfig>;
|
|
99
115
|
packageTimeoutMs?: number;
|
|
116
|
+
scanIgnore?: string[];
|
|
100
117
|
lambdaRuntime?: LambdaRuntimeConfig;
|
|
101
118
|
serviceRuntime?: Record<string, ServiceRuntimeConfig>;
|
|
102
119
|
selfEngine?: SelfEngineConfig;
|
|
@@ -144,6 +161,28 @@ export declare class ConfigManager {
|
|
|
144
161
|
* as the global baseline.
|
|
145
162
|
*/
|
|
146
163
|
getPackageConfigForService(servicePath: string): ResolvedPackageConfig;
|
|
164
|
+
/**
|
|
165
|
+
* Where a package command runs. `packageCwd` is resolved against the PROJECT
|
|
166
|
+
* ROOT (not the service dir — the point of the key is to reach a directory
|
|
167
|
+
* above the service) and fenced to it with the same realpath comparison the
|
|
168
|
+
* /install and /package endpoints apply to `servicePath`: this value is the
|
|
169
|
+
* cwd of a spawned child, so an unfenced `../..` would walk the packaging
|
|
170
|
+
* command out of the project entirely. A rejected value falls back to the
|
|
171
|
+
* service directory and says so, rather than silently packaging elsewhere.
|
|
172
|
+
*/
|
|
173
|
+
private resolvePackageCwd;
|
|
174
|
+
/**
|
|
175
|
+
* Stacks the scan must not offer. A monorepo always holds a few that are
|
|
176
|
+
* never local by decision — a bootstrap stack deployed once per AWS account,
|
|
177
|
+
* a us-east-1 DNS/certificate stack the engine does not emulate — and they
|
|
178
|
+
* came back as pending work, with warnings, on every single scan. Permanent
|
|
179
|
+
* noise is how a real warning goes unread.
|
|
180
|
+
*
|
|
181
|
+
* Matched with the same key spellings as `servicePackaging`/`serviceRuntime`
|
|
182
|
+
* (path relative to the config file's directory or to the project root, or
|
|
183
|
+
* the directory basename), so one convention covers all three.
|
|
184
|
+
*/
|
|
185
|
+
isScanIgnored(servicePath: string): boolean;
|
|
147
186
|
/**
|
|
148
187
|
* The keys a `servicePackaging`/`serviceRuntime` entry may be written under,
|
|
149
188
|
* most specific first: the path relative to the config file's directory, the
|
|
@@ -174,13 +213,21 @@ export declare class ConfigManager {
|
|
|
174
213
|
* (true for source, false for artifact).
|
|
175
214
|
*/
|
|
176
215
|
getRuntimeConfigForService(servicePath: string): ResolvedRuntimeConfig;
|
|
216
|
+
/**
|
|
217
|
+
* The worker-process overrides for one service: `serviceRuntime.<svc>.env`,
|
|
218
|
+
* `.execArgv` and `.functions`. Looked up with the same key spellings as the
|
|
219
|
+
* rest of the entry. Never throws on a malformed hand-edited value — a
|
|
220
|
+
* non-object env or non-array execArgv reads as absent.
|
|
221
|
+
*/
|
|
222
|
+
getWorkerOverridesForService(servicePath: string): ResolvedWorkerOverrides;
|
|
177
223
|
getSelfEngineConfig(): ResolvedSelfEngineConfig;
|
|
178
224
|
private homeStateDir;
|
|
179
225
|
getEngineEndpoint(): string;
|
|
226
|
+
private getSelfEnginePort;
|
|
180
227
|
/**
|
|
181
228
|
* True when the orchestrator and the engine share one listener — the default,
|
|
182
|
-
* since
|
|
183
|
-
* go back to two listeners.
|
|
229
|
+
* since the engine port follows serverPort. Give `selfEngine.port` a
|
|
230
|
+
* different value to go back to two listeners.
|
|
184
231
|
*/
|
|
185
232
|
isSingleListener(): boolean;
|
|
186
233
|
getConfigPath(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../../../src/server/services/config-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../../../src/server/services/config-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { projectCacheSegment } from './project-scope.js';
|
|
5
|
+
import { execArgvError } from './worker-runtime.js';
|
|
5
6
|
// Symlink-stable spelling of a directory (e.g. macOS /tmp -> /private/tmp),
|
|
6
7
|
// so the project identity matches the plugin's symlink-resolved process.cwd().
|
|
7
8
|
function realpathOrSelf(dir) {
|
|
@@ -226,9 +227,11 @@ const EDITABLE_CONFIG_KEYS = {
|
|
|
226
227
|
packageArgs: { kind: 'packageArgs' },
|
|
227
228
|
packageEnv: { kind: 'envRecord' },
|
|
228
229
|
packageTimeoutMs: { kind: 'positiveInt' },
|
|
230
|
+
packageCwd: { kind: 'projectRelativePath' },
|
|
229
231
|
},
|
|
230
232
|
},
|
|
231
233
|
packageTimeoutMs: { kind: 'positiveInt' },
|
|
234
|
+
scanIgnore: { kind: 'stringArray' },
|
|
232
235
|
lambdaRuntime: {
|
|
233
236
|
kind: 'object',
|
|
234
237
|
subKeys: {
|
|
@@ -250,6 +253,16 @@ const EDITABLE_CONFIG_KEYS = {
|
|
|
250
253
|
invokePort: { kind: 'port' },
|
|
251
254
|
execution: { kind: 'string', enum: ['auto', 'artifact', 'source'] },
|
|
252
255
|
watch: { kind: 'boolean' },
|
|
256
|
+
// Same fence as packageEnv: the map lands in a forked worker's
|
|
257
|
+
// environment, where NODE_OPTIONS & co. load code of the caller's choosing.
|
|
258
|
+
env: { kind: 'envRecord' },
|
|
259
|
+
execArgv: { kind: 'execArgv' },
|
|
260
|
+
functions: {
|
|
261
|
+
kind: 'object',
|
|
262
|
+
entrySubKeys: {
|
|
263
|
+
env: { kind: 'envRecord' },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
253
266
|
},
|
|
254
267
|
},
|
|
255
268
|
selfEngine: {
|
|
@@ -262,6 +275,7 @@ const EDITABLE_CONFIG_KEYS = {
|
|
|
262
275
|
memoryBudgetMb: { kind: 'positiveInt' },
|
|
263
276
|
fsync: { kind: 'boolean' },
|
|
264
277
|
fallbackEndpoint: { kind: 'string' },
|
|
278
|
+
fallbackEndpoints: { kind: 'stringRecord' },
|
|
265
279
|
},
|
|
266
280
|
},
|
|
267
281
|
branding: {
|
|
@@ -303,7 +317,7 @@ const CONFIG_ENV_OVERRIDES = {
|
|
|
303
317
|
packageCommand: ['LSS_PACKAGE_COMMAND'],
|
|
304
318
|
packageTimeoutMs: ['LSS_PACKAGE_TIMEOUT_MS'],
|
|
305
319
|
lambdaRuntime: ['LSS_LAMBDA_RUNTIME', 'LSS_LAMBDA_EXECUTION', 'LSS_LAMBDA_WATCH', 'LSS_INVOKE_HOST'],
|
|
306
|
-
selfEngine: ['LSS_ENGINE_PORT', 'LSS_ENGINE_DATA_DIR'],
|
|
320
|
+
selfEngine: ['LSS_ENGINE_PORT', 'LSS_ENGINE_DATA_DIR', 'LSS_FALLBACK_ENDPOINT'],
|
|
307
321
|
};
|
|
308
322
|
function isPlainObject(value) {
|
|
309
323
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -438,20 +452,53 @@ function validateValue(key, value, spec) {
|
|
|
438
452
|
const index = value.findIndex(isBlockedPackageFlag);
|
|
439
453
|
return index === -1 ? null : blockedFlagError(`${key}[${index}]`, value[index]);
|
|
440
454
|
}
|
|
455
|
+
// A plain list of match patterns (scanIgnore) — never spawned, so the
|
|
456
|
+
// package-args screen above does not apply; it just has to be a list of
|
|
457
|
+
// non-empty strings so the matcher cannot be fed garbage.
|
|
458
|
+
case 'stringArray':
|
|
459
|
+
return Array.isArray(value) && value.every(v => typeof v === 'string' && v.length > 0)
|
|
460
|
+
? null
|
|
461
|
+
: `"${key}" must be an array of non-empty strings`;
|
|
462
|
+
// A directory INSIDE the project, written the way the config file spells
|
|
463
|
+
// every other path: relative, forward slashes. Rejected lexically here
|
|
464
|
+
// (absolute, or any `..` segment) and again by realpath when it is resolved
|
|
465
|
+
// — this one becomes the cwd of a spawned child, so "inside the project"
|
|
466
|
+
// has to hold for the path that is actually used, symlinks included.
|
|
467
|
+
case 'projectRelativePath': {
|
|
468
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
469
|
+
return `"${key}" must be a non-empty string`;
|
|
470
|
+
}
|
|
471
|
+
if (path.isAbsolute(value) || value.split(/[\\/]/).includes('..')) {
|
|
472
|
+
return `"${key}" must be a path inside the project root, written relative to it (no leading "/" and no "..")`;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
// Handed to fork() as the worker's execArgv — an allowlist, because a
|
|
477
|
+
// single `--import`/`--require` is code execution. See execArgvError().
|
|
478
|
+
case 'execArgv':
|
|
479
|
+
return execArgvError(key, value);
|
|
441
480
|
case 'stringRecord':
|
|
481
|
+
return isPlainObject(value) && Object.values(value).every(v => typeof v === 'string')
|
|
482
|
+
? null
|
|
483
|
+
: `"${key}" must be an object of string values`;
|
|
484
|
+
// Written as a PATCH, one variable at a time (mergeEnvRecord), so `null` is
|
|
485
|
+
// a value with a meaning here — "remove this variable" — and not just the
|
|
486
|
+
// top-level "delete the block" spelling. Anything else that is not a string
|
|
487
|
+
// is still refused: the map ends up in a child process's environment.
|
|
442
488
|
case 'envRecord': {
|
|
443
|
-
if (!isPlainObject(value) || !Object.values(value).every(v => typeof v === 'string')) {
|
|
444
|
-
return `"${key}" must be an object of string values`;
|
|
489
|
+
if (!isPlainObject(value) || !Object.values(value).every(v => typeof v === 'string' || v === null)) {
|
|
490
|
+
return `"${key}" must be an object of string values (null removes a key)`;
|
|
445
491
|
}
|
|
446
492
|
// An env map is merged into the packaging child's environment, where a
|
|
447
493
|
// handful of names are loader/interpreter hooks: they run code in the
|
|
448
494
|
// child whatever program the command grammar let through, which would
|
|
449
|
-
// make that grammar decorative.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
495
|
+
// make that grammar decorative. Screened per variable being SET —
|
|
496
|
+
// REMOVING one of those names only ever takes capability away, and is the
|
|
497
|
+
// one way the dashboard can undo a hand-edited config file that carries
|
|
498
|
+
// it (the fence guards the API, not the read path).
|
|
499
|
+
for (const [envKey, envValue] of Object.entries(value)) {
|
|
500
|
+
if (envValue !== null && BLOCKED_PACKAGE_ENV_KEYS.has(envKey.toLowerCase())) {
|
|
501
|
+
return `"${key}.${envKey}" cannot be set — it injects code into the packaging process`;
|
|
455
502
|
}
|
|
456
503
|
}
|
|
457
504
|
return null;
|
|
@@ -502,6 +549,51 @@ function validateValue(key, value, spec) {
|
|
|
502
549
|
}
|
|
503
550
|
}
|
|
504
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Merge an `envRecord` patch over the map already in the file: a string sets or
|
|
554
|
+
* replaces one variable, `null` removes one, and every variable the patch does
|
|
555
|
+
* not name is left exactly as it was.
|
|
556
|
+
*
|
|
557
|
+
* Replacing the whole map — what every other non-`object` key does on write —
|
|
558
|
+
* cannot work for `packageEnv`, because its values are **write-only**:
|
|
559
|
+
* `GET /api/config` reports `packageEnvKeys`, the NAMES alone (see
|
|
560
|
+
* routes/config.ts), so the values never leave this process and a client
|
|
561
|
+
* editing one variable has nothing to resend for its siblings. Under a
|
|
562
|
+
* wholesale replace, saving `{"A":"v"}` would silently delete `B` and `C` —
|
|
563
|
+
* secrets the editor was never shown and cannot recover. So the write is a
|
|
564
|
+
* patch, for the same reason the one-level block merge above exists (a partial
|
|
565
|
+
* edit must not drop what it could not see), one level deeper.
|
|
566
|
+
*
|
|
567
|
+
* A file value that is not an object is replaced rather than merged into,
|
|
568
|
+
* mirroring how a malformed `object` block is treated.
|
|
569
|
+
*/
|
|
570
|
+
function mergeEnvRecord(existing, patch) {
|
|
571
|
+
const merged = isPlainObject(existing) ? { ...existing } : {};
|
|
572
|
+
for (const [envKey, envValue] of Object.entries(patch)) {
|
|
573
|
+
if (envValue === null) {
|
|
574
|
+
delete merged[envKey];
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
merged[envKey] = envValue;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return merged;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Write a merged block back, or drop the key when the merge emptied it — an
|
|
584
|
+
* empty block carries no setting and would only add noise to the file the human
|
|
585
|
+
* reviews. It is load-bearing for a map entry: an empty object is truthy, so a
|
|
586
|
+
* leftover `{}` would shadow a basename-keyed entry for the same service and
|
|
587
|
+
* silently disable that override.
|
|
588
|
+
*/
|
|
589
|
+
function setOrDeleteBlock(target, key, merged) {
|
|
590
|
+
if (Object.keys(merged).length === 0) {
|
|
591
|
+
delete target[key];
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
target[key] = merged;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
505
597
|
// Thrown by updateConfig so the route can answer 400 with every problem at
|
|
506
598
|
// once instead of failing on the first.
|
|
507
599
|
export class ConfigValidationError extends Error {
|
|
@@ -634,6 +726,14 @@ export class ConfigManager {
|
|
|
634
726
|
dataDir: process.env.LSS_ENGINE_DATA_DIR,
|
|
635
727
|
};
|
|
636
728
|
}
|
|
729
|
+
// Point unsupported services at another emulator per run (a CI job that
|
|
730
|
+
// starts one next to LSS) without writing it into the shared config file.
|
|
731
|
+
if (process.env.LSS_FALLBACK_ENDPOINT) {
|
|
732
|
+
this.config.selfEngine = {
|
|
733
|
+
...this.config.selfEngine,
|
|
734
|
+
fallbackEndpoint: process.env.LSS_FALLBACK_ENDPOINT,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
637
737
|
}
|
|
638
738
|
getConfig() {
|
|
639
739
|
return this.config;
|
|
@@ -711,8 +811,51 @@ export class ConfigManager {
|
|
|
711
811
|
args: [...(this.config.packageArgs ?? []), ...(perService.packageArgs ?? [])],
|
|
712
812
|
env: { ...(this.config.packageEnv ?? {}), ...(perService.packageEnv ?? {}) },
|
|
713
813
|
timeoutMs: perService.packageTimeoutMs ?? this.getPackageTimeoutMs(),
|
|
814
|
+
cwd: this.resolvePackageCwd(perService.packageCwd, servicePath),
|
|
714
815
|
};
|
|
715
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Where a package command runs. `packageCwd` is resolved against the PROJECT
|
|
819
|
+
* ROOT (not the service dir — the point of the key is to reach a directory
|
|
820
|
+
* above the service) and fenced to it with the same realpath comparison the
|
|
821
|
+
* /install and /package endpoints apply to `servicePath`: this value is the
|
|
822
|
+
* cwd of a spawned child, so an unfenced `../..` would walk the packaging
|
|
823
|
+
* command out of the project entirely. A rejected value falls back to the
|
|
824
|
+
* service directory and says so, rather than silently packaging elsewhere.
|
|
825
|
+
*/
|
|
826
|
+
resolvePackageCwd(packageCwd, servicePath) {
|
|
827
|
+
const serviceDir = path.resolve(servicePath);
|
|
828
|
+
if (!packageCwd)
|
|
829
|
+
return serviceDir;
|
|
830
|
+
const projectRoot = this.getProjectRoot();
|
|
831
|
+
const resolved = path.resolve(projectRoot, packageCwd);
|
|
832
|
+
const realRoot = realpathOrSelf(projectRoot);
|
|
833
|
+
const realTarget = realpathOrSelf(resolved);
|
|
834
|
+
const relative = path.relative(realRoot, realTarget);
|
|
835
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
836
|
+
console.warn(`⚠️ Ignoring packageCwd "${packageCwd}" for ${serviceDir}: it resolves outside the project root (${projectRoot}).`);
|
|
837
|
+
return serviceDir;
|
|
838
|
+
}
|
|
839
|
+
return resolved;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Stacks the scan must not offer. A monorepo always holds a few that are
|
|
843
|
+
* never local by decision — a bootstrap stack deployed once per AWS account,
|
|
844
|
+
* a us-east-1 DNS/certificate stack the engine does not emulate — and they
|
|
845
|
+
* came back as pending work, with warnings, on every single scan. Permanent
|
|
846
|
+
* noise is how a real warning goes unread.
|
|
847
|
+
*
|
|
848
|
+
* Matched with the same key spellings as `servicePackaging`/`serviceRuntime`
|
|
849
|
+
* (path relative to the config file's directory or to the project root, or
|
|
850
|
+
* the directory basename), so one convention covers all three.
|
|
851
|
+
*/
|
|
852
|
+
isScanIgnored(servicePath) {
|
|
853
|
+
const ignore = this.config.scanIgnore;
|
|
854
|
+
if (!ignore || ignore.length === 0)
|
|
855
|
+
return false;
|
|
856
|
+
const patterns = new Set(ignore.map(entry => entry.replace(/^\.\//, '').replace(/\/+$/, '')));
|
|
857
|
+
return this.serviceEntryKeys(servicePath).some(key => patterns.has(key));
|
|
858
|
+
}
|
|
716
859
|
/**
|
|
717
860
|
* The keys a `servicePackaging`/`serviceRuntime` entry may be written under,
|
|
718
861
|
* most specific first: the path relative to the config file's directory, the
|
|
@@ -804,19 +947,34 @@ export class ConfigManager {
|
|
|
804
947
|
invokePort: perService.invokePort,
|
|
805
948
|
};
|
|
806
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* The worker-process overrides for one service: `serviceRuntime.<svc>.env`,
|
|
952
|
+
* `.execArgv` and `.functions`. Looked up with the same key spellings as the
|
|
953
|
+
* rest of the entry. Never throws on a malformed hand-edited value — a
|
|
954
|
+
* non-object env or non-array execArgv reads as absent.
|
|
955
|
+
*/
|
|
956
|
+
getWorkerOverridesForService(servicePath) {
|
|
957
|
+
const perService = this.lookupServiceEntry(this.config.serviceRuntime, servicePath);
|
|
958
|
+
return {
|
|
959
|
+
env: isPlainObject(perService.env) ? { ...perService.env } : {},
|
|
960
|
+
execArgv: Array.isArray(perService.execArgv) ? [...perService.execArgv] : [],
|
|
961
|
+
functions: isPlainObject(perService.functions) ? { ...perService.functions } : {},
|
|
962
|
+
};
|
|
963
|
+
}
|
|
807
964
|
getSelfEngineConfig() {
|
|
808
965
|
const selfEngine = this.config.selfEngine ?? {};
|
|
809
966
|
const stateDir = this.getStateDir();
|
|
810
967
|
const rawDataDir = selfEngine.dataDir
|
|
811
968
|
?? (stateDir ? path.join(stateDir, 'engine') : this.homeStateDir('engine'));
|
|
812
969
|
return {
|
|
813
|
-
port:
|
|
970
|
+
port: this.getSelfEnginePort(),
|
|
814
971
|
dataDir: path.isAbsolute(rawDataDir) ? rawDataDir : path.resolve(process.cwd(), rawDataDir),
|
|
815
972
|
account: selfEngine.account ?? '000000000000',
|
|
816
973
|
idleUnloadMs: selfEngine.idleUnloadMs ?? 300000,
|
|
817
974
|
memoryBudgetMb: selfEngine.memoryBudgetMb ?? 128,
|
|
818
975
|
fsync: selfEngine.fsync ?? false,
|
|
819
976
|
fallbackEndpoint: selfEngine.fallbackEndpoint ?? null,
|
|
977
|
+
fallbackEndpoints: selfEngine.fallbackEndpoints ?? {},
|
|
820
978
|
persistence: this.isPersistence(),
|
|
821
979
|
region: this.getRegion(),
|
|
822
980
|
};
|
|
@@ -833,15 +991,23 @@ export class ConfigManager {
|
|
|
833
991
|
// which would also resolve the data dir — needless work for a URL, and it
|
|
834
992
|
// drags the project-root lookup into every caller that only wants an endpoint.
|
|
835
993
|
getEngineEndpoint() {
|
|
836
|
-
return `http://localhost:${this.
|
|
994
|
+
return `http://localhost:${this.getSelfEnginePort()}`;
|
|
995
|
+
}
|
|
996
|
+
// The engine port follows serverPort unless set explicitly. It used to
|
|
997
|
+
// default to 14566 on its own, so `LSS_DASHBOARD_PORT=3100` alone (or a
|
|
998
|
+
// config with only `serverPort`) silently split the stack: the dashboard
|
|
999
|
+
// moved to 3100 and a second listener still opened 14566 — colliding with
|
|
1000
|
+
// the instance that a second stack was meant to run beside.
|
|
1001
|
+
getSelfEnginePort() {
|
|
1002
|
+
return this.config.selfEngine?.port ?? this.getServerPort();
|
|
837
1003
|
}
|
|
838
1004
|
/**
|
|
839
1005
|
* True when the orchestrator and the engine share one listener — the default,
|
|
840
|
-
* since
|
|
841
|
-
* go back to two listeners.
|
|
1006
|
+
* since the engine port follows serverPort. Give `selfEngine.port` a
|
|
1007
|
+
* different value to go back to two listeners.
|
|
842
1008
|
*/
|
|
843
1009
|
isSingleListener() {
|
|
844
|
-
return this.getServerPort() ===
|
|
1010
|
+
return this.getServerPort() === this.getSelfEnginePort();
|
|
845
1011
|
}
|
|
846
1012
|
getConfigPath() {
|
|
847
1013
|
return this.configPath;
|
|
@@ -931,13 +1097,16 @@ export class ConfigManager {
|
|
|
931
1097
|
// Only the block's OWN settings: region/persistence are tracked under
|
|
932
1098
|
// their own keys above, so one change never flags two entries.
|
|
933
1099
|
selfEngine: {
|
|
934
|
-
|
|
1100
|
+
// The explicit value, not the resolved one: an unset engine port
|
|
1101
|
+
// follows serverPort, and that change is already tracked there.
|
|
1102
|
+
port: this.config.selfEngine?.port,
|
|
935
1103
|
dataDir: selfEngine.dataDir,
|
|
936
1104
|
account: selfEngine.account,
|
|
937
1105
|
idleUnloadMs: selfEngine.idleUnloadMs,
|
|
938
1106
|
memoryBudgetMb: selfEngine.memoryBudgetMb,
|
|
939
1107
|
fsync: selfEngine.fsync,
|
|
940
1108
|
fallbackEndpoint: selfEngine.fallbackEndpoint,
|
|
1109
|
+
fallbackEndpoints: selfEngine.fallbackEndpoints,
|
|
941
1110
|
},
|
|
942
1111
|
};
|
|
943
1112
|
}
|
|
@@ -1028,16 +1197,23 @@ export class ConfigManager {
|
|
|
1028
1197
|
}
|
|
1029
1198
|
}
|
|
1030
1199
|
for (const [key, value] of Object.entries(patch)) {
|
|
1200
|
+
const spec = EDITABLE_CONFIG_KEYS[key];
|
|
1031
1201
|
if (value === null) {
|
|
1032
1202
|
delete fileConfig[key];
|
|
1033
1203
|
}
|
|
1034
|
-
else if (
|
|
1204
|
+
else if (spec.kind === 'envRecord') {
|
|
1205
|
+
// Per-variable patch instead of a replace: these values are write-only,
|
|
1206
|
+
// so the client cannot resend the siblings it was never shown and a
|
|
1207
|
+
// replace would delete them. See mergeEnvRecord.
|
|
1208
|
+
setOrDeleteBlock(fileConfig, key, mergeEnvRecord(fileConfig[key], value));
|
|
1209
|
+
}
|
|
1210
|
+
else if (spec.kind === 'object') {
|
|
1035
1211
|
// One-level merge so a partial block edit (e.g. branding.title) never
|
|
1036
1212
|
// drops sibling settings the UI does not round-trip (branding.logo).
|
|
1037
1213
|
// Map-shaped blocks (serviceRuntime/servicePackaging) merge one level
|
|
1038
1214
|
// deeper for the same reason: onboarding sending { apiPort } for one
|
|
1039
1215
|
// service must not drop that entry's execution/packageEnv siblings.
|
|
1040
|
-
const
|
|
1216
|
+
const entrySubKeys = spec.entrySubKeys;
|
|
1041
1217
|
const existing = isPlainObject(fileConfig[key])
|
|
1042
1218
|
? fileConfig[key]
|
|
1043
1219
|
: {};
|
|
@@ -1046,7 +1222,7 @@ export class ConfigManager {
|
|
|
1046
1222
|
if (subValue === null) {
|
|
1047
1223
|
delete merged[subKey];
|
|
1048
1224
|
}
|
|
1049
|
-
else if (
|
|
1225
|
+
else if (entrySubKeys && isPlainObject(subValue)) {
|
|
1050
1226
|
const entryExisting = isPlainObject(merged[subKey])
|
|
1051
1227
|
? merged[subKey]
|
|
1052
1228
|
: {};
|
|
@@ -1055,6 +1231,14 @@ export class ConfigManager {
|
|
|
1055
1231
|
if (entryValue === null) {
|
|
1056
1232
|
delete entryMerged[entryKey];
|
|
1057
1233
|
}
|
|
1234
|
+
else if (entrySubKeys[entryKey].kind === 'envRecord') {
|
|
1235
|
+
// servicePackaging[*].packageEnv is as write-only as the global
|
|
1236
|
+
// map (redactPackaging() in routes/config.ts collapses it the
|
|
1237
|
+
// same way), so it patches per variable too. Validation already
|
|
1238
|
+
// rejected any subkey that is not in entrySubKeys, so the spec
|
|
1239
|
+
// lookup here always lands on a declared one.
|
|
1240
|
+
setOrDeleteBlock(entryMerged, entryKey, mergeEnvRecord(entryMerged[entryKey], entryValue));
|
|
1241
|
+
}
|
|
1058
1242
|
else {
|
|
1059
1243
|
entryMerged[entryKey] = entryValue;
|
|
1060
1244
|
}
|
|
@@ -1062,12 +1246,7 @@ export class ConfigManager {
|
|
|
1062
1246
|
// An entry left with no settings carries no meaning and would
|
|
1063
1247
|
// shadow a basename-keyed entry for the same service (an empty
|
|
1064
1248
|
// object is truthy), so it is dropped rather than written.
|
|
1065
|
-
|
|
1066
|
-
delete merged[subKey];
|
|
1067
|
-
}
|
|
1068
|
-
else {
|
|
1069
|
-
merged[subKey] = entryMerged;
|
|
1070
|
-
}
|
|
1249
|
+
setOrDeleteBlock(merged, subKey, entryMerged);
|
|
1071
1250
|
}
|
|
1072
1251
|
else {
|
|
1073
1252
|
merged[subKey] = subValue;
|
|
@@ -1100,6 +1279,9 @@ export class ConfigManager {
|
|
|
1100
1279
|
const selfEngine = this.getSelfEngineConfig();
|
|
1101
1280
|
console.log(` Self Engine Port: ${selfEngine.port} (http://localhost:${selfEngine.port})`);
|
|
1102
1281
|
console.log(` Self Engine Data Dir: ${selfEngine.dataDir}`);
|
|
1282
|
+
for (const [service, endpoint] of Object.entries(selfEngine.fallbackEndpoints)) {
|
|
1283
|
+
console.log(` Self Engine Fallback (${service}): ${endpoint}`);
|
|
1284
|
+
}
|
|
1103
1285
|
if (selfEngine.fallbackEndpoint) {
|
|
1104
1286
|
console.log(` Self Engine Fallback: ${selfEngine.fallbackEndpoint}`);
|
|
1105
1287
|
}
|