@lensmcp/cluster 1.21.4 → 1.22.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.
Files changed (33) hide show
  1. package/executors/gateway/jwks-verify.js +1 -1
  2. package/executors/gateway/jwt-claims.d.ts +34 -0
  3. package/executors/gateway/jwt-claims.js +1 -0
  4. package/executors/gateway/main.prod-gateway.js +2 -2
  5. package/executors/gateway/prod-config.d.ts +27 -0
  6. package/executors/gateway/prod-config.js +1 -1
  7. package/executors/gateway/rate-limit.d.ts +9 -0
  8. package/executors/gateway/rate-limit.js +2 -2
  9. package/executors/gateway/runtime/auth.js +1 -1
  10. package/executors/gateway/runtime/chooser.d.ts +30 -9
  11. package/executors/gateway/runtime/chooser.js +9 -9
  12. package/executors/gateway/runtime/control.d.ts +17 -2
  13. package/executors/gateway/runtime/control.js +2 -2
  14. package/executors/gateway/runtime/edge.d.ts +29 -3
  15. package/executors/gateway/runtime/edge.js +1 -1
  16. package/executors/gateway/runtime/handler.js +1 -1
  17. package/executors/gateway/runtime/lens-children.js +1 -1
  18. package/executors/gateway/runtime/lifecycle.d.ts +21 -0
  19. package/executors/gateway/runtime/lifecycle.js +3 -3
  20. package/executors/gateway/runtime/proxy.d.ts +25 -0
  21. package/executors/gateway/runtime/proxy.js +1 -1
  22. package/executors/gateway/runtime/server.d.ts +90 -0
  23. package/executors/gateway/runtime/server.js +2 -2
  24. package/executors/gateway/runtime/types.d.ts +20 -0
  25. package/executors/gateway/runtime/types.js +1 -1
  26. package/executors/gateway/runtime/upgrade.js +1 -1
  27. package/executors/serve/serve.impl.js +1 -1
  28. package/executors/trust/hostname.d.ts +20 -0
  29. package/executors/trust/hostname.js +1 -0
  30. package/executors/trust/trust.impl.js +5 -3
  31. package/main.devserver.d.ts +50 -0
  32. package/main.devserver.js +7 -7
  33. package/package.json +6 -6
@@ -536,6 +536,21 @@ export declare const GATEWAY_CONN_CHECK_MS: number;
536
536
  * Override `LENSMCP_BODY_STALL_MS`. Default 60s.
537
537
  */
538
538
  export declare const BODY_STALL_MS: number;
539
+ /**
540
+ * The h2 front door's RST_STREAM budget, PER SESSION (issues/foodguard/14). nghttp2's Rapid-Reset mitigation
541
+ * (CVE-2023-44487) allows 1000 resets per session, refilling at 33/s, and CLOSES the session (GOAWAY
542
+ * INTERNAL_ERROR) on the next one — with NO 'error' event on the server side. A browser keeps ONE h2 session
543
+ * per origin for the life of the tab and cancels every in-flight stream on each navigation (a Vite dev page:
544
+ * hundreds of modules, plus the API fetches React StrictMode aborts), so the cancels ACCUMULATE across
545
+ * navigations until the budget is spent; then every in-flight module fails at once (`ERR_HTTP2_PROTOCOL_ERROR`
546
+ * ×N in the console) and the page boots into its blank/fallback state. Measured in foodguard 2026-09-07:
547
+ * 19 of 134 screens in one capture sweep; reproduced on round 3 of a 450-cancels-per-round probe. A dev
548
+ * front door serves one developer's browser, so the budget is sized far past any real navigation rate.
549
+ * Node enforces the limit only when BOTH values are set. Override `LENSMCP_H2_RESET_BURST` /
550
+ * `LENSMCP_H2_RESET_RATE` (resets per second).
551
+ */
552
+ export declare const GATEWAY_H2_RESET_BURST: number;
553
+ export declare const GATEWAY_H2_RESET_RATE: number;
539
554
  /** Test-tunable knobs on top of the user-facing schema. */
