@lensmcp/cluster 1.23.13 → 1.24.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/build-host.d.ts +1 -0
- package/build-host.js +1 -0
- package/create-webpack-dev.d.ts +74 -40
- package/create-webpack-dev.js +1 -1
- package/dev-devtool.d.ts +45 -0
- package/dev-devtool.js +1 -0
- package/devserver-notifier.d.ts +57 -0
- package/devserver-notifier.js +1 -0
- package/executors/gateway/body-stall.d.ts +13 -1
- package/executors/gateway/body-stall.js +1 -1
- package/executors/gateway/canonical-path.d.ts +1 -1
- package/executors/gateway/client-ip.d.ts +1 -1
- package/executors/gateway/gateway.impl.js +1 -1
- package/executors/gateway/jwt-claims.d.ts +1 -1
- package/executors/gateway/prod-config.d.ts +1 -1
- package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
- package/executors/gateway/rate-limit.d.ts +1 -1
- package/executors/gateway/runtime/auth.d.ts +1 -1
- package/executors/gateway/runtime/chooser.d.ts +2 -2
- package/executors/gateway/runtime/control.d.ts +3 -3
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/dev-auth.d.ts +1 -1
- package/executors/gateway/runtime/edge.d.ts +3 -3
- package/executors/gateway/runtime/guest-graph.d.ts +113 -0
- package/executors/gateway/runtime/guest-graph.js +2 -0
- package/executors/gateway/runtime/lens-children.d.ts +11 -1
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +54 -8
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/process-names.d.ts +18 -0
- package/executors/gateway/runtime/process-names.js +1 -0
- package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
- package/executors/gateway/runtime/project-graph-store.js +1 -0
- package/executors/gateway/runtime/proxy.d.ts +2 -2
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/scope.d.ts +1 -1
- package/executors/gateway/runtime/serve-launch.d.ts +118 -0
- package/executors/gateway/runtime/serve-launch.js +3 -0
- package/executors/gateway/runtime/server.d.ts +3 -3
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/shared-build.d.ts +65 -0
- package/executors/gateway/runtime/shared-build.js +4 -0
- package/executors/gateway/runtime/types.d.ts +35 -9
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/workspace-log.js +1 -1
- package/executors/gateway/schema.d.ts +3 -0
- package/executors/gateway/schema.json +4 -0
- package/executors/serve/schema.d.ts +2 -0
- package/executors/serve/schema.json +4 -0
- package/executors/serve/serve.impl.d.ts +12 -6
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/hostname.d.ts +1 -1
- package/exit-reason.d.ts +1 -1
- package/fork-ts-check-reporter.d.ts +12 -0
- package/fork-ts-check-reporter.js +1 -1
- package/graph-snapshot.d.ts +1 -0
- package/graph-snapshot.js +1 -0
- package/heap-snapshot.d.ts +66 -0
- package/heap-snapshot.js +1 -0
- package/hot-swap-registries.d.ts +1 -1
- package/inspector-secret.d.ts +39 -0
- package/inspector-secret.js +1 -0
- package/main.devserver.d.ts +33 -5
- package/main.devserver.js +11 -9
- package/package.json +4 -4
- package/perf/heavy-barrel-import.d.ts +135 -0
- package/perf/heavy-barrel-import.js +1 -0
- package/perf/perf-config.d.ts +29 -0
- package/perf/perf-config.js +1 -0
- package/perf/perf-rule-events.d.ts +71 -0
- package/perf/perf-rule-events.js +2 -0
- package/perf/perf-rules-analysis.d.ts +28 -0
- package/perf/perf-rules-analysis.js +1 -0
- package/perf/perf-rules-plugin.d.ts +71 -0
- package/perf/perf-rules-plugin.js +1 -0
- package/perf/perf-rules-worker.d.ts +1 -0
- package/perf/perf-rules-worker.js +1 -0
- package/perf/source-scan.d.ts +59 -0
- package/perf/source-scan.js +5 -0
- package/perf/workspace-libraries.d.ts +52 -0
- package/perf/workspace-libraries.js +2 -0
- package/pod-memory-events.d.ts +71 -0
- package/pod-memory-events.js +2 -0
- package/pod-memory.d.ts +258 -0
- package/pod-memory.js +1 -0
- package/process-name.d.ts +45 -0
- package/process-name.js +1 -1
- package/serve-runner.d.ts +2 -0
- package/serve-runner.js +1 -0
- package/shared-build/attach.d.ts +56 -0
- package/shared-build/attach.js +3 -0
- package/shared-build/host.d.ts +52 -0
- package/shared-build/host.js +4 -0
- package/shared-build/protocol.d.ts +118 -0
- package/shared-build/protocol.js +1 -0
- package/shared-build/shared-compilation.d.ts +91 -0
- package/shared-build/shared-compilation.js +3 -0
- package/shared-build/signature.d.ts +52 -0
- package/shared-build/signature.js +1 -0
- package/stdio-guard.d.ts +1 -1
- package/swap-recycle.d.ts +21 -0
- package/swap-recycle.js +1 -1
- package/tsgo-check-plugin.d.ts +1 -1
- package/workspace-paths.d.ts +76 -0
- package/workspace-paths.js +1 -0
|
@@ -14,6 +14,7 @@ import type { AuthPolicy, Attrs } from '../manifest';
|
|
|
14
14
|
import type { GatewayErrorReason } from '../gateway-errors';
|
|
15
15
|
import type { RouteRegistry } from './route-registry';
|
|
16
16
|
import type { PodFaults } from './pod-output';
|
|
17
|
+
import type { ProjectGraphStore, ProjectGraphStores } from './project-graph-store';
|
|
17
18
|
export type { Attrs };
|
|
18
19
|
export interface GatewayContext {
|
|
19
20
|
root: string;
|
|
@@ -25,6 +26,10 @@ export interface GatewayContext {
|
|
|
25
26
|
};
|
|
26
27
|
/** Nx project-graph adjacency, populated by the executor wrapper when available. */
|
|
27
28
|
projectDependencies?: Record<string, string[]>;
|
|
29
|
+
/** The workspace's project graph, kept current by the executor wrapper — what lets the gateway start a
|
|
30
|
+
* service's serve task without an `nx run` client (serve-launch.ts). Absent ⇒ every service starts
|
|
31
|
+
* through `nx run`, as before. */
|
|
32
|
+
projectGraphStore?: ProjectGraphStore;
|
|
28
33
|
}
|
|
29
34
|
/** Per-project declaration: top-level `cluster` field in project.json (`davnx` legacy). */
|
|
30
35
|
export interface ClusterProjectDecl {
|
|
@@ -145,7 +150,7 @@ export interface SourceSetChange {
|
|
|
145
150
|
* COMPLETION, but the fs event happened anywhere after the PREVIOUS scan started — so the window is
|
|
146
151
|
* "now − previous scan start" (+ the settle margin), not a fixed fraction of the scan period. The old
|
|
147
152
|
* fixed `min(scanPeriod, 15s) + settle` = 23s against a 30s period falsely read a heard add as `missed`
|
|
148
|
-
* whenever the event fell in the first 7+ seconds of the period (issues/dev-gateway
|
|
153
|
+
* whenever the event fell in the first 7+ seconds of the period (issues/closed/dev-gateway-03).
|
|
149
154
|
*/
|
|
150
155
|
windowMs?: number;
|
|
151
156
|
}
|
|
@@ -227,6 +232,9 @@ export interface ServiceCtl {
|
|
|
227
232
|
rolling?: Promise<boolean>;
|
|
228
233
|
/** The pod GENERATION the current `proc` was spawned as (`LENSMCP_POD_GENERATION`, echoed in `/__lensmcp/alive`). */
|
|
229
234
|
generation?: number;
|
|
235
|
+
/** How the current `proc` was started: the gateway's serve runner, or `nx run` (serve-launch.ts). An `nx run`
|
|
236
|
+
* tree holds nx's task lock for its target, so no second generation can start beside it. */
|
|
237
|
+
launcher?: 'runner' | 'nx';
|
|
230
238
|
lastScaleAt: number;
|
|
231
239
|
spawnAt?: number;
|
|
232
240
|
lastErrors?: string[];
|
|
@@ -390,7 +398,7 @@ export declare const LENS_WATCHER_UNVERIFIED: 'recycle' | 'keep';
|
|
|
390
398
|
* roots to Vite's chokidar. Vite watches only `config.root`; a file outside it is watched one-at-a-time
|
|
391
399
|
* as it is imported, so a NEW file in `shared/` or `web/libs/` never produced an `add` event, the
|
|
392
400
|
* watcher-health endpoint never stamped it, and the verdict above was `missed` BY CONSTRUCTION for every
|
|
393
|
-
* out-of-root add — 16 of 16 rolling handoffs in one foodguard day (issues/dev-gateway
|
|
401
|
+
* out-of-root add — 16 of 16 rolling handoffs in one foodguard day (issues/closed/dev-gateway-02). With the scope
|
|
394
402
|
* watched, the add is heard, the verdict is `heard`, and Vite's own create-handling works for those files
|
|
395
403
|
* too. Set `LENSMCP_LENS_WATCH_SCOPE=0` to hand nothing over (inotify-constrained Linux hosts).
|
|
396
404
|
*/
|
|
@@ -399,7 +407,7 @@ export declare const LENS_WATCH_SCOPE_ENABLED: boolean;
|
|
|
399
407
|
* POD watcher testimony ({@link PodWatcherTestimony}) — the pod-side twin of the two lens knobs above.
|
|
400
408
|
* The stale-source-set signal scaled EVERY pod to zero on EVERY source-set change in its scope — 83 cold
|
|
401
409
|
* recycles of six services in one foodguard day, each a ~30-60s API outage the app rendered as "not
|
|
402
|
-
* connected" (issues/dev-gateway
|
|
410
|
+
* connected" (issues/closed/dev-gateway-07). Now the sweeper reads the pod's testimony first.
|
|
403
411
|
*
|
|
404
412
|
* `LENSMCP_POD_WATCHER_UNVERIFIED` — no testimony at all (a pod on an older `@lensmcp/cluster` bundle,
|
|
405
413
|
* or an unreachable probe). `recycle` (default) is today's behaviour for those pods; `keep` leaves them.
|
|
@@ -414,7 +422,7 @@ export declare const POD_WATCHER_UNVERIFIED: 'recycle' | 'keep';
|
|
|
414
422
|
*/
|
|
415
423
|
export declare const POD_WATCHER_SILENT: 'recycle' | 'keep';
|
|
416
424
|
/**
|
|
417
|
-
* HOW a pod is recycled (stale-source-set / max-age) — issues/dev-gateway
|
|
425
|
+
* HOW a pod is recycled (stale-source-set / max-age) — issues/closed/dev-gateway-09. `rolling` (default): spawn a
|
|
418
426
|
* STANDBY `serve-hmr` generation for the same service, wait until one of its pods answers
|
|
419
427
|
* `/__lensmcp/alive` with the new generation number, then retire the old tree — the pool dir is shared, the
|
|
420
428
|
* devserver's bind-steal hands each socket path to the newer pod, and the gateway's rotation never runs
|
|
@@ -425,6 +433,15 @@ export declare const POD_RECYCLE_MODE: 'rolling' | 'in-place';
|
|
|
425
433
|
* generation kept (a cold nx + webpack compile of a large service can take minutes). Override
|
|
426
434
|
* `LENSMCP_POD_ROLL_READY_MS`. Default 3 min. */
|
|
427
435
|
export declare const POD_ROLL_READY_MS: number;
|
|
436
|
+
/**
|
|
437
|
+
* How many rolling recycles may be IN FLIGHT at once, gateway-wide (issues/closed/dev-gateway-17). Each roll
|
|
438
|
+
* holds a standby `serve-hmr` tree — an nx client, a devserver parent, its pods and a cold webpack compile —
|
|
439
|
+
* beside the live one for up to {@link POD_ROLL_READY_MS}, and a shared-lib edit makes every service stale
|
|
440
|
+
* in the same sweep: uncapped, that doubled the fleet (twelve concurrent cold compiles for foodguard's six
|
|
441
|
+
* eager services). The rest queue and roll as slots free. Override `LENSMCP_POD_MAX_CONCURRENT_ROLLS`;
|
|
442
|
+
* `0` removes the cap. Default 2.
|
|
443
|
+
*/
|
|
444
|
+
export declare const POD_MAX_CONCURRENT_ROLLS: number;
|
|
428
445
|
/** Spawn grace: a child is never probed (and so never recycled) until it has had this long to BIND its port
|
|
429
446
|
* — a cold vite/nx boot in a big monorepo takes many seconds, and killing a still-booting dev server would
|
|
430
447
|
* be a self-inflicted outage. Override `LENSMCP_LENS_PROBE_GRACE_MS`. Default 45s. */
|
|
@@ -518,7 +535,7 @@ export declare const LENS_REQUEST_RECOVERY_WAIT_MS: number;
|
|
|
518
535
|
* Override `LENSMCP_KEEPALIVE_TIMEOUT_MS`. Default 76s. */
|
|
519
536
|
export declare const GATEWAY_KEEPALIVE_TIMEOUT_MS: number;
|
|
520
537
|
/**
|
|
521
|
-
* The HEADER-phase bound, INDEPENDENT of `keepAliveTimeout` (issues/dev-gateway
|
|
538
|
+
* The HEADER-phase bound, INDEPENDENT of `keepAliveTimeout` (issues/closed/dev-gateway-12, the prod-gateway-2 #14
|
|
522
539
|
* twin). The old derivation (`keepAliveTimeout + 5s` = 81s) rested on a premise verified obsolete on Node
|
|
523
540
|
* 24: an idle keep-alive socket is NOT closed by a shorter `headersTimeout` — only a connection that has
|
|
524
541
|
* begun a request and stalled mid-headers is. So a partial-header hold was 81s for no reason; Node's own
|
|
@@ -539,7 +556,7 @@ export declare const GATEWAY_MAX_CONNECTIONS: number;
|
|
|
539
556
|
* 90s). 0 (default) keeps Node's value. Override `LENSMCP_CONN_CHECK_MS`. */
|
|
540
557
|
export declare const GATEWAY_CONN_CHECK_MS: number;
|
|
541
558
|
/**
|
|
542
|
-
* The transfer STALL bound (issues/dev-gateway
|
|
559
|
+
* The transfer STALL bound (issues/closed/dev-gateway-12, the prod-gateway-2 #06 / round-1 #10 twins): a request
|
|
543
560
|
* body or a response that has moved NOTHING for this long is cut, in either direction. A slow-but-moving
|
|
544
561
|
* upload, a draining download and an idle SSE stream (nothing queued) are never candidates. Dev streams
|
|
545
562
|
* long (MCP streamable-HTTP, SSE), so this is a stall bound, never a duration bound. `0` disables.
|
|
@@ -547,7 +564,7 @@ export declare const GATEWAY_CONN_CHECK_MS: number;
|
|
|
547
564
|
*/
|
|
548
565
|
export declare const BODY_STALL_MS: number;
|
|
549
566
|
/**
|
|
550
|
-
* The h2 front door's RST_STREAM budget, PER SESSION (issues/foodguard
|
|
567
|
+
* The h2 front door's RST_STREAM budget, PER SESSION (issues/closed/foodguard-14). nghttp2's Rapid-Reset mitigation
|
|
551
568
|
* (CVE-2023-44487) allows 1000 resets per session, refilling at 33/s, and CLOSES the session (GOAWAY
|
|
552
569
|
* INTERNAL_ERROR) on the next one — with NO 'error' event on the server side. A browser keeps ONE h2 session
|
|
553
570
|
* per origin for the life of the tab and cancels every in-flight stream on each navigation (a Vite dev page:
|
|
@@ -587,7 +604,7 @@ export interface GatewayRuntimeOptions extends GatewayExecutorSchema {
|
|
|
587
604
|
bodyStallMs?: number;
|
|
588
605
|
/** Per-session h2 RST_STREAM budget — burst (`LENSMCP_H2_RESET_BURST`, default 100k) and refill per second
|
|
589
606
|
* (`LENSMCP_H2_RESET_RATE`, default 10k). nghttp2's 1000 / 33 default tears a dev browser's session down
|
|
590
|
-
* after a few navigations (issues/foodguard
|
|
607
|
+
* after a few navigations (issues/closed/foodguard-14). Both must be set for Node to apply either. */
|
|
591
608
|
h2ResetBurst?: number;
|
|
592
609
|
h2ResetRate?: number;
|
|
593
610
|
/** Customer-supplied lifecycle hooks layered on top of the built-in pipeline. */
|
|
@@ -602,6 +619,8 @@ export interface GatewayHandle {
|
|
|
602
619
|
/** The lens-frontend supervisor (rolling handoffs) — `recover(route.lens, reason)` rolls an app on demand.
|
|
603
620
|
* Present when the gateway runs in `rolling` mode; what an operator command or an e2e drives. */
|
|
604
621
|
lensSupervisor?: LensFrontendSupervisor;
|
|
622
|
+
/** The project graphs services are planned from, one per workspace root (guest-graph.ts) — for the spec and `gateway status`. */
|
|
623
|
+
graphStores?: ProjectGraphStores;
|
|
605
624
|
}
|
|
606
625
|
export interface GwTrace {
|
|
607
626
|
startedAt: number;
|
|
@@ -651,7 +670,7 @@ export interface GatewayRuntime {
|
|
|
651
670
|
* workspace that joins a running daemon via `POST /register` adopts its keys here. */
|
|
652
671
|
readonly keyToProject: Map<string, string>;
|
|
653
672
|
/** key → owning workspace slug, the twin of `keyToProject` — so an internal caller can be attributed to
|
|
654
|
-
* `<wsKey>/<project>`, not just a bare project name (issues/dev-gateway
|
|
673
|
+
* `<wsKey>/<project>`, not just a bare project name (issues/closed/dev-gateway-13). Mutable like its twin. */
|
|
655
674
|
readonly keyToWorkspace: Map<string, string>;
|
|
656
675
|
/** The client-address resolver (shared with prod — `../client-ip`), built from options/env at boot. Optional
|
|
657
676
|
* only so a partial runtime in a spec still works: the edge helpers fall back to a peer-only resolver. */
|
|
@@ -684,6 +703,13 @@ export interface GatewayRuntime {
|
|
|
684
703
|
readonly projectRoots?: Readonly<Record<string, string>>;
|
|
685
704
|
/** Project root plus transitive Nx dependency roots. */
|
|
686
705
|
readonly projectSourceRoots?: Readonly<Record<string, string[]>>;
|
|
706
|
+
/** The project graphs the serve runner is planned from, one per workspace root: the daemon's own
|
|
707
|
+
* ({@link GatewayContext.projectGraphStore}) and each registered workspace's (guest-graph.ts). The sweeper keeps
|
|
708
|
+
* each current. Absent when the gateway was started without a graph — every service then starts through `nx run`. */
|
|
709
|
+
readonly graphStores?: ProjectGraphStores;
|
|
710
|
+
/** The gateway's `sharedCompiler` option — one webpack compilation for this workspace's services
|
|
711
|
+
* (shared-build.ts, issues/open/pod-memory-05). `LENSMCP_SHARED_COMPILER` wins over it. */
|
|
712
|
+
readonly sharedCompiler?: boolean;
|
|
687
713
|
/** Managed lens frontend handoff state, installed by the child supervisor. */
|
|
688
714
|
lensSupervisor?: LensFrontendSupervisor;
|
|
689
715
|
/** frontend identity (`workspace:project`) → live HMR sockets, registered by the WS upgrade pipeline. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var M=Object.defineProperty;var e=(_,E)=>M(_,"name",{value:E,configurable:!0});var
|
|
1
|
+
"use strict";var M=Object.defineProperty;var e=(_,E)=>M(_,"name",{value:E,configurable:!0});var O=Object.defineProperty,R=e((_,E)=>O(_,"name",{value:E,configurable:!0}),"R");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_RESET_RATE=exports.GATEWAY_H2_RESET_BURST=exports.BODY_STALL_MS=exports.GATEWAY_CONN_CHECK_MS=exports.GATEWAY_MAX_CONNECTIONS=exports.GATEWAY_STOP_GRACE_MS=exports.GATEWAY_HEADERS_TIMEOUT_MS=exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_REQUEST_RECOVERY_WAIT_MS=exports.LENS_REPLACEMENT_DRAIN_MS=exports.LENS_REPLACEMENT_READY_MS=exports.LENS_RESTART_MODE=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.POD_MAX_CONCURRENT_ROLLS=exports.POD_ROLL_READY_MS=exports.POD_RECYCLE_MODE=exports.POD_WATCHER_SILENT=exports.POD_WATCHER_UNVERIFIED=exports.LENS_WATCH_SCOPE_ENABLED=exports.LENS_WATCHER_UNVERIFIED=exports.LENS_WATCHER_PROBE_ENABLED=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_ALIVE_PATH=exports.POD_ALIVE_PROBE_TIMEOUT_MS=exports.POD_SLOW_FIRST_BYTE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;function envInt(_,E,L=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?L?0:E:S}e(envInt,"envInt"),R(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.POD_SLOW_FIRST_BYTE_MS=envInt("LENSMCP_POD_SLOW_FIRST_BYTE_MS",10*6e4,!0),exports.POD_ALIVE_PROBE_TIMEOUT_MS=envInt("LENSMCP_POD_ALIVE_PROBE_TIMEOUT_MS",1e3),exports.POD_ALIVE_PATH="/__lensmcp/alive",exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_WATCHER_PROBE_ENABLED=process.env.LENSMCP_LENS_WATCHER_PROBE!=="0",exports.LENS_WATCHER_UNVERIFIED=process.env.LENSMCP_LENS_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.LENS_WATCH_SCOPE_ENABLED=process.env.LENSMCP_LENS_WATCH_SCOPE!=="0",exports.POD_WATCHER_UNVERIFIED=process.env.LENSMCP_POD_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.POD_WATCHER_SILENT=process.env.LENSMCP_POD_WATCHER_SILENT==="recycle"?"recycle":"keep",exports.POD_RECYCLE_MODE=process.env.LENSMCP_POD_RECYCLE_MODE==="in-place"?"in-place":"rolling",exports.POD_ROLL_READY_MS=envInt("LENSMCP_POD_ROLL_READY_MS",18e4),exports.POD_MAX_CONCURRENT_ROLLS=envInt("LENSMCP_POD_MAX_CONCURRENT_ROLLS",2,!0),exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.LENS_RESTART_MODE=process.env.LENSMCP_LENS_RESTART_MODE==="in-place"?"in-place":"rolling",exports.LENS_REPLACEMENT_READY_MS=envInt("LENSMCP_LENS_REPLACEMENT_READY_MS",3e4),exports.LENS_REPLACEMENT_DRAIN_MS=envInt("LENSMCP_LENS_REPLACEMENT_DRAIN_MS",2e3,!0),exports.LENS_REQUEST_RECOVERY_WAIT_MS=envInt("LENSMCP_LENS_REQUEST_RECOVERY_WAIT_MS",15e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3),exports.GATEWAY_HEADERS_TIMEOUT_MS=envInt("LENSMCP_HEADERS_TIMEOUT_MS",6e4),exports.GATEWAY_STOP_GRACE_MS=envInt("LENSMCP_STOP_GRACE_MS",3e3),exports.GATEWAY_MAX_CONNECTIONS=envInt("LENSMCP_MAX_CONNECTIONS",0,!0),exports.GATEWAY_CONN_CHECK_MS=envInt("LENSMCP_CONN_CHECK_MS",0,!0),exports.BODY_STALL_MS=envInt("LENSMCP_BODY_STALL_MS",6e4,!0),exports.GATEWAY_H2_RESET_BURST=envInt("LENSMCP_H2_RESET_BURST",1e5),exports.GATEWAY_H2_RESET_RATE=envInt("LENSMCP_H2_RESET_RATE",1e4);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});var s=Object.defineProperty,n=r((e,t)=>s(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.workspaceLogWriter=workspaceLogWriter,exports.closeWorkspaceLogs=closeWorkspaceLogs;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),inspector_secret_1=require("../../../inspector-secret"),fds=new Map,written=new Map,MAX_BYTES=50*1024*1024,CHECK_EVERY=1024*1024;function ensurePrivateDir(e){fs.mkdirSync(e,{recursive:!0,mode:448});try{const t=fs.statSync(e);(t.mode&63)!==0&&(typeof process.getuid!="function"||t.uid===process.getuid())&&fs.chmodSync(e,448)}catch{}}r(ensurePrivateDir,"ensurePrivateDir"),n(ensurePrivateDir,"ensurePrivateDir");function ownerOnly(e){try{fs.chmodSync(e,384)}catch{}}r(ownerOnly,"ownerOnly"),n(ownerOnly,"ownerOnly");function ensureFd(e){const t=fds.get(e);if(t!==void 0)return t;try{ensurePrivateDir(path.dirname(e));const o=fs.openSync(e,"a",384);return ownerOnly(e),fds.set(e,o),written.set(e,0),o}catch{return}}r(ensureFd,"ensureFd"),n(ensureFd,"ensureFd");function closeFd(e){const t=fds.get(e);if(fds.delete(e),written.delete(e),t!==void 0)try{fs.closeSync(t)}catch{}}r(closeFd,"closeFd"),n(closeFd,"closeFd");function rotateIfHuge(e){try{if(fs.statSync(e).size<=MAX_BYTES)return}catch{return}closeFd(e);try{fs.renameSync(e,`${e}.1`)}catch{}ownerOnly(`${e}.1`)}r(rotateIfHuge,"rotateIfHuge"),n(rotateIfHuge,"rotateIfHuge");function workspaceLogWriter(e){const t=path.join(e,".lensmcp","gateway.log");if(ensureFd(t)!==void 0)return o=>{const c=ensureFd(t);if(c===void 0)return;try{fs.writeSync(c,(0,inspector_secret_1.redactInspectorUrls)(o))}catch{closeFd(t);return}const i=(written.get(t)??0)+o.length;i>=CHECK_EVERY?(written.set(t,0),rotateIfHuge(t)):written.set(t,i)}}r(workspaceLogWriter,"workspaceLogWriter"),n(workspaceLogWriter,"workspaceLogWriter");function closeWorkspaceLogs(){for(const e of[...fds.keys()])closeFd(e)}r(closeWorkspaceLogs,"closeWorkspaceLogs"),n(closeWorkspaceLogs,"closeWorkspaceLogs");
|
|
@@ -16,4 +16,7 @@ export interface GatewayExecutorSchema {
|
|
|
16
16
|
dashboardPort?: number;
|
|
17
17
|
/** Override the dashboard hostname. Default lens.<baseDomain>. */
|
|
18
18
|
lensHost?: string;
|
|
19
|
+
/** Compile every service of this workspace in ONE webpack compilation (a build host) instead of one watcher per
|
|
20
|
+
* service. `LENSMCP_SHARED_COMPILER=1|0` wins over it. */
|
|
21
|
+
sharedCompiler?: boolean;
|
|
19
22
|
}
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"lensHost": {
|
|
43
43
|
"type": "string",
|
|
44
44
|
"description": "Override the dashboard's dev hostname. Default lens.<baseDomain>, where <baseDomain> is the longest common dotted suffix of the declared cluster hosts (e.g. lens.tetros.ai.local)."
|
|
45
|
+
},
|
|
46
|
+
"sharedCompiler": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"description": "Compile every service of this workspace in ONE webpack compilation (a build host) instead of one watcher per service: shared libraries are compiled once. LENSMCP_SHARED_COMPILER=1|0 wins over it."
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"additionalProperties": false
|
|
@@ -54,4 +54,6 @@ export interface ServeExecutorSchema {
|
|
|
54
54
|
debugPort?: number;
|
|
55
55
|
/** Tee pod/worker logs (ANSI-stripped) to this file for IDE tailing (WebStorm Logs tab). Env fallback: LENSMCP_LOG_FILE. */
|
|
56
56
|
logFile?: string;
|
|
57
|
+
/** webpack `devtool` for the dev build (default `eval`: no source maps). Env fallback: LENSMCP_DEV_DEVTOOL. */
|
|
58
|
+
devtool?: string;
|
|
57
59
|
}
|
|
@@ -194,6 +194,10 @@
|
|
|
194
194
|
"logFile": {
|
|
195
195
|
"type": "string",
|
|
196
196
|
"description": "Tee every pod/worker log line (ANSI-stripped) to this file, relative to workspace root (e.g. '.lensmcp/logs/my-service.log'). Point an IDE at it — WebStorm: Run/Debug configuration → Logs tab — to see live service logs next to the debugger. Truncated once per serve run. Also settable via the LENSMCP_LOG_FILE env var (this option wins)."
|
|
197
|
+
},
|
|
198
|
+
"devtool": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"description": "webpack devtool for the dev build. Default 'eval': no source maps — every stack frame names its module by absolute path, at the transpiled line, and pods hold one copy of the bundle's text. A map-carrying value (e.g. 'cheap-module-source-map', the cheapest for pods) maps frames to TypeScript lines: ts-loader is told to emit maps and pods run with --enable-source-maps. 'false' = none. Also settable via the LENSMCP_DEV_DEVTOOL env var (this option wins)."
|
|
197
201
|
}
|
|
198
202
|
},
|
|
199
203
|
"required": [
|
|
@@ -2,23 +2,29 @@ import type { ServeExecutorSchema } from './schema';
|
|
|
2
2
|
interface ExecutorContext {
|
|
3
3
|
root: string;
|
|
4
4
|
projectName?: string;
|
|
5
|
+
targetName?: string;
|
|
6
|
+
configurationName?: string;
|
|
5
7
|
projectsConfigurations?: {
|
|
6
8
|
projects: Record<string, {
|
|
7
9
|
root: string;
|
|
8
10
|
}>;
|
|
9
11
|
};
|
|
10
12
|
}
|
|
13
|
+
interface ServeResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}
|
|
11
17
|
/**
|
|
12
18
|
* Dev serve executor — webpack watch mode with integrated devserver.
|
|
13
19
|
*
|
|
14
20
|
* Owns the full devserver lifecycle:
|
|
15
|
-
* 1. Starts webpack in watch mode
|
|
21
|
+
* 1. Starts webpack in watch mode — or, under a gateway running the shared compiler, joins the workspace's ONE
|
|
22
|
+
* compilation (shared-build/attach.ts, issues/open/pod-memory-05) and is told about each build of its bundle
|
|
16
23
|
* 2. On first successful build, forks main.devserver.js with proper env vars
|
|
17
|
-
* 3. On subsequent builds, POSTs /webpack/reload to devserver
|
|
24
|
+
* 3. On subsequent builds, POSTs /webpack/reload to devserver — attached: only when ITS bundle changed
|
|
18
25
|
* 4. On shutdown, cleans up devserver and webpack watcher
|
|
26
|
+
*
|
|
27
|
+
* webpack and the dev config are loaded only when this process compiles: an attached executor holds neither.
|
|
19
28
|
*/
|
|
20
|
-
declare function serveExecutor(options: ServeExecutorSchema, context: ExecutorContext): AsyncGenerator<
|
|
21
|
-
success: boolean;
|
|
22
|
-
baseUrl?: string;
|
|
23
|
-
}>;
|
|
29
|
+
declare function serveExecutor(options: ServeExecutorSchema, context: ExecutorContext): AsyncGenerator<ServeResult>;
|
|
24
30
|
export default serveExecutor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var ne=Object.defineProperty;var c=(e,r)=>ne(e,"name",{value:r,configurable:!0});var te=Object.defineProperty,o=c((e,r)=>te(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),M=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),U=e.configEnv||"development",L=path.join(i,"config",`config.${U}.yaml`),C=3050;let u=C,l=r.projectName,S={};fs.existsSync(L)?(S=yaml.load(fs.readFileSync(L,"utf8")),u=Number(S.port)||u,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${L} \u2014 public devserver port defaults to ${C}. If another service also lacks its config, both bind ${C} \u2192 EADDRINUSE. Add config/config.${U}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const R=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),H=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let O,T;e.gateway?.middleware&&(O=path.resolve(g,e.gateway.middleware),T=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",z=path.resolve(g,K),F=path.resolve(g,Q),Z=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),B={appName:r.projectName,appRoot:g,outputDir:M,main:z,tsConfig:F,assets:e.assets||[],additionalEntryPoints:Z,port:u,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let d=null,$=!1,D=!1,m=!1;const W=path.join(__dirname,"../../main.devserver.js"),q=path.join(__dirname,"../../main.devserver.ts"),V=!fs.existsSync(W)&&fs.existsSync(q),k=V?q:W,ee=path.join(M,"main.js");function I(){d&&!d.killed&&(d.kill("SIGTERM"),d=null)}c(I,"W"),o(I,"cleanup");function y(){m=!0,I()}c(y,"O"),o(y,"stop");const oe=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>y(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",y);function E(){if(m)return;if(!fs.existsSync(k)){console.error(`[serve] devserver script not found at ${k}`);return}const t=["--enable-source-maps"];V&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(k,{cwd:i,env:{...process.env,PORT:String(u),SERVICE_NAME:l,SERVE_PREFIX:R,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:ee,...O&&{GATEWAY_MIDDLEWARE:O},...T&&{GATEWAY_CONFIG:T},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:D?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});d=n,n.on("exit",(a,f)=>{d===n&&(d=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!d&&$&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${u} (service: ${l}${R?`, prefix: /${R}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),H?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${l}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!d||d.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${u}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),I(),E()})}c(re,"oe"),o(re,"_notifyDevServerReload");let N=null;const x=[];function h(t){if(N){const n=N;N=null,n(t)}else x.push(t)}c(h,"p"),o(h,"pushResult");function J(){return x.length>0?Promise.resolve(x.shift()):new Promise(t=>{N=t})}c(J,"Z"),o(J,"waitForNext");let w=o(()=>{},"closeBuild");function A(){return!$||!d?($=!0,E(),!0):!1}c(A,"q"),o(A,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(B),{watch:f,...P}=a,s=n(P);D=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const G=s.watch(a.watchOptions||{},(j,X)=>{if(j){console.error("[serve] Webpack error:",j),h({success:!1});return}const se=X.hasErrors();if(console.info(X.toString({colors:!0,chunks:!1,assets:!0})),se){h({success:!1});return}A(),h({success:!0,baseUrl:`http://localhost:${u}`})});w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"k"),o(b,"compileOnItsOwn");function Y(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(u,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:F,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),D=s.sourceMaps,f.built(s),!s.ok){a.tellWatching(),h({success:!1});return}const G=s.hash!==P;P=s.hash;const j=A();a.built(G&&!j),h({success:!0,baseUrl:`http://localhost:${u}`});return}default:return}})}if(c(Y,"ee"),o(Y,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:B,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?Y(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await J()}finally{y(),w(),oe.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `nx trust` collects host strings from every project's `project.json`, from embedded route options,
|
|
5
5
|
* and from its own `hosts` option, then writes them into `/etc/hosts` as root. Workspace
|
|
6
6
|
* configuration therefore reaches a privileged command, and the only check was "does not contain
|
|
7
|
-
* `*`" (issues/codex-security
|
|
7
|
+
* `*`" (issues/closed/codex-security-33).
|
|
8
8
|
*
|
|
9
9
|
* This is the first of two defences. The second — removing the shell — is what actually closes the
|
|
10
10
|
* injection; this one means a value that could never be a hostname fails loudly at the point it is
|
package/exit-reason.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Why a pod died, in words (issues/foodguard
|
|
2
|
+
* Why a pod died, in words (issues/closed/foodguard-22). "socket hang up" was the only trace a pod left when
|
|
3
3
|
* it died mid-work: a crash, a SIGKILL and an OOM kill read the same, and the stderr that explained
|
|
4
4
|
* which was gone with the process. Pure helpers so the devserver's exit handler stays a few lines.
|
|
5
5
|
*/
|
|
@@ -5,6 +5,18 @@ export interface ForkTsCheckReporterOptions {
|
|
|
5
5
|
/** Override the lens bus file; defaults to `LENSMCP_EVENT_FILE` / `<root>/.lensmcp/events.jsonl`. */
|
|
6
6
|
eventFile?: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Load the SAME module instance `@nx/webpack` loaded.
|
|
10
|
+
*
|
|
11
|
+
* `getCompilerHooks` reads a module-level `WeakMap` keyed by compiler, so hooks registered
|
|
12
|
+
* through a second copy of the package are a different map and never fire. `@nx/webpack` does a
|
|
13
|
+
* bare `require('fork-ts-checker-webpack-plugin')` from its own directory, so resolving from
|
|
14
|
+
* there first is what guarantees we get its copy; the workspace root and our own resolution are
|
|
15
|
+
* fallbacks for a layout that hoists differently.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadForkTsChecker(workspaceRoot: string): {
|
|
18
|
+
getCompilerHooks?: (c: unknown) => unknown;
|
|
19
|
+
} | null;
|
|
8
20
|
export declare class ForkTsCheckReporterPlugin {
|
|
9
21
|
private readonly opts;
|
|
10
22
|
private startedAt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty;var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var o=(e,t)=>p(e,"name",{value:t,configurable:!0});var a=Object.defineProperty,r=o((e,t)=>a(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.ForkTsCheckReporterPlugin=void 0,exports.loadForkTsChecker=loadForkTsChecker;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus");function loadForkTsChecker(e){const t=[];try{t.push(path.dirname(require.resolve("@nx/webpack/package.json",{paths:[e]})))}catch{}t.push(e,__dirname);for(const s of t)try{return require(require.resolve("fork-ts-checker-webpack-plugin",{paths:[s]}))}catch{}return null}o(loadForkTsChecker,"loadForkTsChecker"),r(loadForkTsChecker,"loadForkTsChecker");function toDiagnostic(e,t){return{file:e.file?path.relative(t,e.file)||e.file:"<unknown>",line:e.location?.start?.line??0,column:e.location?.start?.column??0,severity:e.severity==="warning"?"warning":"error",code:/^TS\d+$/.test(e.code)?e.code:`TS${e.code}`,message:e.message}}o(toDiagnostic,"toDiagnostic"),r(toDiagnostic,"toDiagnostic");class ForkTsCheckReporterPlugin{static{o(this,"ForkTsCheckReporterPlugin")}static{r(this,"ForkTsCheckReporterPlugin")}constructor(t){this.startedAt=0,this.opts=t}apply(t){const s=loadForkTsChecker(this.opts.workspaceRoot)?.getCompilerHooks;if(typeof s!="function")return;let c;try{c=s(t)}catch{return}c.start?.tapPromise("LensmcpForkTsCheckReporter",async i=>(this.startedAt=Date.now(),i)),c.issues?.tap("LensmcpForkTsCheckReporter",i=>{try{(0,typecheck_bus_1.publishTypecheckDiagnostics)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"fork-ts-checker",tsConfig:this.opts.tsConfig,diagnostics:(i??[]).map(n=>toDiagnostic(n,this.opts.workspaceRoot)),durationMs:this.startedAt?Date.now()-this.startedAt:0,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}return i})}}exports.ForkTsCheckReporterPlugin=ForkTsCheckReporterPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var i=Object.defineProperty;var o=(r,e)=>i(r,"name",{value:e,configurable:!0});var t=Object.defineProperty,s=o((r,e)=>t(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),guest_graph_1=require("./executors/gateway/runtime/guest-graph");function main(){const r=process.argv[2];r||(console.error("usage: graph-snapshot.js <output file> \u2014 run in the workspace root"),process.exit(2)),(0,guest_graph_1.snapshotWorkspaceGraph)(process.cwd()).then(e=>{fs.writeFileSync(r,JSON.stringify(e),{mode:384}),process.exit(0)},e=>{console.error(`[graph-snapshot] ${e?.message??String(e)}`),process.exit(1)})}o(main,"main"),s(main,"main"),require.main===module&&main();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface HeapSnapshotPolicy {
|
|
2
|
+
/** Refuse above this live heap. */
|
|
3
|
+
maxHeapBytes: number;
|
|
4
|
+
/** Never take one with less than this available on the machine… */
|
|
5
|
+
minFreeBytes: number;
|
|
6
|
+
/** …or less than this many times the live heap (the snapshot graph is built in native memory first). */
|
|
7
|
+
freeFactor: number;
|
|
8
|
+
/** Snapshots kept per directory; older ones are deleted before a new one is written. */
|
|
9
|
+
keep: number;
|
|
10
|
+
/** Snapshots older than this are deleted. */
|
|
11
|
+
maxAgeMs: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* - `LENSMCP_HEAP_SNAPSHOT_MAX_MB` — default `1024`.
|
|
15
|
+
* - `LENSMCP_HEAP_SNAPSHOT_MIN_FREE_MB` — default `1024`.
|
|
16
|
+
* - `LENSMCP_HEAP_SNAPSHOT_FREE_FACTOR` — default `3`.
|
|
17
|
+
* - `LENSMCP_HEAP_SNAPSHOT_KEEP` — default `3`.
|
|
18
|
+
* - `LENSMCP_HEAP_SNAPSHOT_MAX_AGE_H` — default `24`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function heapSnapshotPolicy(env?: NodeJS.ProcessEnv): HeapSnapshotPolicy;
|
|
21
|
+
/** Memory the machine can hand out without paging, and how that was read. */
|
|
22
|
+
export interface AvailableMemory {
|
|
23
|
+
bytes: number;
|
|
24
|
+
source: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What the machine could give a snapshot without swapping.
|
|
28
|
+
*
|
|
29
|
+
* `os.freemem()` is the wrong number on macOS: it counts only FREE pages, and a Mac under normal use keeps a few
|
|
30
|
+
* hundred MB free while gigabytes of inactive (reclaimable) pages sit beside them — it would refuse every time.
|
|
31
|
+
* `vm_stat`'s free + inactive + speculative + purgeable is what the kernel can reclaim without compressing or
|
|
32
|
+
* swapping. Linux has `MemAvailable` for exactly this. Elsewhere `os.freemem()` is all there is.
|
|
33
|
+
*/
|
|
34
|
+
export declare function availableSystemMemory(platform?: NodeJS.Platform): AvailableMemory | undefined;
|
|
35
|
+
/** Why a snapshot must not be taken now, in the words the caller will print — or `undefined`. */
|
|
36
|
+
export declare function heapSnapshotRefusal(policy: HeapSnapshotPolicy, input: {
|
|
37
|
+
live: number;
|
|
38
|
+
available?: AvailableMemory;
|
|
39
|
+
}): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Where snapshots go: `LENSMCP_HEAP_DIR`, else `heap/` beside the workspace's bus (`<root>/.lensmcp/heap`), else
|
|
42
|
+
* `<cwd>/.lensmcp/heap`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function heapSnapshotDir(env?: NodeJS.ProcessEnv, cwd?: string): string;
|
|
45
|
+
/** A snapshot's file name: service, pod, pid, app generation, time. */
|
|
46
|
+
export declare function heapSnapshotFileName(service: string, pod: number, pid: number, gen: number, at: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* Make `dir` exist and be owner-only — and refuse a symlink there: snapshots carry everything the app holds
|
|
49
|
+
* (tokens, keys, customer data), so where they land must be ours.
|
|
50
|
+
*/
|
|
51
|
+
export declare function ensureHeapDir(dir: string): void;
|
|
52
|
+
/** Delete this directory's snapshots older than `maxAgeMs`, then all but the newest `keep - 1` (room for one more). */
|
|
53
|
+
export declare function pruneHeapSnapshots(dir: string, policy: HeapSnapshotPolicy, now?: number): string[];
|
|
54
|
+
/** What a written snapshot weighs and cost. */
|
|
55
|
+
export interface HeapSnapshotWritten {
|
|
56
|
+
bytes: number;
|
|
57
|
+
ms: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Stream a heap snapshot of THIS process to `file` through an in-process inspector session.
|
|
61
|
+
*
|
|
62
|
+
* The chunks arrive as the snapshot is serialised and are written as they come, so the JS heap never holds the
|
|
63
|
+
* whole thing. The file is created exclusively (`O_EXCL | O_NOFOLLOW`, `0600`): never someone else's file,
|
|
64
|
+
* never through a planted link. A failure removes what was written.
|
|
65
|
+
*/
|
|
66
|
+
export declare function writeHeapSnapshot(file: string): Promise<HeapSnapshotWritten>;
|
package/heap-snapshot.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var _=Object.defineProperty;var p=(e,t)=>_(e,"name",{value:t,configurable:!0});var h=Object.defineProperty,i=p((e,t)=>h(e,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.heapSnapshotPolicy=heapSnapshotPolicy,exports.availableSystemMemory=availableSystemMemory,exports.heapSnapshotRefusal=heapSnapshotRefusal,exports.heapSnapshotDir=heapSnapshotDir,exports.heapSnapshotFileName=heapSnapshotFileName,exports.ensureHeapDir=ensureHeapDir,exports.pruneHeapSnapshots=pruneHeapSnapshots,exports.writeHeapSnapshot=writeHeapSnapshot;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),inspector=tslib_1.__importStar(require("node:inspector")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),MB=1024*1024,numberFrom=i((e,t,o)=>{if(e===void 0||e.trim()==="")return t;const a=Number(e);return Number.isFinite(a)&&a>=o?a:t},"numberFrom");function heapSnapshotPolicy(e=process.env){return{maxHeapBytes:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MAX_MB,1024,1)*MB,minFreeBytes:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MIN_FREE_MB,1024,0)*MB,freeFactor:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_FREE_FACTOR,3,0),keep:Math.max(1,Math.floor(numberFrom(e.LENSMCP_HEAP_SNAPSHOT_KEEP,3,1))),maxAgeMs:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MAX_AGE_H,24,0)*36e5}}p(heapSnapshotPolicy,"heapSnapshotPolicy"),i(heapSnapshotPolicy,"heapSnapshotPolicy");function availableSystemMemory(e=process.platform){try{if(e==="darwin"){const t=(0,node_child_process_1.spawnSync)("vm_stat",[],{encoding:"utf8",timeout:2e3}).stdout??"",o=Number(/page size of (\d+) bytes/.exec(t)?.[1]??4096),a=i(r=>Number(new RegExp(`Pages ${r}:\\s+(\\d+)`).exec(t)?.[1]??0),"pages"),n=a("free")+a("inactive")+a("speculative")+a("purgeable");if(n>0)return{bytes:n*o,source:"vm_stat free+inactive+speculative+purgeable"}}else if(e==="linux"){const t=/MemAvailable:\s+(\d+) kB/.exec(fs.readFileSync("/proc/meminfo","utf8"));if(t)return{bytes:Number(t[1])*1024,source:"/proc/meminfo MemAvailable"}}return{bytes:os.freemem(),source:"os.freemem()"}}catch{return}}p(availableSystemMemory,"availableSystemMemory"),i(availableSystemMemory,"availableSystemMemory");function heapSnapshotRefusal(e,t){if(t.live>e.maxHeapBytes)return`the pod's live heap is ${Math.round(t.live/MB)} MB, over the ${Math.round(e.maxHeapBytes/MB)} MB bound (LENSMCP_HEAP_SNAPSHOT_MAX_MB) \u2014 a snapshot this size needs several times that in memory while it is written`;if(t.available){const o=Math.max(e.minFreeBytes,e.freeFactor*t.live);if(t.available.bytes<o)return`only ${Math.round(t.available.bytes/MB)} MB of memory is available (${t.available.source}) and a snapshot of a ${Math.round(t.live/MB)} MB heap needs about ${Math.round(o/MB)} MB \u2014 free some memory first, or lower the floor with LENSMCP_HEAP_SNAPSHOT_MIN_FREE_MB / LENSMCP_HEAP_SNAPSHOT_FREE_FACTOR`}}p(heapSnapshotRefusal,"heapSnapshotRefusal"),i(heapSnapshotRefusal,"heapSnapshotRefusal");function heapSnapshotDir(e=process.env,t=process.cwd()){return e.LENSMCP_HEAP_DIR?path.resolve(e.LENSMCP_HEAP_DIR):e.LENSMCP_EVENT_FILE?path.join(path.dirname(path.resolve(e.LENSMCP_EVENT_FILE)),"heap"):path.join(t,".lensmcp","heap")}p(heapSnapshotDir,"heapSnapshotDir"),i(heapSnapshotDir,"heapSnapshotDir");function heapSnapshotFileName(e,t,o,a,n){const r=(e||"service").replace(/[^A-Za-z0-9_.-]/g,"_"),s=new Date(n).toISOString().replace(/[:.]/g,"-");return`${r}-pod${t}-pid${o}-g${a}-${s}.heapsnapshot`}p(heapSnapshotFileName,"heapSnapshotFileName"),i(heapSnapshotFileName,"heapSnapshotFileName");function ensureHeapDir(e){fs.mkdirSync(e,{recursive:!0,mode:448});const t=fs.lstatSync(e);if(!t.isDirectory())throw new Error(`${e} is not a directory`);(t.mode&63)!==0&&fs.chmodSync(e,448)}p(ensureHeapDir,"ensureHeapDir"),i(ensureHeapDir,"ensureHeapDir");function pruneHeapSnapshots(e,t,o=Date.now()){const a=[];let n;try{n=fs.readdirSync(e).filter(r=>r.endsWith(".heapsnapshot")).map(r=>{const s=path.join(e,r),u=fs.lstatSync(s);return u.isFile()?{file:s,mtime:u.mtimeMs}:void 0}).filter(r=>r!==void 0).sort((r,s)=>s.mtime-r.mtime)}catch{return a}return n.forEach((r,s)=>{if(s>=t.keep-1||o-r.mtime>t.maxAgeMs)try{fs.unlinkSync(r.file),a.push(r.file)}catch{}}),a}p(pruneHeapSnapshots,"pruneHeapSnapshots"),i(pruneHeapSnapshots,"pruneHeapSnapshots");function writeHeapSnapshot(e){const t=Date.now(),o=fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_EXCL|(fs.constants.O_NOFOLLOW??0),a=fs.openSync(e,o,384),n=new inspector.Session;let r=0,s;return new Promise((u,S)=>{const m=i(c=>{n.removeAllListeners("HeapProfiler.addHeapSnapshotChunk");try{n.disconnect()}catch{}try{fs.closeSync(a)}catch{}const l=c??s;if(l){try{fs.unlinkSync(e)}catch{}S(l);return}u({bytes:r,ms:Date.now()-t})},"finish");try{n.connect(),n.on("HeapProfiler.addHeapSnapshotChunk",c=>{if(!s)try{r+=fs.writeSync(a,c.params.chunk)}catch(l){s=l}}),n.post("HeapProfiler.takeHeapSnapshot",{reportProgress:!1},c=>m(c))}catch(c){m(c)}})}p(writeHeapSnapshot,"writeHeapSnapshot"),i(writeHeapSnapshot,"writeHeapSnapshot");
|
package/hot-swap-registries.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Why a pod's heap grew by a whole bundle on every hot swap (issues/foodguard
|
|
2
|
+
* Why a pod's heap grew by a whole bundle on every hot swap (issues/closed/foodguard-24).
|
|
3
3
|
*
|
|
4
4
|
* A swap re-requires the webpack bundle in the SAME process, so every decorated class is created again.
|
|
5
5
|
* The old app is closed and its module graph should go with it — but a library loaded from node_modules
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inspector credential, and where it may go (issues/closed/pod-memory-06).
|
|
3
|
+
*
|
|
4
|
+
* A Node inspector has no authentication: whoever holds its `ws://127.0.0.1:<port>/<uuid>` URL can run code in
|
|
5
|
+
* the pod as the developer. The UUID is the whole credential. It used to reach `.lensmcp/gateway.log` — mode
|
|
6
|
+
* 0644, readable by every account on the machine — three ways: the pod's own "Debugger listening on …" line
|
|
7
|
+
* (Node prints it to the pod's stderr, which the devserver relays), the devserver's echo of it, and the
|
|
8
|
+
* "Debugger ending on …" line at close. The gateway's stdout IS that file.
|
|
9
|
+
*
|
|
10
|
+
* So the URL is handed out on exactly one path, the owner-only control socket (`lensmcp debug <svc>`, and the
|
|
11
|
+
* owner's own `lensmcp logs`), and redacted everywhere it would land in a file. A terminal is the developer's own
|
|
12
|
+
* screen and keeps the attachable link — IDE run consoles make it a click-to-attach — unless what the devserver
|
|
13
|
+
* writes is going into a gateway's log.
|
|
14
|
+
*
|
|
15
|
+
* The inspector itself published the UUID too: `GET /json/list` on its port answers it to anyone who can
|
|
16
|
+
* connect, and an ephemeral loopback port is one `connect()` sweep away. `--inspect-publish-uid=stderr` turns
|
|
17
|
+
* every HTTP endpoint of the inspector off (`/json`, `/json/list`, `/json/version` all answer 404), leaving the
|
|
18
|
+
* URL to the process that opened it — which reports it to the devserver over IPC.
|
|
19
|
+
*/
|
|
20
|
+
/** `text` with every inspector session UUID replaced — host and port kept, so the line still says where. */
|
|
21
|
+
export declare function redactInspectorUrls(text: string): string;
|
|
22
|
+
/** A stream this process writes, as far as the decision needs to know it. */
|
|
23
|
+
export interface StdioStream {
|
|
24
|
+
isTTY?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* May `stream` carry an attachable inspector URL? Only when it is a terminal AND this process is not writing into
|
|
28
|
+
* a gateway's log — a devserver the gateway started has its stdout piped to the gateway, whose own stdout is
|
|
29
|
+
* `.lensmcp/gateway.log` (`LENSMCP_GATEWAY_PID` marks one, main.devserver.ts `gatewayManaged`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function streamMayCarrySecrets(stream: StdioStream, underGateway: boolean): boolean;
|
|
32
|
+
/** What to write to `stream`: the text itself where it may carry the URL, redacted anywhere else. */
|
|
33
|
+
export declare function forStream(stream: StdioStream, underGateway: boolean, text: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* The flag a pod or worker runs under so its inspector never answers the UUID over HTTP. Left off only under
|
|
36
|
+
* `LENSMCP_DEBUG_FIXED_PORTS` — the opt-in for an IDE that attaches by host:port, which it does by asking
|
|
37
|
+
* `/json/list` for the target. That opt-out already warns that its ports are reachable by every account.
|
|
38
|
+
*/
|
|
39
|
+
export declare function inspectorPublishExecArgv(fixedPorts: boolean): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var n=Object.defineProperty;var s=(r,e)=>n(r,"name",{value:e,configurable:!0});var c=Object.defineProperty,t=s((r,e)=>c(r,"name",{value:e,configurable:!0}),"t");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=redactInspectorUrls,exports.streamMayCarrySecrets=streamMayCarrySecrets,exports.forStream=forStream,exports.inspectorPublishExecArgv=inspectorPublishExecArgv;const INSPECTOR_TOKEN_RE=/((?:wss?:\/\/[^\s/]+\/|ws=[^\s/]+\/))([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;function redactInspectorUrls(r){return r.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}s(redactInspectorUrls,"redactInspectorUrls"),t(redactInspectorUrls,"redactInspectorUrls");function streamMayCarrySecrets(r,e){return!e&&r.isTTY===!0}s(streamMayCarrySecrets,"streamMayCarrySecrets"),t(streamMayCarrySecrets,"streamMayCarrySecrets");function forStream(r,e,a){return streamMayCarrySecrets(r,e)?a:redactInspectorUrls(a)}s(forStream,"forStream"),t(forStream,"forStream");function inspectorPublishExecArgv(r){return r?[]:["--inspect-publish-uid=stderr"]}s(inspectorPublishExecArgv,"inspectorPublishExecArgv"),t(inspectorPublishExecArgv,"inspectorPublishExecArgv");
|
package/main.devserver.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Serializable } from 'node:child_process';
|
|
2
|
+
import { redactInspectorUrls } from './inspector-secret';
|
|
1
3
|
export interface GatewayRoute {
|
|
2
4
|
/** Hostname to match — exact (`api.tetros.localhost`) or wildcard (`*.tetros.localhost`). */
|
|
3
5
|
host?: string;
|
|
@@ -17,10 +19,10 @@ export interface GatewayRoute {
|
|
|
17
19
|
* Did this request arrive on the OWNER-ONLY control socket, rather than the public TCP port?
|
|
18
20
|
*
|
|
19
21
|
* `requestHandler` serves both: the Unix socket at `$TMPDIR/<ws>/<svc>-devserver/parent.sock`
|
|
20
|
-
* (0700 dir, see issues/codex-security
|
|
22
|
+
* (0700 dir, see issues/closed/codex-security-23) and `server.listen(publicPort)`. Admin branches were
|
|
21
23
|
* reachable on both, so `POST /webpack/debug` — which opens a Node inspector in every pod and
|
|
22
24
|
* returns the ws:// URLs, and whose UUID is the only thing guarding `Runtime.evaluate` — could be
|
|
23
|
-
* called by anything that could reach the port (issues/codex-security
|
|
25
|
+
* called by anything that could reach the port (issues/closed/codex-security-05).
|
|
24
26
|
*
|
|
25
27
|
* Node's `Server.address()` returns the PATH STRING for a pipe/Unix socket and an object
|
|
26
28
|
* `{ address, family, port }` for TCP. That is the discriminator, and it needs no extra plumbing
|
|
@@ -29,6 +31,22 @@ export interface GatewayRoute {
|
|
|
29
31
|
export declare function isOwnerOnlyTransport(req: {
|
|
30
32
|
socket?: unknown;
|
|
31
33
|
}): boolean;
|
|
34
|
+
/** Either end of an IPC channel: a pod or worker as its parent holds it, or the pod's own `process`. */
|
|
35
|
+
export interface IpcEndpoint {
|
|
36
|
+
connected?: boolean;
|
|
37
|
+
send?(message: Serializable, callback: (error: Error | null) => void): boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tell a pod (or the parent) something over IPC — if it can still hear it (issues/closed/dev-gateway-19).
|
|
41
|
+
*
|
|
42
|
+
* `send` on a channel that has closed does not throw: it emits `'error'` (`ERR_IPC_CHANNEL_CLOSED`) on the next
|
|
43
|
+
* tick, and nothing listened for it. A pod that had exited is still listed until it is replaced, so a file change
|
|
44
|
+
* relayed to "every pod" — or a reload forwarded mid-stop — crashed the devserver PARENT, and the serve executor
|
|
45
|
+
* then respawned one. Never on a closed channel, always with a callback (which takes the error the event would have
|
|
46
|
+
* carried: a close between the check and the write is what the exit handler is for), and `false` when the message
|
|
47
|
+
* did not go, for a caller that has something else to do then.
|
|
48
|
+
*/
|
|
49
|
+
export declare function tell(endpoint: IpcEndpoint, message: Serializable): boolean;
|
|
32
50
|
/** A devserver the gateway started carries its pid; one run by hand (`nx run <svc>:serve-hmr`) does not. */
|
|
33
51
|
export declare function gatewayManaged(env?: NodeJS.ProcessEnv): boolean;
|
|
34
52
|
/**
|
|
@@ -37,7 +55,7 @@ export declare function gatewayManaged(env?: NodeJS.ProcessEnv): boolean;
|
|
|
37
55
|
* Under a gateway, only the control socket may. The TCP port is not an identity: it comes from a
|
|
38
56
|
* tracked config file, so every checkout of a repo has the same number. A second checkout's FIRST
|
|
39
57
|
* build has no control socket yet, so its plugin fell back to `localhost:<port>` — and that port
|
|
40
|
-
* belonged to the first checkout, whose pods it hot-swapped (issues/foodguard
|
|
58
|
+
* belonged to the first checkout, whose pods it hot-swapped (issues/closed/foodguard-35). Nothing checked
|
|
41
59
|
* who was asking. The gateway and the service's own build plugin both reach the control socket,
|
|
42
60
|
* which lives in the workspace's owner-only socket dir; nobody else has a reason to.
|
|
43
61
|
*
|
|
@@ -50,7 +68,7 @@ export declare function mayDriveThePool(req: {
|
|
|
50
68
|
/**
|
|
51
69
|
* Opt-out that restores the pre-`06` PREDICTABLE inspector ports (`base+slot`, `base+20+i`).
|
|
52
70
|
*
|
|
53
|
-
* issues/codex-security
|
|
71
|
+
* issues/closed/codex-security-06: with `debugPort` (or `LENSMCP_DEBUG_PORT`) set, every pod and worker
|
|
54
72
|
* opened a Node inspector on a port derived from a configured, PRINTED base. Node's inspector has no
|
|
55
73
|
* application authentication — `GET /json/list` on that port returns the session UUID, and the UUID is
|
|
56
74
|
* the whole credential for `Runtime.evaluate`, i.e. arbitrary code execution inside the pod with the
|
|
@@ -78,6 +96,16 @@ export declare function usesFixedInspectorPorts(env?: NodeJS.ProcessEnv): boolea
|
|
|
78
96
|
* degrades to `0` rather than throwing at `inspector.open`.
|
|
79
97
|
*/
|
|
80
98
|
export declare function inspectorPortFor(basePort: number, offset: number, env?: NodeJS.ProcessEnv): number;
|
|
81
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Strip the inspector session UUID out of any `ws://host:port/<uuid>` in a log line (inspector-secret.ts).
|
|
101
|
+
*
|
|
102
|
+
* `GET /webpack/logs` is served on BOTH the owner-only control socket and the public TCP port, and
|
|
103
|
+
* the log hub mirrors the pod's own "Debugger listening on ws://…" line plus the parent's echo of it.
|
|
104
|
+
* So moving the inspector to a random port would have moved the credential rather than removed it:
|
|
105
|
+
* a public reader could simply read the full URL — port AND UUID — out of the log stream. The owner
|
|
106
|
+
* (control socket) still gets the attachable URL; everyone else gets the shape and no secret. What the
|
|
107
|
+
* devserver writes to its own stdout/stderr and to its log tee is redacted the same way (issues/closed/pod-memory-06).
|
|
108
|
+
*/
|
|
109
|
+
export { redactInspectorUrls };
|
|
82
110
|
/** The TCP port out of a `ws://host:port/uuid` inspector URL, or `null`. */
|
|
83
111
|
export declare function inspectorPortOf(url: string | undefined): number | null;
|