540
555
  export interface GatewayRuntimeOptions extends GatewayExecutorSchema {
541
556
  /** Traffic-edge flush period (ms). Default 5000. */
@@ -560,6 +575,11 @@ export interface GatewayRuntimeOptions extends GatewayExecutorSchema {
560
575
  connectionsCheckingIntervalMs?: number;
561
576
  /** Abort a transfer that has moved NOTHING for this long, either direction (`LENSMCP_BODY_STALL_MS`, default 60s; 0 off). */
562
577
  bodyStallMs?: number;
578
+ /** Per-session h2 RST_STREAM budget — burst (`LENSMCP_H2_RESET_BURST`, default 100k) and refill per second
579
+ * (`LENSMCP_H2_RESET_RATE`, default 10k). nghttp2's 1000 / 33 default tears a dev browser's session down
580
+ * after a few navigations (issues/foodguard/14). Both must be set for Node to apply either. */
581
+ h2ResetBurst?: number;
582
+ h2ResetRate?: number;
563
583
  /** Customer-supplied lifecycle hooks layered on top of the built-in pipeline. */
564
584
  hooks?: GatewayHooks;
565
585
  }
@@ -1 +1 @@
1
- "use strict";var O=Object.defineProperty;var e=(_,E)=>O(_,"name",{value:E,configurable:!0});var M=Object.defineProperty,L=e((_,E)=>M(_,"name",{value:E,configurable:!0}),"L");Object.defineProperty(exports,"__esModule",{value:!0}),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_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,R=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?R?0:E:S}e(envInt,"envInt"),L(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.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);
1
+ "use strict";var M=Object.defineProperty;var e=(_,E)=>M(_,"name",{value:E,configurable:!0});var T=Object.defineProperty,R=e((_,E)=>T(_,"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_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.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 k=Object.defineProperty;var u=(a,s)=>k(a,"name",{value:s,configurable:!0});var v=Object.defineProperty,p=u((a,s)=>v(a,"name",{value:s,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");function createUpgrade(a){const{rt:s,auth:y,proxy:f,hooks:c}=a,g=p(async(r,o,d)=>{for(const t of edge_1.GATEWAY_OWNED_HEADERS)delete r.headers[t];const i=(0,edge_1.canonicalize)(r.url??"/");if(i===void 0){o.destroy();return}r.url=i.path+i.query;const m=(r.headers.host||"").split(":")[0],e=(0,manifest_1.matchRoute)(s.routes,m,i.policy+i.query);if(!e){o.destroy();return}(0,edge_1.resolveClientIp)(s,r);const n=c.upgradeContext(r,o,void 0);if(n.route=e,e.internal){const t=y.internal(r,e,void 0);if(!t.ok){o.destroy();return}n.caller=t.caller}else{const t=y.public(r,e,void 0,i.policy);if(!t.ok){o.destroy();return}if(n.claims=t.claims,c.has("afterAuth")&&(await c.run("afterAuth",n),n.responded()))return}const h=e.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)]:void 0;if(h&&(r.headers["x-internal-token"]=h),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(s,r),c.has("onUpgrade")&&(await c.run("onUpgrade",n),n.responded()))return;const l=e.pool?(()=>{const t=(0,discovery_1.pickSock)(e.pool,s.scanTtlMs);return t?{socketPath:t}:void 0})():e.target;if(!l){o.destroy();return}if(e.lens&&String(r.headers["sec-websocket-protocol"]??"").split(",").map(t=>t.trim()).includes("vite-hmr")){const t=e.lens.workspaceKey?`${e.lens.workspaceKey}:${e.lens.project}`:e.lens.project;(0,hmr_reload_1.registerHmrClient)(s,t,o)}f.proxy.ws(r,o,d,{target:l,changeOrigin:!e.pool,agent:f.agentForTarget(l)},()=>o.destroy())},"run");return(r,o,d)=>{o.on("error",()=>o.destroy()),g(r,o,d).catch(()=>o.destroy())}}u(createUpgrade,"createUpgrade"),p(createUpgrade,"createUpgrade");
1
+ "use strict";var k=Object.defineProperty;var p=(a,s)=>k(a,"name",{value:s,configurable:!0});var m=Object.defineProperty,l=p((a,s)=>m(a,"name",{value:s,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");function accountUpgrade(a,s){const n=a.svc;if(!n||s.destroyed)return;n.inflight+=1,n.lastUsed=Date.now();let d=!1;s.once("close",()=>{d||(d=!0,n.inflight-=1,n.lastUsed=Date.now())})}p(accountUpgrade,"accountUpgrade"),l(accountUpgrade,"accountUpgrade");function createUpgrade(a){const{rt:s,auth:n,proxy:d,hooks:u}=a,h=l(async(r,o,y)=>{for(const t of edge_1.GATEWAY_OWNED_HEADERS)delete r.headers[t];const c=(0,edge_1.canonicalize)(r.url??"/");if(c===void 0){o.destroy();return}r.url=c.path+c.query;const v=(r.headers.host||"").split(":")[0],e=(0,manifest_1.matchRoute)(s.routes,v,c.policy+c.query);if(!e){o.destroy();return}(0,edge_1.resolveClientIp)(s,r);const i=u.upgradeContext(r,o,void 0);if(i.route=e,e.internal){const t=n.internal(r,e,void 0);if(!t.ok){o.destroy();return}i.caller=t.caller}else{const t=n.public(r,e,void 0,c.policy);if(!t.ok){o.destroy();return}if(i.claims=t.claims,u.has("afterAuth")&&(await u.run("afterAuth",i),i.responded()))return}const g=e.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)]:void 0;if(g&&(r.headers["x-internal-token"]=g),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(s,r),u.has("onUpgrade")&&(await u.run("onUpgrade",i),i.responded()))return;const f=e.pool?(()=>{const t=(0,discovery_1.pickSock)(e.pool,s.scanTtlMs);return t?{socketPath:t}:void 0})():e.target;if(!f){o.destroy();return}if(e.lens&&String(r.headers["sec-websocket-protocol"]??"").split(",").map(t=>t.trim()).includes("vite-hmr")){const t=e.lens.workspaceKey?`${e.lens.workspaceKey}:${e.lens.project}`:e.lens.project;(0,hmr_reload_1.registerHmrClient)(s,t,o)}accountUpgrade(e,o),d.proxy.ws(r,o,y,{target:f,changeOrigin:!e.pool,agent:d.agentForTarget(f)},()=>o.destroy())},"run");return(r,o,y)=>{o.on("error",()=>o.destroy()),h(r,o,y).catch(()=>o.destroy())}}p(createUpgrade,"createUpgrade"),l(createUpgrade,"createUpgrade");
@@ -1 +1 @@
1
- "use strict";var Y=Object.defineProperty;var a=(e,o)=>Y(e,"name",{value:o,configurable:!0});var J=Object.defineProperty,c=a((e,o)=>J(e,"name",{value:o,configurable:!0}),"c");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")),create_webpack_dev_1=require("../../create-webpack-dev"),webpack=require("webpack");async function*serveExecutor(e,o){process.env.NODE_ENV="development";const C=o.projectsConfigurations.projects[o.projectName],n=o.root,u=path.join(n,C.root),L=e.outputPath?path.join(n,e.outputPath):path.join(n,"dist",C.root),j=e.configEnv||"development",S=path.join(n,"config",`config.${j}.yaml`),_=3050;let i=_,l=o.projectName,m={};fs.existsSync(S)?(m=yaml.load(fs.readFileSync(S,"utf8")),i=Number(m.port)||i,l=m.serviceName||l):console.warn(`[serve] ${o.projectName}: no config file at ${S} \u2014 public devserver port defaults to ${_}. If another service also lacks its config, both bind ${_} \u2192 EADDRINUSE. Add config/config.${j}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const y=e.servePrefix??"",d=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),p=e.logFile?path.resolve(n,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;p&&(fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,""));let N,P;e.gateway?.middleware&&(N=path.resolve(u,e.gateway.middleware),P=JSON.stringify(m));const I=e.entryFile||"./src/deployments/service/main.ts",A=e.tsConfigFile||"./tsconfig.app.json",$=path.resolve(u,I),F=path.resolve(u,A),G=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(u,t.entryPath)})),k=(0,create_webpack_dev_1.createDevWebpackConfig)({appName:o.projectName,appRoot:u,outputDir:L,main:$,tsConfig:F,assets:e.assets||[],additionalEntryPoints:G,port:i,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:n,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0});let s=null,b=!1;const R=path.join(__dirname,"../../main.devserver.js"),O=path.join(__dirname,"../../main.devserver.ts"),x=!fs.existsSync(R)&&fs.existsSync(O),w=x?O:R,M=path.join(L,"main.js");function v(){s&&!s.killed&&(s.kill("SIGTERM"),s=null)}a(v,"f"),c(v,"cleanup");const W=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const r=c(()=>v(),"handler");return process.on(t,r),{sig:t,handler:r}});process.on("exit",v);function g(){if(!fs.existsSync(w)){console.error(`[serve] devserver script not found at ${w}`);return}const t=["--enable-source-maps"];x&&t.unshift("--require","@swc-node/register"),s=(0,node_child_process_1.fork)(w,{cwd:n,env:{...process.env,PORT:String(i),SERVICE_NAME:l,SERVE_PREFIX:y,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||o.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(n,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:M,...N&&{GATEWAY_MIDDLEWARE:N},...P&&{GATEWAY_CONFIG:P},...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)},...d&&{LENSMCP_DEBUG_PORT:String(d)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(r=>r.name))}},stdio:["inherit","inherit","inherit","ipc"],execArgv:t}),s.on("exit",(r,E)=>{s=null,!E&&(console.error(`[serve] devserver exited unexpectedly (code=${r}, signal=${E}) \u2014 respawning in 1.5s`),setTimeout(()=>{!s&&b&&g()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${i} (service: ${l}${y?`, prefix: /${y}`:""})`),console.log(d?`[serve] Debug: pod inspectors on 127.0.0.1:${d}+ \u2014 click the "Debugger listening" link in the console, or attach WebStorm (Attach to Node.js/Chrome) to port ${d}`:`[serve] Debug on demand: POST http://localhost:${i}/webpack/debug (optionally ?port=<base>) 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`)}a(g,"v"),c(g,"startDevServer");function U(){if(!s||s.killed){console.log("[serve] Devserver not running, restarting..."),g();return}fetch(`http://localhost:${i}/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),v(),g()})}a(U,"B"),c(U,"_notifyDevServerReload");const{watch:B,...q}=k,V=webpack(q);let f=null;const T=[];function h(t){if(f){const r=f;f=null,r(t)}else T.push(t)}a(h,"b"),c(h,"pushResult");function D(){return T.length>0?Promise.resolve(T.shift()):new Promise(t=>{f=t})}a(D,"W"),c(D,"waitForNext");const H=V.watch(k.watchOptions||{},(t,r)=>{if(t){console.error("[serve] Webpack error:",t),h({success:!1});return}const E=r.hasErrors();if(console.info(r.toString({colors:!0,chunks:!1,assets:!0})),E){h({success:!1});return}(!b||!s)&&(b=!0,g()),h({success:!0,baseUrl:`http://localhost:${i}`})});try{for(;;)yield await D()}finally{H.close(()=>{}),v(),W.forEach(({sig:t,handler:r})=>process.removeListener(t,r))}}a(serveExecutor,"serveExecutor"),c(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
1
+ "use strict";var X=Object.defineProperty;var l=(e,r)=>X(e,"name",{value:r,configurable:!0});var J=Object.defineProperty,a=l((e,r)=>J(e,"name",{value:r,configurable:!0}),"a");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")),create_webpack_dev_1=require("../../create-webpack-dev"),webpack=require("webpack");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const D=r.projectsConfigurations.projects[r.projectName],i=r.root,d=path.join(i,D.root),R=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",D.root),T=e.configEnv||"development",_=path.join(i,"config",`config.${T}.yaml`),S=3050;let u=S,n=r.projectName,h={};fs.existsSync(_)?(h=yaml.load(fs.readFileSync(_,"utf8")),u=Number(h.port)||u,n=h.serviceName||n):console.warn(`[serve] ${r.projectName}: no config file at ${_} \u2014 public devserver port defaults to ${S}. If another service also lacks its config, both bind ${S} \u2192 EADDRINUSE. Add config/config.${T}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(n=e.serviceName);const y=e.servePrefix??"",p=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),x=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),c=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(c){fs.mkdirSync(path.dirname(c),{recursive:!0}),fs.writeFileSync(c,"",{mode:384});try{fs.chmodSync(c,384)}catch{}}let N,P;e.gateway?.middleware&&(N=path.resolve(d,e.gateway.middleware),P=JSON.stringify(h));const A=e.entryFile||"./src/deployments/service/main.ts",$=e.tsConfigFile||"./tsconfig.app.json",G=path.resolve(d,A),F=path.resolve(d,$),U=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(d,t.entryPath)})),C=(0,create_webpack_dev_1.createDevWebpackConfig)({appName:r.projectName,appRoot:d,outputDir:R,main:G,tsConfig:F,assets:e.assets||[],additionalEntryPoints:U,port:u,serviceName:n,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});let s=null,b=!1;const O=path.join(__dirname,"../../main.devserver.js"),k=path.join(__dirname,"../../main.devserver.ts"),I=!fs.existsSync(O)&&fs.existsSync(k),w=I?k:O,M=path.join(R,"main.js");function v(){s&&!s.killed&&(s.kill("SIGTERM"),s=null)}l(v,"g"),a(v,"cleanup");const W=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const o=a(()=>v(),"handler");return process.on(t,o),{sig:t,handler:o}});process.on("exit",v);function g(){if(!fs.existsSync(w)){console.error(`[serve] devserver script not found at ${w}`);return}const t=["--enable-source-maps"];I&&t.unshift("--require","@swc-node/register"),s=(0,node_child_process_1.fork)(w,{cwd:i,env:{...process.env,PORT:String(u),SERVICE_NAME:n,SERVE_PREFIX:y,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||n,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:M,...N&&{GATEWAY_MIDDLEWARE:N},...P&&{GATEWAY_CONFIG:P},...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)},...p&&{LENSMCP_DEBUG_PORT:String(p)},...c&&{LENSMCP_LOG_FILE:c},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(o=>o.name))}},stdio:["inherit","inherit","inherit","ipc"],execArgv:t}),s.on("exit",(o,f)=>{s=null,!f&&(console.error(`[serve] devserver exited unexpectedly (code=${o}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!s&&b&&g()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${u} (service: ${n}${y?`, prefix: /${y}`:""})`),p?(console.warn(`[serve] DEBUGGER ENABLED for ${n}. 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.`),x?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${p}+slot (workers ${p}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${p}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port \u2014 click the "Debugger listening" link in this console, or run \`lensmcp debug ${n}\` to get the ws:// URLs over the owner-only control socket. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${n}\` opens pod inspectors without a restart`),c&&console.log(`[serve] Logs teed to ${c} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}l(g,"v"),a(g,"startDevServer");function B(){if(!s||s.killed){console.log("[serve] Devserver not running, restarting..."),g();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),v(),g()})}l(B,"V"),a(B,"_notifyDevServerReload");const{watch:Y,...q}=C,H=webpack(q);let E=null;const L=[];function m(t){if(E){const o=E;E=null,o(t)}else L.push(t)}l(m,"b"),a(m,"pushResult");function j(){return L.length>0?Promise.resolve(L.shift()):new Promise(t=>{E=t})}l(j,"W"),a(j,"waitForNext");const V=H.watch(C.watchOptions||{},(t,o)=>{if(t){console.error("[serve] Webpack error:",t),m({success:!1});return}const f=o.hasErrors();if(console.info(o.toString({colors:!0,chunks:!1,assets:!0})),f){m({success:!1});return}(!b||!s)&&(b=!0,g()),m({success:!0,baseUrl:`http://localhost:${u}`})});try{for(;;)yield await j()}finally{V.close(()=>{}),v(),W.forEach(({sig:t,handler:o})=>process.removeListener(t,o))}}l(serveExecutor,"serveExecutor"),a(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hostname validation for the trust executor.
3
+ *
4
+ * `nx trust` collects host strings from every project's `project.json`, from embedded route options,
5
+ * and from its own `hosts` option, then writes them into `/etc/hosts` as root. Workspace
6
+ * configuration therefore reaches a privileged command, and the only check was "does not contain
7
+ * `*`" (issues/codex-security/33).
8
+ *
9
+ * This is the first of two defences. The second — removing the shell — is what actually closes the
10
+ * injection; this one means a value that could never be a hostname fails loudly at the point it is
11
+ * read, rather than becoming a confusing `/etc/hosts` line or a silent no-op.
12
+ */
13
+ /**
14
+ * Is this a hostname we are willing to write into `/etc/hosts`?
15
+ *
16
+ * Deliberately strict: a hostname that needs quoting is not a hostname. Wildcards are rejected here
17
+ * too, preserving the executor's existing (and correct) refusal to write a `*` entry — `/etc/hosts`
18
+ * cannot express one.
19
+ */
20
+ export declare function isValidHostname(host: string): boolean;
@@ -0,0 +1 @@
1
+ "use strict";var s=Object.defineProperty;var a=(e,t)=>s(e,"name",{value:t,configurable:!0});var n=Object.defineProperty,r=a((e,t)=>n(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isValidHostname=isValidHostname;const LABEL=/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/,MAX_HOSTNAME_LENGTH=253;function isValidHostname(e){return typeof e!="string"||e.length===0||e.length>MAX_HOSTNAME_LENGTH?!1:e.split(".").every(t=>LABEL.test(t))}a(isValidHostname,"isValidHostname"),r(isValidHostname,"isValidHostname");
@@ -1,4 +1,6 @@
1
- "use strict";var v=Object.defineProperty;var p=(e,t)=>v(e,"name",{value:t,configurable:!0});var m=Object.defineProperty,d=p((e,t)=>m(e,"name",{value:t,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=trustExecutor,exports.collectHosts=collectHosts;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),basic_ssl_1=require("../../basic-ssl");async function trustExecutor(e,t){const o=path.join(t.root,"node_modules",".cache","davnx-webpack"),n=(0,basic_ssl_1.ensureCaSync)(o),u=process.env.LENSMCP_TRUST_HOSTS_ONLY==="1",a=collectHosts(e,t);if(a.length===0&&console.log("[trust] no concrete hostnames found (only wildcards?) \u2014 keychain step only."),process.platform!=="darwin"){console.log("[trust] non-macOS platform \u2014 do these manually:"),console.log(` \u2022 trust ${n} in your OS/browser store (Linux: update-ca-certificates / certutil)`);for(const s of a)console.log(` \u2022 /etc/hosts: "127.0.0.1 ${s}" and "::1 ${s}"`);return{success:!0}}const r=caSha256Fingerprint(n),h=u||r!==void 0&&(0,node_child_process_1.spawnSync)("security",["find-certificate","-a","-c","lensmcp local dev CA","-Z","/Library/Keychains/System.keychain"],{encoding:"utf8"}).stdout?.includes(r)===!0;if(u)console.log("[trust] hosts-only: a shared daemon serves TLS with its own CA \u2014 skipping the keychain step.");else if(h)console.log("[trust] CA already in the System keychain \u2014 skipping.");else{if(console.log(`[trust] adding CA to the System keychain (sudo will prompt): ${n}`),(0,node_child_process_1.spawnSync)("sudo",["security","add-trusted-cert","-d","-r","trustRoot","-k","/Library/Keychains/System.keychain",n],{stdio:"inherit"}).status!==0)return console.error("[trust] add-trusted-cert failed \u2014 rerun in a terminal so sudo can prompt."),{success:!1};console.log("[trust] CA trusted. Restart your browser once (it caches certificate verdicts).")}const c=fs.readFileSync("/etc/hosts","utf8"),i=d((s,f)=>c.split(`
2
- `).some(S=>{const y=S.trim();if(y.startsWith("#"))return!1;const g=y.split(/\s+/);return g[0]===s&&g.slice(1).includes(f)}),"hasEntry"),l=[];for(const s of a)i("127.0.0.1",s)||l.push(`127.0.0.1 ${s}`),i("::1",s)||l.push(`::1 ${s}`);if(l.length===0)console.log("[trust] /etc/hosts already complete (both families for every hostname).");else{console.log(`[trust] appending to /etc/hosts (sudo):
1
+ "use strict";var v=Object.defineProperty;var p=(o,t)=>v(o,"name",{value:t,configurable:!0});var m=Object.defineProperty,d=p((o,t)=>m(o,"name",{value:t,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=trustExecutor,exports.collectHosts=collectHosts;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),basic_ssl_1=require("../../basic-ssl"),hostname_1=require("./hostname");async function trustExecutor(o,t){const r=path.join(t.root,"node_modules",".cache","davnx-webpack"),i=(0,basic_ssl_1.ensureCaSync)(r),a=process.env.LENSMCP_TRUST_HOSTS_ONLY==="1",u=collectHosts(o,t);if(u.length===0&&console.log("[trust] no concrete hostnames found (only wildcards?) \u2014 keychain step only."),process.platform!=="darwin"){console.log("[trust] non-macOS platform \u2014 do these manually:"),console.log(` \u2022 trust ${i} in your OS/browser store (Linux: update-ca-certificates / certutil)`);for(const s of u)console.log(` \u2022 /etc/hosts: "127.0.0.1 ${s}" and "::1 ${s}"`);return{success:!0}}const e=caSha256Fingerprint(i),h=a||e!==void 0&&(0,node_child_process_1.spawnSync)("security",["find-certificate","-a","-c","lensmcp local dev CA","-Z","/Library/Keychains/System.keychain"],{encoding:"utf8"}).stdout?.includes(e)===!0;if(a)console.log("[trust] hosts-only: a shared daemon serves TLS with its own CA \u2014 skipping the keychain step.");else if(h)console.log("[trust] CA already in the System keychain \u2014 skipping.");else{if(console.log(`[trust] adding CA to the System keychain (sudo will prompt): ${i}`),(0,node_child_process_1.spawnSync)("sudo",["security","add-trusted-cert","-d","-r","trustRoot","-k","/Library/Keychains/System.keychain",i],{stdio:"inherit"}).status!==0)return console.error("[trust] add-trusted-cert failed \u2014 rerun in a terminal so sudo can prompt."),{success:!1};console.log("[trust] CA trusted. Restart your browser once (it caches certificate verdicts).")}const c=fs.readFileSync("/etc/hosts","utf8"),n=d((s,g)=>c.split(`
2
+ `).some(S=>{const f=S.trim();if(f.startsWith("#"))return!1;const y=f.split(/\s+/);return y[0]===s&&y.slice(1).includes(g)}),"hasEntry"),l=[];for(const s of u)n("127.0.0.1",s)||l.push(`127.0.0.1 ${s}`),n("::1",s)||l.push(`::1 ${s}`);if(l.length===0)console.log("[trust] /etc/hosts already complete (both families for every hostname).");else{if(console.log(`[trust] appending to /etc/hosts (sudo):
3
3
  ${l.join(`
4
- `)}`);const s=`printf '%s\\n' ${l.map(f=>`'${f}'`).join(" ")} >> /etc/hosts`;if((0,node_child_process_1.spawnSync)("sudo",["sh","-c",s],{stdio:"inherit"}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["sh","-c","dscacheutil -flushcache; killall -HUP mDNSResponder"],{stdio:"inherit"})}if(!u){const s=(0,node_child_process_1.spawnSync)("security",["verify-cert","-c",n,"-p","basic"],{stdio:"ignore"});console.log(`[trust] CA trust check: ${s.status===0?"OK":"NOT TRUSTED YET (browser restart may be needed)"}`)}for(const s of a)console.log(`[trust] ${s} \u2192 https ready (hosts + cert SAN ride the gateway config)`);return{success:!0}}p(trustExecutor,"trustExecutor"),d(trustExecutor,"trustExecutor");function caSha256Fingerprint(e){const t=(0,node_child_process_1.spawnSync)("openssl",["x509","-in",e,"-noout","-fingerprint","-sha256"],{encoding:"utf8"});if(t.status!==0||!t.stdout)return;const o=t.stdout.split("=")[1]?.replaceAll(":","").trim().toUpperCase();return o&&/^[0-9A-F]{64}$/.test(o)?o:void 0}p(caSha256Fingerprint,"caSha256Fingerprint"),d(caSha256Fingerprint,"caSha256Fingerprint");function collectHosts(e,t){const o=[],n=t.projectName?t.projectsConfigurations?.projects[t.projectName]:void 0,u=e.serveTarget??"serve-hmr",a=n?.targets?.[u]?.options?.gateway?.routes??[];for(const r of a)r.host&&!r.host.includes("*")&&o.push(r.host);for(const r of Object.values(t.projectsConfigurations?.projects??{}))try{const h=JSON.parse(fs.readFileSync(path.join(t.root,r.root,"project.json"),"utf8")),c=h.cluster??h.davnx,i=c?.host;i&&!i.includes("*")&&(o.push(i),c?.service&&c.internalHost!==!1&&o.push(typeof c.internalHost=="string"?c.internalHost:`internal.${i}`))}catch{}return[...new Set([...o,"lensmcp.local",...e.hosts??[]])]}p(collectHosts,"collectHosts"),d(collectHosts,"collectHosts");
4
+ `)}`),(0,node_child_process_1.spawnSync)("sudo",["tee","-a","/etc/hosts"],{input:l.join(`
5
+ `)+`
6
+ `,stdio:["pipe","ignore","inherit"]}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["dscacheutil","-flushcache"],{stdio:"inherit"}),(0,node_child_process_1.spawnSync)("sudo",["killall","-HUP","mDNSResponder"],{stdio:"inherit"})}if(!a){const s=(0,node_child_process_1.spawnSync)("security",["verify-cert","-c",i,"-p","basic"],{stdio:"ignore"});console.log(`[trust] CA trust check: ${s.status===0?"OK":"NOT TRUSTED YET (browser restart may be needed)"}`)}for(const s of u)console.log(`[trust] ${s} \u2192 https ready (hosts + cert SAN ride the gateway config)`);return{success:!0}}p(trustExecutor,"trustExecutor"),d(trustExecutor,"trustExecutor");function caSha256Fingerprint(o){const t=(0,node_child_process_1.spawnSync)("openssl",["x509","-in",o,"-noout","-fingerprint","-sha256"],{encoding:"utf8"});if(t.status!==0||!t.stdout)return;const r=t.stdout.split("=")[1]?.replaceAll(":","").trim().toUpperCase();return r&&/^[0-9A-F]{64}$/.test(r)?r:void 0}p(caSha256Fingerprint,"caSha256Fingerprint"),d(caSha256Fingerprint,"caSha256Fingerprint");function collectHosts(o,t){const r=[],i=t.projectName?t.projectsConfigurations?.projects[t.projectName]:void 0,a=o.serveTarget??"serve-hmr",u=i?.targets?.[a]?.options?.gateway?.routes??[];for(const e of u)e.host&&(0,hostname_1.isValidHostname)(e.host)?r.push(e.host):e.host&&console.warn(`[trust] ignoring invalid hostname in ${a} routes: ${JSON.stringify(e.host)}`);for(const e of Object.values(t.projectsConfigurations?.projects??{}))try{const h=JSON.parse(fs.readFileSync(path.join(t.root,e.root,"project.json"),"utf8")),c=h.cluster??h.davnx,n=c?.host;n&&!(0,hostname_1.isValidHostname)(n)&&console.warn(`[trust] ignoring invalid hostname in ${e.root}/project.json: ${JSON.stringify(n)}`),n&&(0,hostname_1.isValidHostname)(n)&&(r.push(n),c?.service&&c.internalHost!==!1&&r.push(typeof c.internalHost=="string"?c.internalHost:`internal.${n}`))}catch{}return[...new Set([...r,"lensmcp.local",...o.hosts??[]])]}p(collectHosts,"collectHosts"),d(collectHosts,"collectHosts");
@@ -13,3 +13,53 @@ export interface GatewayRoute {
13
13
  */
14
14
  prependPrefix?: string;
15
15
  }
16
+ /**
17
+ * Did this request arrive on the OWNER-ONLY control socket, rather than the public TCP port?
18
+ *
19
+ * `requestHandler` serves both: the Unix socket at `$TMPDIR/<ws>/<svc>-devserver/parent.sock`
20
+ * (0700 dir, see issues/codex-security/23) and `server.listen(publicPort)`. Admin branches were
21
+ * reachable on both, so `POST /webpack/debug` — which opens a Node inspector in every pod and
22
+ * 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/05).
24
+ *
25
+ * Node's `Server.address()` returns the PATH STRING for a pipe/Unix socket and an object
26
+ * `{ address, family, port }` for TCP. That is the discriminator, and it needs no extra plumbing
27
+ * through the handler.
28
+ */
29
+ export declare function isOwnerOnlyTransport(req: {
30
+ socket?: unknown;
31
+ }): boolean;
32
+ /**
33
+ * Opt-out that restores the pre-`06` PREDICTABLE inspector ports (`base+slot`, `base+20+i`).
34
+ *
35
+ * issues/codex-security/06: with `debugPort` (or `LENSMCP_DEBUG_PORT`) set, every pod and worker
36
+ * opened a Node inspector on a port derived from a configured, PRINTED base. Node's inspector has no
37
+ * application authentication — `GET /json/list` on that port returns the session UUID, and the UUID is
38
+ * the whole credential for `Runtime.evaluate`, i.e. arbitrary code execution inside the pod with the
39
+ * developer's environment, files and network. Binding `127.0.0.1` excludes other HOSTS. It does not
40
+ * exclude other ACCOUNTS on the same machine, and this project's threat model refuses to trust them.
41
+ *
42
+ * A debugger the developer cannot attach to is useless, so the feature stays. What changed is which
43
+ * way the default falls: the port is now EPHEMERAL (`inspector.open(0, …)`, kernel-chosen), and the
44
+ * resulting `ws://` URL is delivered by the pod's own console line and by `POST /webpack/debug` on the
45
+ * owner-only control socket (`lensmcp debug <svc>`). Guessing a random high port and then guessing a
46
+ * v4 UUID is a materially different proposition from `curl 127.0.0.1:9229/json/list`.
47
+ *
48
+ * The one thing an ephemeral port costs is a PRE-CONFIGURED IDE attach ("Attach to Node.js, port
49
+ * 9229"), which needs a stable number. `LENSMCP_DEBUG_FIXED_PORTS=1` buys that back, and every path
50
+ * that reads it also warns that it is host-wide.
51
+ */
52
+ export declare const DEBUG_FIXED_PORTS_ENV = "LENSMCP_DEBUG_FIXED_PORTS";
53
+ export declare function usesFixedInspectorPorts(env?: NodeJS.ProcessEnv): boolean;
54
+ /**
55
+ * The port a pod/worker inspector should bind: `0` (kernel picks a free loopback port) unless the
56
+ * caller has explicitly opted back into fixed ports.
57
+ *
58
+ * `0` is a real value here, not "off" — the caller decides whether to open an inspector at all
59
+ * (`debugBasePort` being set); this only decides WHICH port. An out-of-range derived port also
60
+ * degrades to `0` rather than throwing at `inspector.open`.
61
+ */
62
+ export declare function inspectorPortFor(basePort: number, offset: number, env?: NodeJS.ProcessEnv): number;
63
+ export declare function redactInspectorUrls(text: string): string;
64
+ /** The TCP port out of a `ws://host:port/uuid` inspector URL, or `null`. */
65
+ export declare function inspectorPortOf(url: string | undefined): number | null;
package/main.devserver.js CHANGED
@@ -1,10 +1,10 @@
1
- "use strict";var ge=Object.defineProperty;var g=(s,f)=>ge(s,"name",{value:f,configurable:!0});var he=Object.defineProperty,l=g((s,f)=>he(s,"name",{value:f,configurable:!0}),"l");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")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const s=process.env.GATEWAY_CONFIG;if(!s)return{};try{return JSON.parse(s)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const s=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof s=="function"?s:typeof s.default=="function"?s.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(s){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,s)}function applyGatewayMiddleware(s){if(gatewayMiddleware)try{gatewayMiddleware(s,GATEWAY_CONFIG)}catch(f){console.error("[gateway] Middleware error:",f)}}g(applyGatewayMiddleware,"applyGatewayMiddleware"),l(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(s,f){if(s.startsWith("*.")){const S=s.slice(1);return f.endsWith(S)||f===s.slice(2)}return f===s}g(hostMatches,"hostMatches"),l(hostMatches,"hostMatches");function matchGatewayRoute(s){if(GATEWAY_ROUTES.length===0)return;const f=(s.headers.host||"").split(":")[0],S=s.url||"/";for(const y of GATEWAY_ROUTES)if(!(y.host&&!hostMatches(y.host,f))&&!(y.prefix&&!S.startsWith(y.prefix)))return y}g(matchGatewayRoute,"matchGatewayRoute"),l(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_DIR=LENSMCP_WS_KEY?path.join(os.tmpdir(),LENSMCP_WS_KEY,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),POD_GENERATION=(()=>{const s=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(s)&&s>1?Math.floor(s):1})();function childSockPath(s){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${s}-g${POD_GENERATION}.sock`:`child-${s}.sock`)}g(childSockPath,"childSockPath"),l(childSockPath,"childSockPath");function sockOwnerPath(s){return`${s}.owner`}g(sockOwnerPath,"sockOwnerPath"),l(sockOwnerPath,"sockOwnerPath");function claimSock(s,f=process.pid){try{fs.mkdirSync(path.dirname(s),{recursive:!0})}catch{}try{fs.writeFileSync(sockOwnerPath(s),String(f))}catch{}try{fs.unlinkSync(s)}catch{}}g(claimSock,"claimSock"),l(claimSock,"claimSock");function cleanupSock(s,f=process.pid){if(s){try{const S=fs.readFileSync(sockOwnerPath(s),"utf8").trim();if(S&&S!==String(f))return}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(sockOwnerPath(s))}catch{}}}g(cleanupSock,"cleanupSock"),l(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const s=process.env.LENSMCP_SERVICE_KEY;if(!s)return;const{createHash:f}=require("node:crypto");return f("sha256").update(s).digest("hex").slice(0,8)}g(serviceKeyFingerprint,"serviceKeyFingerprint"),l(serviceKeyFingerprint,"serviceKeyFingerprint");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const resolved=path.isAbsolute(spec)?spec:path.join(__dirname,spec);return eval(`delete require.cache["${resolved}"]`),eval(`require("${resolved}")`)}g(importFresh,"importFresh"),l(importFresh,"importFresh");function debounce(s,f){let S=null;return(...y)=>{S&&clearTimeout(S),S=setTimeout(()=>s(...y),f)}}if(g(debounce,"debounce"),l(debounce,"debounce"),process.env.APP_RUNNER==="1"){const s=l(S=>{if(!inspector.url())try{inspector.open(S,"127.0.0.1",!1)}catch(_){console.warn(`[child] inspector.open(${S}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,_.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const y=inspector.url();y&&process.send&&process.send({type:"inspector-url",url:y})},"openInspector"),f=Number(process.env.CHILD_DEBUG_PORT||"");Number.isInteger(f)&&f>1024&&s(f);try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let S=null,y=!1,_,x=!1,k={app:null,handler:l((r,n)=>{n.statusCode=503,n.end("starting")},"handler"),inflight:0};const M=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),O=l(r=>{const n=globalThis.__lensmcpActiveWork;if(typeof n!="function")return{count:0};try{const u=n(r);return{count:Number(u?.count??0),...u?.oldestStartedAt?{oldestStartedAt:u.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),G=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),P=new Set;async function v(r,n){if(!r.closed&&(r.closed=!0,P.delete(r),n&&console.error(`[child] ${n}`),!!r.app))try{await r.app.close()}catch(u){console.error("[child] error closing a previous app:",u)}}g(v,"G"),l(v,"closeGeneration");async function F(r){for(;P.size>=G;){const h=P.values().next().value;if(!h)break;await v(h,`closing a still-draining generation early \u2014 ${P.size} were pending and the cap is ${G} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}P.add(r);const n=Date.now(),u=l(()=>({inflight:r.inflight,work:O(r.workGen).count}),"busy");for(;M>0&&Date.now()-n<M;){if(r.closed)return;const h=u();if(h.inflight===0&&h.work===0)break;await new Promise(m=>{setTimeout(m,250).unref?.()})}if(r.closed)return;const E=u();E.inflight>0||E.work>0?console.error(`[child] swap drain expired after ${Date.now()-n}ms \u2014 closing the previous app with ${E.inflight} in-flight request(s) and ${E.work} background job(s) still running. A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${M}ms) if this is legitimate work.`):Date.now()-n>250&&console.log(`[child] previous app drained in ${Date.now()-n}ms \u2014 closing it`),await v(r)}g(F,"V"),l(F,"drainThenClose");async function I(){if(y)return x=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),S;y=!0,process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(h,m)=>{console.error(`[child] uncaughtException (${m}):`,h)}),process.on("unhandledRejection",h=>{const m=h instanceof Error?h.stack||h.message:String(h);m.includes("FronteggContextScope")||m.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",h)});try{await importFresh(BUNDLE_PATH)}catch(h){throw y=!1,h}if(typeof global.createChildApp!="function")throw y=!1,new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);const r=global.__lensmcpBeginWorkGeneration,n=typeof r=="function"?r():void 0;let u;try{u=await global.createChildApp()}catch(h){throw y=!1,h}const E=k;return S=u,k={app:u,handler:u.handler,inflight:0,...n===void 0?{}:{workGen:n}},console.log("[child] Swapped to fresh app (previous draining in background)"),E.app&&F(E).catch(h=>console.error("[child] background close error:",h)),y=!1,x?(x=!1,I()):u}g(I,"O"),l(I,"swapNow");const J=debounce(async()=>{try{console.log("[child] /webpack/reload"),await I()}catch(r){console.error("[child] reload failed:",r)}},250),W=http.createServer((r,n)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){n.writeHead(200,{"content-type":"application/json"}),n.end(JSON.stringify({ok:!0})),J();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const E=r.headers["x-internal-token"],h=process.env.LENSMCP_SERVICE_KEY;if(h&&E!==h){n.writeHead(404,{"content-type":"text/plain"}),n.end("not found");return}const m=O();n.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),n.end(JSON.stringify({ok:!0,pid:process.pid,swapping:y,inflight:k.inflight,oldestInflightAt:k.oldestAt,activeWork:m.count,oldestWorkAt:m.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),..._?{watcher:_}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const u=k;u.inflight+=1,u.oldestAt===void 0&&(u.oldestAt=Date.now()),n.on("close",()=>{u.inflight-=1,u.inflight===0&&(u.oldestAt=void 0)}),u.handler(r,n)}),A=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(A),process.exit(0)});try{await I();const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);console.log(`[child] Using bundle: ${r}`),claimSock(A),W.listen(A,()=>{console.log(`[child] Listening on ${A}`),process.send&&process.send({type:"ready",socketPath:A})})}catch(r){console.error("[child] Startup error:",r),process.send&&process.send({type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const n=Number(r.port);s(Number.isInteger(n)&&n>1024?n:0);return}if(r.type==="watcher"){const n=r.testimony;n&&typeof n=="object"&&(_=n);return}if(r.type==="reload")try{await I(),process.send&&process.send({type:"reloaded",socketPath:A})}catch(n){console.error("[child] reload error:",n),process.send&&process.send({type:"reload-error",error:String(n?.stack||n)})}}}),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const d=l(async r=>{console.log(`[child ${r}] shutting down\u2026`);const n=S?.close().catch(E=>console.error("[child] close error:",E)),u=new Promise(E=>W.close(()=>E()));cleanupSock(A),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([n,u]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{inspector?.close?.(),d(r)})})})()}else{let s=g(function(d){P.push(d),P.length>G&&P.splice(0,P.length-G),M?.write(d.replace(O,""));for(const r of v)try{r.res.write(r.plain?d.replace(O,""):d)}catch{v.delete(r)}},"G");l(s,"emitLogLine");const f=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],S="\x1B[0m",y=Math.max(1,Number(process.env.CHILD_COUNT||1));let _=(()=>{const d=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(d)&&d>1024?d:0})();const x=20,k=process.env.LENSMCP_LOG_FILE||"",M=k?(()=>{try{return fs.mkdirSync(path.dirname(k),{recursive:!0}),fs.createWriteStream(k,{flags:"a"})}catch(d){return console.warn(`[parent] cannot open log file ${k}:`,d.message),null}})():null,O=/\x1b\[[0-9;]*m/g,G=2e3,P=[],v=new Set;for(const d of["log","info","warn","error"]){const r=console[d].bind(console);console[d]=(...n)=>{r(...n),s(n.map(u=>typeof u=="string"?u:util.inspect(u)).join(" ")+`
2
- `)}}const F=l(d=>f[(d-1)%f.length],"colorFor"),I=l(d=>{if(y===1)return"";const r=`[child#${d}] `;return`${F(d)}${r}${S}`},"tagFor"),J=l(d=>d.startsWith("{")&&d.endsWith("}"),"isJsonLog"),W=l((d,r)=>{const n=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:l(h=>`
3
- `+String(h),"stack")}}),u=l((h,m)=>{const b=h==="stdout"?process.stdout:process.stderr,T=J(m)?n(m):`${m}
4
- `;b.write(`${r}${T}`),s(`${r}${T}`)},"write"),E=l((h,m)=>{if(!h)return;const b=readline.createInterface({input:h});b.on("line",T=>u(m,T)),b.on("close",()=>{})},"attach");E(d.stdout,"stdout"),E(d.stderr,"stderr")},"wireProcLogging"),A=l(d=>W(d.proc,I(d.id)),"wireChildLogging");(async()=>{fs.mkdirSync(SOCK_DIR,{recursive:!0});const d=httpProxy.createProxyServer({});let r=Number(process.env.PORT)||0;r||(r=9090);const n=[],u=new Set;function E(){let e=0;for(;u.has(e);)e++;return u.add(e),e}g(E,"E"),l(E,"allocDebugSlot");let h=1,m=0,b=!1,T=y,D=[];function X(){return n.filter(e=>e.healthy&&typeof e.socketPath=="string")}g(X,"q"),l(X,"healthyChildren");let j;function z(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){j=t;for(const p of n)try{p.proc.send?.({type:"watcher",testimony:t})}catch{}}}g(z,"ee"),l(z,"recordWatcherTestimony");function Q(e,t=256*1024){return new Promise(p=>{let c="",a=!1;e.on("data",o=>{a||(c+=o,c.length>t&&(a=!0))}),e.on("end",()=>{if(a||!c)return p(void 0);try{p(JSON.parse(c))}catch{p(void 0)}}),e.on("error",()=>p(void 0))})}g(Q,"te"),l(Q,"readJsonBody");function L(){const e=X();if(e.length===0)return null;const t=m%e.length;return m=(m+1)%e.length,e[t]}g(L,"F"),l(L,"pickChild");async function Y(){const e=h++,t=childSockPath(e);cleanupSock(t);const p=["--enable-source-maps"];__filename.endsWith(".ts")&&p.unshift("--require","@swc-node/register");const c=_?E():-1,a=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...c>=0&&{CHILD_DEBUG_PORT:String(_+c)}},stdio:["inherit","pipe","pipe","ipc"],execArgv:p}),o={id:e,proc:a,healthy:!1,lastError:null,debugSlot:c};return a.on("message",w=>{if(!w||typeof w!="object")return;const i=w;if(i.type==="ready"){if(o.socketPath=String(i.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),j)try{a.send?.({type:"watcher",testimony:j})}catch{}}else i.type==="inspector-url"?(o.inspectorUrl=String(i.url),console.log(`[parent] Child#${o.id} Debugger listening on ${i.url}`)):i.type==="reloaded"?(o.socketPath=String(i.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`)):i.type==="reload-error"?(o.lastError=String(i.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):i.type==="boot-error"&&(o.lastError=String(i.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1)}),a.on("exit",(w,i)=>{o.debugSlot>=0&&(u.delete(o.debugSlot),o.debugSlot=-1);const N=i==="SIGINT"||i==="SIGTERM"||w===0;if(N?console.log(`[parent] Child#${o.id} exited cleanly (code=${w}, signal=${i??"none"})`):(console.error(`[parent] Child#${o.id} crashed (code=${w}, signal=${i??"none"})`),o.lastError||(o.lastError=`Child exited abnormally (code=${w}, signal=${i??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!N&&!b){const $=n.indexOf(o);$>=0&&n.splice($,1);const pe=Date.now();if(D=D.filter(V=>pe-V<3e4),D.length>=5){console.error(`[parent] crash-loop detected (${D.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}D.push(pe),setTimeout(()=>{const V=n.filter(fe=>fe.proc.killed===!1).length;!b&&V<T&&(console.log(`[parent] respawning a crashed pod (${V}/${T} live)`),Y().catch(()=>{}))},1500).unref?.()}}),n.push(o),A(o),o}g(Y,"J"),l(Y,"spawnChild");async function Z(e){T=Math.max(T,e);const t=n.filter(c=>c.proc.killed===!1),p=e-t.length;for(let c=0;c<p;c++)await Y()}g(Z,"re"),l(Z,"ensurePoolSize");async function ne(){const e=n.filter(t=>!t.healthy||typeof t.socketPath!="string");await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const p=n.indexOf(t);p>=0&&n.splice(p,1),await Y()}))}g(ne,"ae"),l(ne,"respawnUnhealthy");const R=[];function ee(e){const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),p=path.join(t,`${e}.js`);if(!fs.existsSync(p))return console.error(`[parent] Worker "${e}" bundle not found: ${p}`),null;const c=["--enable-source-maps"];__filename.endsWith(".ts")&&c.unshift("--require","@swc-node/register");try{c.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const a=(_||9229)+x+Math.max(0,WORKER_NAMES.indexOf(e));c.push(_?`--inspect=127.0.0.1:${a}`:`--inspect-port=127.0.0.1:${a}`);const o=(0,node_child_process_1.fork)(p,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:c}),w={name:e,proc:o,debugPort:a};return R.push(w),W(o,`\x1B[35m[worker:${e}] \x1B[0m`),o.on("exit",(i,N)=>{N==="SIGINT"||N==="SIGTERM"||i===0||console.error(`[parent] Worker "${e}" crashed (code=${i}, signal=${N??"none"})`);const $=R.indexOf(w);$>=0&&R.splice($,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),w}g(ee,"oe"),l(ee,"spawnWorkerProcess");function se(){for(const e of[...R])try{e.proc.kill("SIGTERM")}catch{}R.length=0;for(const e of WORKER_NAMES)ee(e)}g(se,"le"),l(se,"restartWorkers");const de=debounce(async()=>{const e=X();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)t.proc.send?.({type:"reload"})}await ne(),WORKER_NAMES.length>0&&se()},200),H=l(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),Q(e).then(a=>z(a?.watcher)),de();return}if(e.method==="POST"&&e.url==="/webpack/watching"){const a=await Q(e);z(a),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:j!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){let a="";e.on("data",o=>{a+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(a||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await Z(o),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const a=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(a.get("tail")??"100")||0,0),G),w=a.get("follow")!=="0",i=a.get("plain")==="1";t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const N=o===0?"":P.slice(-o).join("");if(t.write(i?N.replace(O,""):N),!w){t.end();return}const $={res:t,plain:i};v.add($),e.on("close",()=>v.delete($));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){const a=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(a)&&a>1024&&(_=a),_||(_=9229);const o=n.filter(i=>i.proc.exitCode===null&&i.proc.signalCode===null);for(const i of o){i.debugSlot<0&&(i.debugSlot=E());try{i.proc.send?.({type:"debug-open",port:_+i.debugSlot})}catch{}}for(const i of R)try{i.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&o.some(i=>!i.inspectorUrl);)await new Promise(i=>setTimeout(i,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:_,pods:o.map(i=>({id:i.id,inspectorUrl:i.inspectorUrl??null})),workers:R.map(i=>({name:i.name,port:i.debugPort}))},null,2)+`
5
- `);return}const p=matchGatewayRoute(e);if(p?.prependPrefix&&!e.url?.startsWith(p.prependPrefix)&&(e.url=p.prependPrefix+(e.url??"/")),p?.target){applyGatewayMiddleware(e),d.web(e,t,{target:p.target,autoRewrite:typeof p.target=="string",changeOrigin:!0},a=>{console.error(`[parent] proxy error to ${p.target}:`,a),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${p.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const c=L();if(!c){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const a=n.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
1
+ "use strict";var ge=Object.defineProperty;var g=(o,d)=>ge(o,"name",{value:d,configurable:!0});var he=Object.defineProperty,i=g((o,d)=>he(o,"name",{value:d,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEBUG_FIXED_PORTS_ENV=void 0,exports.isOwnerOnlyTransport=isOwnerOnlyTransport,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor,exports.redactInspectorUrls=redactInspectorUrls,exports.inspectorPortOf=inspectorPortOf;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const o=process.env.GATEWAY_CONFIG;if(!o)return{};try{return JSON.parse(o)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const o=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof o=="function"?o:typeof o.default=="function"?o.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(o){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,o)}function applyGatewayMiddleware(o){if(gatewayMiddleware)try{gatewayMiddleware(o,GATEWAY_CONFIG)}catch(d){console.error("[gateway] Middleware error:",d)}}g(applyGatewayMiddleware,"applyGatewayMiddleware"),i(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(o,d){if(o.startsWith("*.")){const f=o.slice(1);return d.endsWith(f)||d===o.slice(2)}return d===o}g(hostMatches,"hostMatches"),i(hostMatches,"hostMatches");function matchGatewayRoute(o){if(GATEWAY_ROUTES.length===0)return;const d=(o.headers.host||"").split(":")[0],f=o.url||"/";for(const y of GATEWAY_ROUTES)if(!(y.host&&!hostMatches(y.host,d))&&!(y.prefix&&!f.startsWith(y.prefix)))return y}g(matchGatewayRoute,"matchGatewayRoute"),i(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_DIR=LENSMCP_WS_KEY?path.join(os.tmpdir(),LENSMCP_WS_KEY,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),POD_GENERATION=(()=>{const o=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(o)&&o>1?Math.floor(o):1})();function childSockPath(o){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${o}-g${POD_GENERATION}.sock`:`child-${o}.sock`)}g(childSockPath,"childSockPath"),i(childSockPath,"childSockPath");function sockOwnerPath(o){return`${o}.owner`}g(sockOwnerPath,"sockOwnerPath"),i(sockOwnerPath,"sockOwnerPath");function isOwnerOnlyTransport(o){try{return typeof o.socket?.server?.address?.()=="string"}catch{return!1}}g(isOwnerOnlyTransport,"isOwnerOnlyTransport"),i(isOwnerOnlyTransport,"isOwnerOnlyTransport"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(o=process.env){const d=(o[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return d==="1"||d==="true"||d==="yes"||d==="on"}g(usesFixedInspectorPorts,"usesFixedInspectorPorts"),i(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(o,d,f=process.env){if(!usesFixedInspectorPorts(f))return 0;const y=Math.trunc(o)+Math.trunc(d);return Number.isInteger(y)&&y>1024&&y<=65535?y:0}g(inspectorPortFor,"inspectorPortFor"),i(inspectorPortFor,"inspectorPortFor");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(o){return o.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}g(redactInspectorUrls,"redactInspectorUrls"),i(redactInspectorUrls,"redactInspectorUrls");function inspectorPortOf(o){const d=/^wss?:\/\/[^/]*?:(\d+)\//.exec(o??""),f=d?Number(d[1]):NaN;return Number.isInteger(f)&&f>0?f:null}g(inspectorPortOf,"inspectorPortOf"),i(inspectorPortOf,"inspectorPortOf");function ensureOwnerOnlyDir(o){try{fs.mkdirSync(o,{recursive:!0,mode:448})}catch{}try{const d=fs.lstatSync(o);d.isDirectory()&&(d.mode&63)!==0&&fs.chmodSync(o,448)}catch{}}g(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),i(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(o,d=process.pid){ensureOwnerOnlyDir(path.dirname(o));try{const f=sockOwnerPath(o);try{fs.unlinkSync(f)}catch{}fs.writeFileSync(f,String(d),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(o)}catch{}}g(claimSock,"claimSock"),i(claimSock,"claimSock");function cleanupSock(o,d=process.pid){if(o){try{const f=fs.readFileSync(sockOwnerPath(o),"utf8").trim();if(f&&f!==String(d))return}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(sockOwnerPath(o))}catch{}}}g(cleanupSock,"cleanupSock"),i(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const o=process.env.LENSMCP_SERVICE_KEY;if(!o)return;const{createHash:d}=require("node:crypto");return d("sha256").update(o).digest("hex").slice(0,8)}g(serviceKeyFingerprint,"serviceKeyFingerprint"),i(serviceKeyFingerprint,"serviceKeyFingerprint");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const resolved=path.isAbsolute(spec)?spec:path.join(__dirname,spec);return eval(`delete require.cache["${resolved}"]`),eval(`require("${resolved}")`)}g(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(o,d){let f=null;return(...y)=>{f&&clearTimeout(f),f=setTimeout(()=>o(...y),d)}}if(g(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){const o=i(f=>{if(!inspector.url())try{inspector.open(f,"127.0.0.1",!1)}catch(w){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,w.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const y=inspector.url();y&&process.send&&process.send({type:"inspector-url",url:y})},"openInspector"),d=process.env.CHILD_DEBUG_PORT;if(d!==void 0&&d!==""){const f=Number(d);Number.isInteger(f)&&(f===0||f>1024)&&o(f)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let f=null,y=!1,w,C=!1,k={app:null,handler:i((r,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const W=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),G=i(r=>{const s=globalThis.__lensmcpActiveWork;if(typeof s!="function")return{count:0};try{const h=s(r);return{count:Number(h?.count??0),...h?.oldestStartedAt?{oldestStartedAt:h.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),M=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),N=new Set;async function R(r,s){if(!r.closed&&(r.closed=!0,N.delete(r),s&&console.error(`[child] ${s}`),!!r.app))try{await r.app.close()}catch(h){console.error("[child] error closing a previous app:",h)}}g(R,"L"),i(R,"closeGeneration");async function B(r){for(;N.size>=M;){const E=N.values().next().value;if(!E)break;await R(E,`closing a still-draining generation early \u2014 ${N.size} were pending and the cap is ${M} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}N.add(r);const s=Date.now(),h=i(()=>({inflight:r.inflight,work:G(r.workGen).count}),"busy");for(;W>0&&Date.now()-s<W;){if(r.closed)return;const E=h();if(E.inflight===0&&E.work===0)break;await new Promise(S=>{setTimeout(S,250).unref?.()})}if(r.closed)return;const m=h();m.inflight>0||m.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${m.inflight} in-flight request(s) and ${m.work} background job(s) still running. A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${W}ms) if this is legitimate work.`):Date.now()-s>250&&console.log(`[child] previous app drained in ${Date.now()-s}ms \u2014 closing it`),await R(r)}g(B,"V"),i(B,"drainThenClose");async function $(){if(y)return C=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),f;y=!0,process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(E,S)=>{console.error(`[child] uncaughtException (${S}):`,E)}),process.on("unhandledRejection",E=>{const S=E instanceof Error?E.stack||E.message:String(E);S.includes("FronteggContextScope")||S.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",E)});try{await importFresh(BUNDLE_PATH)}catch(E){throw y=!1,E}if(typeof global.createChildApp!="function")throw y=!1,new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);const r=global.__lensmcpBeginWorkGeneration,s=typeof r=="function"?r():void 0;let h;try{h=await global.createChildApp()}catch(E){throw y=!1,E}const m=k;return f=h,k={app:h,handler:h.handler,inflight:0,...s===void 0?{}:{workGen:s}},console.log("[child] Swapped to fresh app (previous draining in background)"),m.app&&B(m).catch(E=>console.error("[child] background close error:",E)),y=!1,C?(C=!1,$()):h}g($,"G"),i($,"swapNow");const J=debounce(async()=>{try{console.log("[child] /webpack/reload"),await $()}catch(r){console.error("[child] reload failed:",r)}},250),U=http.createServer((r,s)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({ok:!0})),J();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const m=r.headers["x-internal-token"],E=process.env.LENSMCP_SERVICE_KEY;if(E&&m!==E){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const S=G();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:y,inflight:k.inflight,oldestInflightAt:k.oldestAt,activeWork:S.count,oldestWorkAt:S.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...w?{watcher:w}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const h=k;h.inflight+=1,h.oldestAt===void 0&&(h.oldestAt=Date.now()),s.on("close",()=>{h.inflight-=1,h.inflight===0&&(h.oldestAt=void 0)}),h.handler(r,s)}),b=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(b),process.exit(0)});try{await $();const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);console.log(`[child] Using bundle: ${r}`),claimSock(b),U.listen(b,()=>{console.log(`[child] Listening on ${b}`),process.send&&process.send({type:"ready",socketPath:b})})}catch(r){console.error("[child] Startup error:",r),process.send&&process.send({type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const s=Number(r.port);o(Number.isInteger(s)&&s>1024?s:0);return}if(r.type==="watcher"){const s=r.testimony;s&&typeof s=="object"&&(w=s);return}if(r.type==="reload")try{await $(),process.send&&process.send({type:"reloaded",socketPath:b})}catch(s){console.error("[child] reload error:",s),process.send&&process.send({type:"reload-error",error:String(s?.stack||s)})}}}),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const u=i(async r=>{console.log(`[child ${r}] shutting down\u2026`);const s=f?.close().catch(m=>console.error("[child] close error:",m)),h=new Promise(m=>U.close(()=>m()));cleanupSock(b),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([s,h]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{inspector?.close?.(),u(r)})})})()}else{let o=g(function(u){N.push(u),N.length>M&&N.splice(0,N.length-M),W?.write(u.replace(G,""));for(const r of R)try{const s=r.plain?u.replace(G,""):u;r.res.write(r.redact?redactInspectorUrls(s):s)}catch{R.delete(r)}},"L");i(o,"emitLogLine");const d=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],f="\x1B[0m",y=Math.max(1,Number(process.env.CHILD_COUNT||1));let w=(()=>{const u=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(u)&&u>1024?u:0})();const C=20;w&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/codex-security/06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${w}+slot, workers ${w}+${C}+i). Any account on this host can connect to 127.0.0.1:${w} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const k=process.env.LENSMCP_LOG_FILE||"",W=k?(()=>{try{fs.mkdirSync(path.dirname(k),{recursive:!0});const u=fs.createWriteStream(k,{flags:"a",mode:384});try{fs.chmodSync(k,384)}catch{}return u}catch(u){return console.warn(`[parent] cannot open log file ${k}:`,u.message),null}})():null,G=/\x1b\[[0-9;]*m/g,M=2e3,N=[],R=new Set;for(const u of["log","info","warn","error"]){const r=console[u].bind(console);console[u]=(...s)=>{r(...s),o(s.map(h=>typeof h=="string"?h:util.inspect(h)).join(" ")+`
2
+ `)}}const B=i(u=>d[(u-1)%d.length],"colorFor"),$=i(u=>{if(y===1)return"";const r=`[child#${u}] `;return`${B(u)}${r}${f}`},"tagFor"),J=i(u=>u.startsWith("{")&&u.endsWith("}"),"isJsonLog"),U=i((u,r,s)=>{const h=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:i(S=>`
3
+ `+String(S),"stack")}}),m=i((S,T)=>{const I=S==="stdout"?process.stdout:process.stderr;try{s?.(T)}catch{}const A=J(T)?h(T):`${T}
4
+ `;I.write(`${r}${A}`),o(`${r}${A}`)},"write"),E=i((S,T)=>{if(!S)return;const I=readline.createInterface({input:S});I.on("line",A=>m(T,A)),I.on("close",()=>{})},"attach");E(u.stdout,"stdout"),E(u.stderr,"stderr")},"wireProcLogging"),b=i(u=>U(u.proc,$(u.id)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const u=httpProxy.createProxyServer({});let r=Number(process.env.PORT)||0;r||(r=9090);const s=[],h=new Set;function m(){let e=0;for(;h.has(e);)e++;return h.add(e),e}g(m,"S"),i(m,"allocDebugSlot");let E=1,S=0,T=!1,I=y,A=[];function z(){return s.filter(e=>e.healthy&&typeof e.socketPath=="string")}g(z,"q"),i(z,"healthyChildren");let Y;function Q(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){Y=t;for(const p of s)try{p.proc.send?.({type:"watcher",testimony:t})}catch{}}}g(Q,"ee"),i(Q,"recordWatcherTestimony");function Z(e,t=256*1024){return new Promise(p=>{let c="",l=!1;e.on("data",n=>{l||(c+=n,c.length>t&&(l=!0))}),e.on("end",()=>{if(l||!c)return p(void 0);try{p(JSON.parse(c))}catch{p(void 0)}}),e.on("error",()=>p(void 0))})}g(Z,"te"),i(Z,"readJsonBody");function L(){const e=z();if(e.length===0)return null;const t=S%e.length;return S=(S+1)%e.length,e[t]}g(L,"j"),i(L,"pickChild");async function q(){const e=E++,t=childSockPath(e);cleanupSock(t);const p=["--enable-source-maps"];__filename.endsWith(".ts")&&p.unshift("--require","@swc-node/register");const c=w?m():-1,l=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...c>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(w,c))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:p}),n={id:e,proc:l,healthy:!1,lastError:null,debugSlot:c};return l.on("message",_=>{if(!_||typeof _!="object")return;const a=_;if(a.type==="ready"){if(n.socketPath=String(a.socketPath),n.healthy=!0,console.log(`[parent] Child#${n.id} ready on socket ${n.socketPath}`),Y)try{l.send?.({type:"watcher",testimony:Y})}catch{}}else a.type==="inspector-url"?(n.inspectorUrl=String(a.url),console.log(`[parent] Child#${n.id} Debugger listening on ${a.url}`)):a.type==="reloaded"?(n.socketPath=String(a.socketPath),n.healthy=!0,console.log(`[parent] Child#${n.id} hot-swapped on socket ${n.socketPath}`)):a.type==="reload-error"?(n.lastError=String(a.error||"unknown reload error"),console.error(`[parent] Child#${n.id} reload error: ${n.lastError}`),n.healthy=!1):a.type==="boot-error"&&(n.lastError=String(a.error||"unknown boot error"),console.error(`[parent] Child#${n.id} boot error: ${n.lastError}`),n.healthy=!1)}),l.on("exit",(_,a)=>{n.debugSlot>=0&&(h.delete(n.debugSlot),n.debugSlot=-1);const P=a==="SIGINT"||a==="SIGTERM"||_===0;if(P?console.log(`[parent] Child#${n.id} exited cleanly (code=${_}, signal=${a??"none"})`):(console.error(`[parent] Child#${n.id} crashed (code=${_}, signal=${a??"none"})`),n.lastError||(n.lastError=`Child exited abnormally (code=${_}, signal=${a??"none"})`)),n.healthy=!1,cleanupSock(n.socketPath,n.proc.pid),n.socketPath=void 0,!P&&!T){const v=s.indexOf(n);v>=0&&s.splice(v,1);const j=Date.now();if(A=A.filter(D=>j-D<3e4),A.length>=5){console.error(`[parent] crash-loop detected (${A.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}A.push(j),setTimeout(()=>{const D=s.filter(fe=>fe.proc.killed===!1).length;!T&&D<I&&(console.log(`[parent] respawning a crashed pod (${D}/${I} live)`),q().catch(()=>{}))},1500).unref?.()}}),s.push(n),b(n),n}g(q,"J"),i(q,"spawnChild");async function ee(e){I=Math.max(I,e);const t=s.filter(c=>c.proc.killed===!1),p=e-t.length;for(let c=0;c<p;c++)await q()}g(ee,"re"),i(ee,"ensurePoolSize");async function se(){const e=s.filter(t=>!t.healthy||typeof t.socketPath!="string");await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const p=s.indexOf(t);p>=0&&s.splice(p,1),await q()}))}g(se,"ae"),i(se,"respawnUnhealthy");const O=[];function te(e){const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),p=path.join(t,`${e}.js`);if(!fs.existsSync(p))return console.error(`[parent] Worker "${e}" bundle not found: ${p}`),null;const c=["--enable-source-maps"];__filename.endsWith(".ts")&&c.unshift("--require","@swc-node/register");try{c.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const l=inspectorPortFor(w||9229,C+Math.max(0,WORKER_NAMES.indexOf(e)));c.push(w?`--inspect=127.0.0.1:${l}`:`--inspect-port=127.0.0.1:${l}`);const n=(0,node_child_process_1.fork)(p,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:c}),_={name:e,proc:n,debugPort:l};return O.push(_),U(n,`\x1B[35m[worker:${e}] \x1B[0m`,a=>{const P=/Debugger listening on (wss?:\/\/\S+)/.exec(a);P&&(_.inspectorUrl=P[1])}),n.on("exit",(a,P)=>{P==="SIGINT"||P==="SIGTERM"||a===0||console.error(`[parent] Worker "${e}" crashed (code=${a}, signal=${P??"none"})`);const v=O.indexOf(_);v>=0&&O.splice(v,1)}),console.log(`[parent] Worker "${e}" started (pid=${n.pid})`),_}g(te,"oe"),i(te,"spawnWorkerProcess");function ce(){for(const e of[...O])try{e.proc.kill("SIGTERM")}catch{}O.length=0;for(const e of WORKER_NAMES)te(e)}g(ce,"le"),i(ce,"restartWorkers");const de=debounce(async()=>{const e=z();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)t.proc.send?.({type:"reload"})}await se(),WORKER_NAMES.length>0&&ce()},200),F=i(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),Z(e).then(l=>Q(l?.watcher)),de();return}if(e.method==="POST"&&e.url==="/webpack/watching"){const l=await Z(e);Q(l),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:Y!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){let l="";e.on("data",n=>{l+=n}),e.on("end",async()=>{let n=0;try{n=Number(JSON.parse(l||"{}").pods)||0}catch{}n=Math.max(1,Math.min(n,16)),await ee(n),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:n}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const l=new URL(e.url,"http://localhost").searchParams,n=Math.min(Math.max(Number(l.get("tail")??"100")||0,0),M),_=l.get("follow")!=="0",a=l.get("plain")==="1",P=!isOwnerOnlyTransport(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const v=n===0?"":N.slice(-n).join(""),j=a?v.replace(G,""):v;if(t.write(P?redactInspectorUrls(j):j),!_){t.end();return}const D={res:t,plain:a,redact:P};R.add(D),e.on("close",()=>R.delete(D));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!isOwnerOnlyTransport(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const l=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(l)&&l>1024&&(w=l),w||(w=9229);const n=s.filter(a=>a.proc.exitCode===null&&a.proc.signalCode===null);for(const a of n){a.debugSlot<0&&(a.debugSlot=m());try{a.proc.send?.({type:"debug-open",port:inspectorPortFor(w,a.debugSlot)})}catch{}}for(const a of O)try{a.proc.kill("SIGUSR1")}catch{}const _=Date.now()+2e3;for(;Date.now()<_&&n.some(a=>!a.inspectorUrl);)await new Promise(a=>setTimeout(a,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:w,fixedPorts:usesFixedInspectorPorts(),pods:n.map(a=>({id:a.id,inspectorUrl:a.inspectorUrl??null})),workers:O.map(a=>({name:a.name,port:a.debugPort||inspectorPortOf(a.inspectorUrl),inspectorUrl:a.inspectorUrl??null}))},null,2)+`
5
+ `);return}const p=matchGatewayRoute(e);if(p?.prependPrefix&&!e.url?.startsWith(p.prependPrefix)&&(e.url=p.prependPrefix+(e.url??"/")),p?.target){applyGatewayMiddleware(e),u.web(e,t,{target:p.target,autoRewrite:typeof p.target=="string",changeOrigin:!0},l=>{console.error(`[parent] proxy error to ${p.target}:`,l),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${p.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const c=L();if(!c){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const l=s.map(n=>`#${n.id}: ${n.lastError??"no error recorded"}`).join(`
6
6
  `);t.end(`No healthy children available. POST /webpack/reload to recover.
7
7
 
8
8
  Last errors:
9
- ${a}`);return}d.web(e,t,{target:{socketPath:c.socketPath},headers:{"x-proxy-child-id":String(c.id)}},a=>{console.error(`[parent] proxy error to Child#${c.id}:`,a),c.healthy=!1,c.lastError=String(a?.stack||a),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from Child#${c.id}. Try /webpack/reload.
10
- ${c.lastError}`)})},"requestHandler");let C,U,te;const q=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(c=>c.host).filter(c=>!!c),p=path.join(process.cwd(),"node_modules",".cache","davnx-webpack");U=e.getCertificateSync(p,SERVICE_NAME||"lensmcp.dev",t),te=e.caCertPath(),C=require("node:https").createServer({key:U,cert:U},H)}else C=http.createServer(H);const ce=l((e,t,p)=>{const c=matchGatewayRoute(e);if(c?.prependPrefix&&!e.url?.startsWith(c.prependPrefix)&&(e.url=c.prependPrefix+(e.url??"/")),c?.target){d.ws(e,t,p,{target:c.target,changeOrigin:!0},()=>t.destroy());return}const a=L();if(!a){t.destroy();return}d.ws(e,t,p,{target:{socketPath:a.socketPath}},()=>t.destroy())},"upgradeHandler");C.on("upgrade",ce);const ae=2e3;let ie=!1;C.on("error",e=>{if(e.code==="EADDRINUSE"){ie||(ie=!0,console.warn(`[parent] public port ${r} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${ae/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!b&&!C.listening&&C.listen(r)},ae).unref?.();return}console.error("[parent] public server error:",e)}),await Z(y);for(const e of WORKER_NAMES)ee(e);C.listen(r,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${r}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),te&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${te}`)),console.log(`POST ${e}://localhost:${r}/webpack/reload to trigger rolling swap/respawn`)});const B=Number(process.env.INTERNAL_PORT||0);if(B>0){const e=l((p,c)=>{const a=L();if(!a){c.statusCode=503,c.setHeader("content-type","text/plain; charset=utf-8"),c.end("No healthy children available.");return}d.web(p,c,{target:{socketPath:a.socketPath}},o=>{a.healthy=!1,a.lastError=String(o?.stack||o),c.statusCode=502,c.end(`Upstream error from Child#${a.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(p,c,a)=>{const o=L();if(!o){c.destroy();return}d.ws(p,c,a,{target:{socketPath:o.socketPath}},()=>c.destroy())}),t.on("error",p=>{console.warn(`[parent] internal port ${B} unavailable (${p.code}).`)}),t.listen(B,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${B}`)}),q.push(t)}const ue=(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of ue){if(!Number.isInteger(e)||e===Number(r))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:U,cert:U},H):http.createServer(H);t.on("upgrade",ce),t.on("error",p=>{console.warn(`[parent] extra port ${e} unavailable (${p.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),q.push(t)}const re=path.join(SOCK_DIR,"parent.sock");claimSock(re);const oe=http.createServer(H);oe.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),oe.listen(re),q.push(oe);const le=l(async e=>{b=!0,console.log(`[${e}] [parent] shutting down\u2026`),cleanupSock(re);for(const t of q)try{t.close()}catch{}for(const t of R)try{t.proc.kill("SIGTERM")}catch{}for(const t of n){try{t.proc.kill("SIGTERM")}catch{}t.socketPath&&cleanupSock(t.socketPath,t.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{le(e)}));const K=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(K)&&K>0){const e=setInterval(()=>{try{process.kill(K,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${K}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),le("gateway-gone")}},5e3);e.unref()}})()}
9
+ ${l}`);return}u.web(e,t,{target:{socketPath:c.socketPath},headers:{"x-proxy-child-id":String(c.id)}},l=>{console.error(`[parent] proxy error to Child#${c.id}:`,l),c.healthy=!1,c.lastError=String(l?.stack||l),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from Child#${c.id}. Try /webpack/reload.
10
+ ${c.lastError}`)})},"requestHandler");let x,H,re;const K=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(c=>c.host).filter(c=>!!c),p=path.join(process.cwd(),"node_modules",".cache","davnx-webpack");H=e.getCertificateSync(p,SERVICE_NAME||"lensmcp.dev",t),re=e.caCertPath(),x=require("node:https").createServer({key:H,cert:H},F)}else x=http.createServer(F);const ie=i((e,t,p)=>{const c=matchGatewayRoute(e);if(c?.prependPrefix&&!e.url?.startsWith(c.prependPrefix)&&(e.url=c.prependPrefix+(e.url??"/")),c?.target){u.ws(e,t,p,{target:c.target,changeOrigin:!0},()=>t.destroy());return}const l=L();if(!l){t.destroy();return}u.ws(e,t,p,{target:{socketPath:l.socketPath}},()=>t.destroy())},"upgradeHandler");x.on("upgrade",ie);const ae=2e3;let le=!1;x.on("error",e=>{if(e.code==="EADDRINUSE"){le||(le=!0,console.warn(`[parent] public port ${r} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${ae/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!T&&!x.listening&&x.listen(r)},ae).unref?.();return}console.error("[parent] public server error:",e)}),await ee(y);for(const e of WORKER_NAMES)te(e);x.listen(r,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${r}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),re&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${re}`)),console.log(`POST ${e}://localhost:${r}/webpack/reload to trigger rolling swap/respawn`)});const V=Number(process.env.INTERNAL_PORT||0);if(V>0){const e=i((p,c)=>{const l=L();if(!l){c.statusCode=503,c.setHeader("content-type","text/plain; charset=utf-8"),c.end("No healthy children available.");return}u.web(p,c,{target:{socketPath:l.socketPath}},n=>{l.healthy=!1,l.lastError=String(n?.stack||n),c.statusCode=502,c.end(`Upstream error from Child#${l.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(p,c,l)=>{const n=L();if(!n){c.destroy();return}u.ws(p,c,l,{target:{socketPath:n.socketPath}},()=>c.destroy())}),t.on("error",p=>{console.warn(`[parent] internal port ${V} unavailable (${p.code}).`)}),t.listen(V,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${V}`)}),K.push(t)}const ue=(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of ue){if(!Number.isInteger(e)||e===Number(r))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:H,cert:H},F):http.createServer(F);t.on("upgrade",ie),t.on("error",p=>{console.warn(`[parent] extra port ${e} unavailable (${p.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),K.push(t)}const oe=path.join(SOCK_DIR,"parent.sock");claimSock(oe);const ne=http.createServer(F);ne.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),ne.listen(oe),K.push(ne);const pe=i(async e=>{T=!0,console.log(`[${e}] [parent] shutting down\u2026`),cleanupSock(oe);for(const t of K)try{t.close()}catch{}for(const t of O)try{t.proc.kill("SIGTERM")}catch{}for(const t of s){try{t.proc.kill("SIGTERM")}catch{}t.socketPath&&cleanupSock(t.socketPath,t.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{pe(e)}));const X=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(X)&&X>0){const e=setInterval(()=>{try{process.kill(X,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${X}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),pe("gateway-gone")}},5e3);e.unref()}})()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.21.4",
3
+ "version": "1.22.0",
4
4
  "description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -57,11 +57,11 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lensmcp/core": "1.21.4",
61
- "@lensmcp/node-instrumentation": "1.21.4",
62
- "@lensmcp/nx-plugin": "1.21.4",
63
- "fastify": "^5.0.0",
64
- "@fastify/reply-from": "^12.0.0",
60
+ "@lensmcp/core": "1.22.0",
61
+ "@lensmcp/node-instrumentation": "1.22.0",
62
+ "@lensmcp/nx-plugin": "1.22.0",
63
+ "fastify": "^5.8.5",
64
+ "@fastify/reply-from": "^12.6.2",
65
65
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
66
66
  "fork-ts-checker-webpack-plugin": "^9.0.0",
67
67
  "glob": "^11.0.0",