@lensmcp/cluster 1.23.1 → 1.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -115,7 +115,7 @@ One front door, two transports sharing one routing brain:
115
115
  - **Routing** — every `cluster` declaration becomes a route; requests match by hostname (exact beats `*.suffix` wildcard) then by most-specific path prefix. A *claimed* host's unmatched root is served by the gateway itself (`/health` aggregate, `/.well-known/...`, CORS + preflights); an *unclaimed* host can only reach the `default` route or 404.
116
116
  - **Dev transport** — `node:http` + `http-proxy`. It owns the unix-socket pods: **scale-from-zero** on first request, autoscale while requests overlap, idle-kill, supervised reforks, and ghost-socket sweeping.
117
117
  - **Prod transport** — the same `matchRoute` composed over **Fastify + @fastify/reply-from (undici)** (connection-reusing, HTTP/1.1 pipelined; benchmarks materially lower CPU/req than http-proxy), shipped as a **Node 26 distroless** image. No process management — prod pods are always-on (k8s keeps them up).
118
- - **Edge auth + identity** — for `jwt` routes the gateway verifies the end-user token (dev: HS256 shared secret; prod: RS256 via JWKS) and stamps trusted identity headers (`x-user-id`, `x-tenant-id`, roles, perms) onto the forwarded request; `internal.<host>` routes require the CALLER's own `x-api-key`, and once it is validated the `x-tenant-id` the caller named is forwarded to the callee — production's service-to-service path is direct, so the callee reads it there too; every other identity header stays the gateway's. Per-path **ABAC** rules (a `permission` or full predicate over verified claims + IP/headers, scoped by HTTP method) are enforced at the edge → 403.
118
+ - **Edge auth + identity** — for `jwt` routes the gateway verifies the end-user token (dev: HS256 shared secret; prod: RS256 via JWKS) and stamps trusted identity headers (`x-user-id`, `x-tenant-id`, roles, perms) onto the forwarded request; `internal.<host>` routes require the CALLER's own `x-api-key`, and once it is validated the `x-tenant-id` and `x-user-id` the caller named are forwarded to the callee — production's service-to-service path is direct, so the callee reads them there too; roles, permissions and every other owned header stay the gateway's. Per-path **ABAC** rules (a `permission` or full predicate over verified claims + IP/headers, scoped by HTTP method) are enforced at the edge → 403.
119
119
  - **Rollout** — per-request upstream selection supports **weighted** cohorts (smooth nginx-style split for canary / blue-green) and **attribute-based targeting** (route by user / tenant / IP / header / sticky device key, consistent-hashed so a ramp moves a key across the boundary at most once). The serving version rides on `x-lensmcp-version` and the trace.
120
120
 
121
121
  ## How it fits
@@ -1 +1 @@
1
- "use strict";var p=Object.defineProperty;var a=(t,r)=>p(t,"name",{value:r,configurable:!0});var u=Object.defineProperty,o=a((t,r)=>u(t,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=gatewayExecutor;const devkit_1=require("@nx/devkit"),gateway_lib_1=require("./gateway.lib"),server_1=require("./runtime/server");async function gatewayExecutor(t,r){process.on("uncaughtException",e=>{if((e.syscall==="read"||e.syscall==="write")&&server_1.BENIGN_SOCKET_ERRNOS.has(e.code??"")){console.warn(`[gateway] survived a stray socket ${e.syscall} error (${e.code}) \u2014 one connection dropped, the daemon lives`);return}console.error(e),process.exit(1)});let c;try{let e;try{const s=await(0,devkit_1.createProjectGraphAsync)();e=Object.fromEntries(Object.entries(s.dependencies).map(([n,i])=>[n,i.map(l=>l.target)]))}catch{}c=await(0,gateway_lib_1.startGateway)(t,{...r,...e?{projectDependencies:e}:{}})}catch(e){return console.error(e.message),{success:!1}}return new Promise(e=>{const s=o(()=>{c.stop().then(()=>e({success:!0}))},"stop");process.on("SIGINT",s),process.on("SIGTERM",s)})}a(gatewayExecutor,"gatewayExecutor"),o(gatewayExecutor,"gatewayExecutor"),module.exports=gatewayExecutor,module.exports.default=gatewayExecutor,module.exports.startGateway=gateway_lib_1.startGateway;
1
+ "use strict";var u=Object.defineProperty;var i=(t,r)=>u(t,"name",{value:r,configurable:!0});var p=Object.defineProperty,n=i((t,r)=>p(t,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=gatewayExecutor;const devkit_1=require("@nx/devkit"),gateway_lib_1=require("./gateway.lib"),server_1=require("./runtime/server");async function gatewayExecutor(t,r){process.on("uncaughtException",e=>{if((e.syscall==="read"||e.syscall==="write")&&server_1.BENIGN_SOCKET_ERRNOS.has(e.code??"")){console.warn(`[gateway] survived a stray socket ${e.syscall} error (${e.code}) \u2014 one connection dropped, the daemon lives`);return}console.error(e),process.exit(1)});let l;try{let e;try{const s=await(0,devkit_1.createProjectGraphAsync)();e=Object.fromEntries(Object.entries(s.dependencies).map(([o,c])=>[o,c.map(a=>a.target)]))}catch{}l=await(0,gateway_lib_1.startGateway)(t,{...r,...e?{projectDependencies:e}:{}})}catch(e){return console.error(e.message),{success:!1}}return new Promise(e=>{let s=!1;const o=n(c=>{if(s)return;s=!0;let a=!0;try{process.kill(process.ppid,0)}catch{a=!1}console.log(`[gateway] received ${c} (pid ${process.pid}, parent ${process.ppid} ${a?"alive":"gone \u2014 orphaned"}) \u2014 shutting down`),l.stop().then(()=>e({success:!0}))},"stop");process.on("SIGINT",()=>o("SIGINT")),process.on("SIGTERM",()=>o("SIGTERM"))})}i(gatewayExecutor,"gatewayExecutor"),n(gatewayExecutor,"gatewayExecutor"),module.exports=gatewayExecutor,module.exports.default=gatewayExecutor,module.exports.startGateway=gateway_lib_1.startGateway;
@@ -1 +1 @@
1
- "use strict";var y=Object.defineProperty;var p=(n,s)=>y(n,"name",{value:s,configurable:!0});var h=Object.defineProperty,d=p((n,s)=>h(n,"name",{value:s,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEdgeAuth=createEdgeAuth;const manifest_1=require("../manifest"),dev_auth_1=require("./dev-auth"),edge_1=require("./edge");function createEdgeAuth(n,s,f){const{traceStep:o}=s;return{internal:d((e,k,r)=>{const l=String(e.headers["x-api-key"]??""),a=n.keyToProject.get(l);if(!a)return o(r,"auth",{mode:"x-api-key",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};const t=n.keyToWorkspace?.get(l),u=k.svc?.wsKey;if(t&&u&&t!==u)return o(r,"auth",{mode:"x-api-key",ok:!1,reason:"cross-workspace",caller:a,callerWorkspace:t,routeWorkspace:u}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};e.headers["x-api-key-id"]=a,t?e.headers["x-api-key-workspace"]=t:delete e.headers["x-api-key-workspace"],e.__lensmcpCaller=a,delete e.headers["x-api-key"];const i=(0,edge_1.callerTenantOf)(e);return i!==void 0&&(e.headers["x-tenant-id"]=i),o(r,"auth",{mode:"x-api-key",ok:!0,caller:a,...t?{callerWorkspace:t}:{},...i!==void 0?{callerTenant:!0}:{}}),{ok:!0,caller:a}},"internal"),public:d((e,k,r,l)=>{const a=k.auth;if(a){const t=l??(0,edge_1.canonicalPath)(e.url??"/")??(e.url??"/").split("?")[0],u=(e.method??"GET").toUpperCase(),i=(0,manifest_1.authRuleFor)(a,t,u);if(i.mode==="jwt"){const m=String(e.headers.host??"").split(":")[0].toLowerCase(),c=(0,dev_auth_1.verifyDevJwt)(e,f?.for(m));return c?i.rule&&!(0,manifest_1.evalRule)(i.rule,(0,dev_auth_1.devAttrs)(e,c,t))?(o(r,"authz",{ok:!1}),{ok:!1,status:403,reason:"forbidden",detail:"Forbidden: insufficient permission"}):((0,dev_auth_1.stampIdentity)(e,c),o(r,"auth",{mode:"jwt",ok:!0}),{ok:!0,claims:c}):(o(r,"auth",{mode:"jwt",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Unauthorized: invalid or missing token"})}return o(r,"auth",{mode:i.mode}),{ok:!0}}return o(r,"auth",{mode:"public",unconfigured:!0}),{ok:!0}},"publicAuth")}}p(createEdgeAuth,"createEdgeAuth"),d(createEdgeAuth,"createEdgeAuth");
1
+ "use strict";var y=Object.defineProperty;var f=(n,u)=>y(n,"name",{value:u,configurable:!0});var h=Object.defineProperty,d=f((n,u)=>h(n,"name",{value:u,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEdgeAuth=createEdgeAuth;const manifest_1=require("../manifest"),dev_auth_1=require("./dev-auth"),edge_1=require("./edge");function createEdgeAuth(n,u,m){const{traceStep:o}=u;return{internal:d((e,k,r)=>{const c=String(e.headers["x-api-key"]??""),a=n.keyToProject.get(c);if(!a)return o(r,"auth",{mode:"x-api-key",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};const t=n.keyToWorkspace?.get(c),l=k.svc?.wsKey;if(t&&l&&t!==l)return o(r,"auth",{mode:"x-api-key",ok:!1,reason:"cross-workspace",caller:a,callerWorkspace:t,routeWorkspace:l}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};e.headers["x-api-key-id"]=a,t?e.headers["x-api-key-workspace"]=t:delete e.headers["x-api-key-workspace"],e.__lensmcpCaller=a,delete e.headers["x-api-key"];const i=(0,edge_1.callerContextOf)(e);for(const[p,s]of Object.entries(i))e.headers[p]=s;return o(r,"auth",{mode:"x-api-key",ok:!0,caller:a,...t?{callerWorkspace:t}:{},...i["x-tenant-id"]!==void 0?{callerTenant:!0}:{},...i["x-user-id"]!==void 0?{callerUser:!0}:{}}),{ok:!0,caller:a}},"internal"),public:d((e,k,r,c)=>{const a=k.auth;if(a){const t=c??(0,edge_1.canonicalPath)(e.url??"/")??(e.url??"/").split("?")[0],l=(e.method??"GET").toUpperCase(),i=(0,manifest_1.authRuleFor)(a,t,l);if(i.mode==="jwt"){const p=String(e.headers.host??"").split(":")[0].toLowerCase(),s=(0,dev_auth_1.verifyDevJwt)(e,m?.for(p));return s?i.rule&&!(0,manifest_1.evalRule)(i.rule,(0,dev_auth_1.devAttrs)(e,s,t))?(o(r,"authz",{ok:!1}),{ok:!1,status:403,reason:"forbidden",detail:"Forbidden: insufficient permission"}):((0,dev_auth_1.stampIdentity)(e,s),o(r,"auth",{mode:"jwt",ok:!0}),{ok:!0,claims:s}):(o(r,"auth",{mode:"jwt",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Unauthorized: invalid or missing token"})}return o(r,"auth",{mode:i.mode}),{ok:!0}}return o(r,"auth",{mode:"public",unconfigured:!0}),{ok:!0}},"publicAuth")}}f(createEdgeAuth,"createEdgeAuth"),d(createEdgeAuth,"createEdgeAuth");
@@ -37,25 +37,35 @@ export declare const sendError: (res: http.ServerResponse, req: http.IncomingMes
37
37
  */
38
38
  export declare const GATEWAY_OWNED_HEADERS: string[];
39
39
  /**
40
- * Where {@link stripOwnedHeaders} sets the caller's `x-tenant-id` aside. Read back ONLY by the internal-route
41
- * auth, once the calling service's own key has been validated ({@link callerTenantOf}).
40
+ * The CONTEXT a calling service names on an east-west call — the tenant it works in and the user it acts
41
+ * for. Never authority: `x-user-roles` / `x-user-permissions` are not in this list and never come back.
42
42
  */
43
- export declare const CALLER_TENANT: unique symbol;
43
+ export declare const CALLER_CONTEXT_HEADERS: readonly ["x-tenant-id", "x-user-id"];
44
+ /** One of {@link CALLER_CONTEXT_HEADERS}. */
45
+ export type CallerContextHeader = (typeof CALLER_CONTEXT_HEADERS)[number];
46
+ /** The caller context {@link stripOwnedHeaders} set aside — only the headers that held a usable value. */
47
+ export type CallerContext = Readonly<Partial<Record<CallerContextHeader, string>>>;
48
+ /**
49
+ * Where {@link stripOwnedHeaders} sets the caller context aside. Read back ONLY by the internal-route
50
+ * auth, once the calling service's own key has been validated ({@link callerContextOf}).
51
+ */
52
+ export declare const CALLER_CONTEXT: unique symbol;
44
53
  /**
45
54
  * Strip every gateway-owned header at ingress — the HTTP handler and the WS upgrade both call this —
46
- * keeping the caller's `x-tenant-id` aside on the request.
55
+ * keeping the caller context ({@link CALLER_CONTEXT_HEADERS}) aside on the request.
47
56
  *
48
- * Why that one header survives for an internal route (and nothing else does): service-to-service traffic
57
+ * Why those two headers survive for an internal route (and nothing else does): service-to-service traffic
49
58
  * goes DIRECT in production — the caller's pod calls the callee's cluster service, with no gateway in
50
- * between — so the callee receives the tenant its caller named and authorizes the call itself (a
51
- * per-purpose secret, for one). Deleting it on the dev `internal.<host>` made dev refuse calls prod
52
- * accepts, and pushed services onto each other's pod ports to get around the gateway. It comes back only
53
- * after the caller has proven which service it is; the `x-user-*` identity never does, and a public
54
- * route's `x-tenant-id` is only ever the one the verified token carries.
59
+ * between — so the callee receives the tenant and the user its caller named, and authorizes the call
60
+ * itself (a per-purpose secret, for one). Deleting them on the dev `internal.<host>` made dev refuse calls
61
+ * prod accepts, and pushed services onto each other's pod ports to get around the gateway. They come back
62
+ * only after the caller has proven which service it is. Roles and permissions never do: a service names
63
+ * whom it acts for, never what that person may do. And a PUBLIC route's identity is only ever the verified
64
+ * token's.
55
65
  */
56
66
  export declare function stripOwnedHeaders(req: http.IncomingMessage): void;
57
- /** The tenant the caller named — undefined when it sent none, or a value not worth handing back. */
58
- export declare const callerTenantOf: (req: http.IncomingMessage) => string | undefined;
67
+ /** The context the caller named — empty when it sent none, or only values not worth handing back. */
68
+ export declare const callerContextOf: (req: http.IncomingMessage) => CallerContext;
59
69
  /**
60
70
  * Is this `Origin` one of the hosts THIS gateway routes?
61
71
  *
@@ -1 +1 @@
1
- "use strict";var d=Object.defineProperty;var i=(e,r)=>d(e,"name",{value:r,configurable:!0});var p=Object.defineProperty,o=i((e,r)=>p(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.isRoutedOrigin=exports.callerTenantOf=exports.CALLER_TENANT=exports.GATEWAY_OWNED_HEADERS=exports.sendError=exports.stampForwardingHeaders=exports.clientIpOf=exports.resolveClientIp=exports.CLIENT_IP=exports.rememberRawTarget=exports.rawTargetOf=exports.RAW_TARGET=exports.canonicalPath=exports.canonicalize=exports.ENCODED_STRUCTURE=void 0,exports.stripOwnedHeaders=stripOwnedHeaders;const gateway_errors_1=require("../gateway-errors"),client_ip_1=require("../client-ip"),manifest_1=require("../manifest");var canonical_path_1=require("../canonical-path");Object.defineProperty(exports,"ENCODED_STRUCTURE",{enumerable:!0,get:o(function(){return canonical_path_1.ENCODED_STRUCTURE},"get")}),Object.defineProperty(exports,"canonicalize",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalize},"get")}),Object.defineProperty(exports,"canonicalPath",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalPath},"get")}),Object.defineProperty(exports,"RAW_TARGET",{enumerable:!0,get:o(function(){return canonical_path_1.RAW_TARGET},"get")}),Object.defineProperty(exports,"rawTargetOf",{enumerable:!0,get:o(function(){return canonical_path_1.rawTargetOf},"get")}),Object.defineProperty(exports,"rememberRawTarget",{enumerable:!0,get:o(function(){return canonical_path_1.rememberRawTarget},"get")}),exports.CLIENT_IP=Symbol.for("lensmcp.gateway.clientIp");const FORWARDED=Symbol.for("lensmcp.gateway.forwarded"),peerOnly=(0,client_ip_1.createClientIpResolver)({}),resolverOf=o(e=>e.clientIp??peerOnly,"resolverOf"),peerOf=o(e=>e.socket?.remoteAddress??"","peerOf"),resolveClientIp=o((e,r)=>{const t=resolverOf(e).clientIpOf(r,peerOf(r));return r[exports.CLIENT_IP]=t,t},"resolveClientIp");exports.resolveClientIp=resolveClientIp;const clientIpOf=o(e=>e[exports.CLIENT_IP]??peerOf(e),"clientIpOf");exports.clientIpOf=clientIpOf;const stampForwardingHeaders=o((e,r)=>{const t=r;if(t[FORWARDED])return;t[FORWARDED]=!0;const s=peerOf(r);r.headers["x-forwarded-for"]=resolverOf(e).forwardedFor(r,s),r.headers["x-forwarded-proto"]=e.https?"https":"http";const n=r.headers.host;(resolverOf(e).trustProxyHops===0||!r.headers["x-forwarded-host"])&&(n?r.headers["x-forwarded-host"]=n:delete r.headers["x-forwarded-host"])},"stampForwardingHeaders");exports.stampForwardingHeaders=stampForwardingHeaders;const sendError=o((e,r,t,s,n,c,l)=>{const a=(0,gateway_errors_1.ensureRequestId)(r);return e.headersSent||e.writeHead(t,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":a,...l??{}}),e.end((0,gateway_errors_1.gatewayErrorBody)(t,s,a,n,c)),a},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-api-key-workspace","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"],exports.CALLER_TENANT=Symbol.for("lensmcp.gateway.callerTenant");const CALLER_TENANT_VALUE=/^[\x21-\x2b\x2d-\x7e]{1,128}$/;function stripOwnedHeaders(e){const r=e.headers["x-tenant-id"];e[exports.CALLER_TENANT]=typeof r=="string"&&CALLER_TENANT_VALUE.test(r)?r:void 0;for(const t of exports.GATEWAY_OWNED_HEADERS)delete e.headers[t]}i(stripOwnedHeaders,"stripOwnedHeaders"),o(stripOwnedHeaders,"stripOwnedHeaders");const callerTenantOf=o(e=>e[exports.CALLER_TENANT],"callerTenantOf");exports.callerTenantOf=callerTenantOf;const isRoutedOrigin=o((e,r)=>{if(!e||e==="null")return!1;let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!1}return t?t==="localhost"||t==="127.0.0.1"||t==="::1"?!0:r.some(s=>!!s.host&&(0,manifest_1.hostMatches)(s.host,t)):!1},"isRoutedOrigin");exports.isRoutedOrigin=isRoutedOrigin;const cors=o((e,r,t)=>{const s=e.headers.origin;typeof s!="string"||s===""||(r.setHeader("vary","Origin"),(0,exports.isRoutedOrigin)(s,t)&&(r.setHeader("access-control-allow-origin",s),r.setHeader("access-control-allow-credentials","true")))},"cors");exports.cors=cors;const corsPreflight=o((e,r,t,s)=>{(0,exports.cors)(e,r,s),r.writeHead(204,{"access-control-allow-methods":t,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
1
+ "use strict";var d=Object.defineProperty;var c=(e,r)=>d(e,"name",{value:r,configurable:!0});var p=Object.defineProperty,o=c((e,r)=>p(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.isRoutedOrigin=exports.callerContextOf=exports.CALLER_CONTEXT=exports.CALLER_CONTEXT_HEADERS=exports.GATEWAY_OWNED_HEADERS=exports.sendError=exports.stampForwardingHeaders=exports.clientIpOf=exports.resolveClientIp=exports.CLIENT_IP=exports.rememberRawTarget=exports.rawTargetOf=exports.RAW_TARGET=exports.canonicalPath=exports.canonicalize=exports.ENCODED_STRUCTURE=void 0,exports.stripOwnedHeaders=stripOwnedHeaders;const gateway_errors_1=require("../gateway-errors"),client_ip_1=require("../client-ip"),manifest_1=require("../manifest");var canonical_path_1=require("../canonical-path");Object.defineProperty(exports,"ENCODED_STRUCTURE",{enumerable:!0,get:o(function(){return canonical_path_1.ENCODED_STRUCTURE},"get")}),Object.defineProperty(exports,"canonicalize",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalize},"get")}),Object.defineProperty(exports,"canonicalPath",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalPath},"get")}),Object.defineProperty(exports,"RAW_TARGET",{enumerable:!0,get:o(function(){return canonical_path_1.RAW_TARGET},"get")}),Object.defineProperty(exports,"rawTargetOf",{enumerable:!0,get:o(function(){return canonical_path_1.rawTargetOf},"get")}),Object.defineProperty(exports,"rememberRawTarget",{enumerable:!0,get:o(function(){return canonical_path_1.rememberRawTarget},"get")}),exports.CLIENT_IP=Symbol.for("lensmcp.gateway.clientIp");const FORWARDED=Symbol.for("lensmcp.gateway.forwarded"),peerOnly=(0,client_ip_1.createClientIpResolver)({}),resolverOf=o(e=>e.clientIp??peerOnly,"resolverOf"),peerOf=o(e=>e.socket?.remoteAddress??"","peerOf"),resolveClientIp=o((e,r)=>{const t=resolverOf(e).clientIpOf(r,peerOf(r));return r[exports.CLIENT_IP]=t,t},"resolveClientIp");exports.resolveClientIp=resolveClientIp;const clientIpOf=o(e=>e[exports.CLIENT_IP]??peerOf(e),"clientIpOf");exports.clientIpOf=clientIpOf;const stampForwardingHeaders=o((e,r)=>{const t=r;if(t[FORWARDED])return;t[FORWARDED]=!0;const s=peerOf(r);r.headers["x-forwarded-for"]=resolverOf(e).forwardedFor(r,s),r.headers["x-forwarded-proto"]=e.https?"https":"http";const n=r.headers.host;(resolverOf(e).trustProxyHops===0||!r.headers["x-forwarded-host"])&&(n?r.headers["x-forwarded-host"]=n:delete r.headers["x-forwarded-host"])},"stampForwardingHeaders");exports.stampForwardingHeaders=stampForwardingHeaders;const sendError=o((e,r,t,s,n,i,l)=>{const a=(0,gateway_errors_1.ensureRequestId)(r);return e.headersSent||e.writeHead(t,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":a,...l??{}}),e.end((0,gateway_errors_1.gatewayErrorBody)(t,s,a,n,i)),a},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-api-key-workspace","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"],exports.CALLER_CONTEXT_HEADERS=["x-tenant-id","x-user-id"],exports.CALLER_CONTEXT=Symbol.for("lensmcp.gateway.callerContext");const CALLER_CONTEXT_VALUE=/^[\x21-\x2b\x2d-\x7e]{1,128}$/;function stripOwnedHeaders(e){const r={};for(const t of exports.CALLER_CONTEXT_HEADERS){const s=e.headers[t];typeof s=="string"&&CALLER_CONTEXT_VALUE.test(s)&&(r[t]=s)}e[exports.CALLER_CONTEXT]=r;for(const t of exports.GATEWAY_OWNED_HEADERS)delete e.headers[t]}c(stripOwnedHeaders,"stripOwnedHeaders"),o(stripOwnedHeaders,"stripOwnedHeaders");const callerContextOf=o(e=>e[exports.CALLER_CONTEXT]??{},"callerContextOf");exports.callerContextOf=callerContextOf;const isRoutedOrigin=o((e,r)=>{if(!e||e==="null")return!1;let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!1}return t?t==="localhost"||t==="127.0.0.1"||t==="::1"?!0:r.some(s=>!!s.host&&(0,manifest_1.hostMatches)(s.host,t)):!1},"isRoutedOrigin");exports.isRoutedOrigin=isRoutedOrigin;const cors=o((e,r,t)=>{const s=e.headers.origin;typeof s!="string"||s===""||(r.setHeader("vary","Origin"),(0,exports.isRoutedOrigin)(s,t)&&(r.setHeader("access-control-allow-origin",s),r.setHeader("access-control-allow-credentials","true")))},"cors");exports.cors=cors;const corsPreflight=o((e,r,t,s)=>{(0,exports.cors)(e,r,s),r.writeHead(204,{"access-control-allow-methods":t,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
@@ -1,3 +1,3 @@
1
- "use strict";var he=Object.defineProperty;var v=(t,n)=>he(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=v((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types");function eastWestEnv(t,n){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const g=require("../../../basic-ssl").caCertPath();fs.existsSync(g)&&(a.NODE_EXTRA_CA_CERTS=g)}return a}v(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}v(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const g=l?.ignoreTests??!0;let S=0,y=0,_=!1;const w=new Set,b=await gitIgnoredPaths(t),h=i(R=>path.relative(t,R).split(path.sep).join("/"),"rel"),M=i(async(R,U=!0)=>{if(S>=SCAN_FILE_CAP){_=!0;return}let D;try{D=await fs.promises.readdir(R,{withFileTypes:!0})}catch{return}for(const K of D){if(S>=SCAN_FILE_CAP){_=!0;return}const x=K.name,V=path.join(R,x);if(K.isDirectory()){if(!U||x.startsWith(".")||SRC_EXCLUDE.has(x)||a?.has(x)||x.endsWith("-devserver")||b.has(`${h(V)}/`))continue;await M(V)}else if(K.isFile()){const Y=x.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(x.slice(Y)))continue;const P=h(V);if(b.has(P)||g&&isTestSourcePath(P)||w.has(P))continue;w.add(P),S+=1,y^=fnv1a(P)}}},"walk");if(n&&n.length>0){await M(t,!1);for(const R of n)await M(R)}else await M(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:w,truncated:_}}v(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}v(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let _=!0;for(const w of t.paths)if(!n.paths.has(w)){_=!1;break}if(_)return null}const g=[],S=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?g.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(_):y=!0);return{changedAt:a,added:g,removed:S,truncated:y,...l!==void 0?{windowMs:l}:{}}}v(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const g=n&&n>0?t-n:a;return Math.max(g,a)+l}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,g){const S=i(w=>{const b=path.relative(t,w).split(path.sep).filter(Boolean);return b.length===0||b[0].startsWith("..")?"":b[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),_=new Set([S]);for(const w of l)w.startsWith(".")||SRC_EXCLUDE.has(w)||g?.has(w)||w.endsWith("-devserver")||y.has(w)||_.add(w);return[..._].sort().map(w=>path.join(t,w))}v(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,g){return!(l-n<g.cooldownMs||a<=0||a-t<=g.graceMs||l-a<g.settleMs)}v(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const g=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=g).map(y=>path.resolve(y.path)));return l.every(y=>S.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const g=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<g||a-n<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}v(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,g=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:w}=n,b=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>w(o,s,r,{wsKey:e.wsKey,...c},void 0,b(e)),"emitSvc"),M=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),R=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const d of s.split(`
2
- `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const j=Number(p[1]),m=Number(p[2]),A=p[3];if(A.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(A)&&!(A.includes("/node_modules/nx/")&&!A.includes(e.root))&&!(j===process.pid||j===r)&&!(r!==void 0&&m===r)){try{process.kill(-m,"SIGKILL")}catch{try{process.kill(j,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),U=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[p,j]=fs.existsSync(d)?[process.execPath,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=l(p,j,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1);const A=i(u=>{const E=u.toString();for(const F of E.split(`
3
- `)){const{clean:C,broken:f,healthy:L}=(0,pod_output_1.classifyPodLine)(F),$=C;if(f){e.lastErrors.push(C),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const k=Date.now();(C!==e.lastErrorEmitSig||k-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=C,e.lastErrorEmitAt=k,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:C}))}L&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test($)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(O(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),R(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.())},250).unref?.())}},"sniff"),T=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",u=>{if(t.stopped())return;A(u);const E=`[${e.project}] ${u}`;process.stdout.write(E),T?.(E)}),m.stderr?.on("data",u=>{if(t.stopped())return;A(u);const E=`[${e.project}] ${u}`;process.stderr.write(E),T?.(E)}),m.on("exit",u=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped())||(console.log(`[gateway] ${r} exited (${u})`),h(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u}),u===0||e.buildFailed))return;const E=Date.now();if(e.restarts=(e.restarts??[]).filter(C=>E-C<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(E);const F=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),D(e))},F)}),m},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&R(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=U(e,e.generation,"active")},"spawnService"),K=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),x=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),V=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,x(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),P=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(M(e.pool)>0){const r=await V(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&O(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return P(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(M(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),O=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{H(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),H=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),ee=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const p=U(e,c,"standby"),j=(async()=>{const m=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],A=d+y;let T;for(;!t.stopped()&&Date.now()<A&&!(p.exitCode!==null||p.signalCode!==null);){M(e.pool);for(const u of e.pool.socks)if((await g(u,void 0,m))?.podGeneration===c){T=u;break}if(T)break;await sleep(_)}if(!T||t.stopped()){const u=t.stopped()?"gateway stopping":p.exitCode!==null||p.signalCode!==null?`standby exited (${p.exitCode??p.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{p.stdout?.removeAllListeners("data"),p.stderr?.removeAllListeners("data")}catch{}return S(p,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${u} \u2014 old generation g${r} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:u,durationMs:Date.now()-d}),setTimeout(()=>H(e),1200).unref?.(),!1}if(e.proc!==s)return S(p,"SIGTERM"),!1;e.proc=p,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return S(s,"SIGTERM"),setTimeout(()=>{H(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(T)} in ${Date.now()-d}ms; retiring g${r}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=j,j},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),O(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),O(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let z;const q=new Map,te=new Map;let J=!1,Q=0,X=0;const ae=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ie=i((e,o,s,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const p=sourceScopeDirs(t.root,d,s,o,r);return p.length>0?p:void 0},"scopeFor"),le=i(()=>{if(J||Date.now()-Q<types_1.POD_STALE_SCAN_MS)return;J=!0,Q=Date.now();const e=Q;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),d=Date.now(),p=sourceChangeWindowMs(d,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),j=z?diffSourceSets(z,c,d,p):null;if(j&&(t.sourceSetChangedAt=d,t.sourceSetChange=j,w("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:j.added,removed:j.removed,truncated:j.truncated})),z=c,!t.projectRoots){X=e;return}let m;try{m=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(f=>f.isDirectory()).map(f=>f.name)}catch{return}const A=ae(),T=new Set(t.services.map(f=>f.project));for(const f of t.routes)f.lens&&T.add(f.lens.project);const u=new Map;for(const f of T){const L=ie(f,m,A,s);if(!L)continue;const $=L.join("|"),k=u.get($)??{dirs:L,projects:[]};k.projects.push(f),u.set($,k)}const E=Date.now(),F=sourceChangeWindowMs(E,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);X=e;const C=i((f,L,$)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const k of f)t.sourceSetChangedFor.set(k,L),t.sourceSetChangesFor.set(k,$)},"stampScoped");for(const[f,{dirs:L,projects:$}]of u){const k=await sourceSetSnapshot(t.root,L,s,r),oe=q.get(f);q.set(f,k);const Z=new Map;for(const W of $){const I=te.get(W);if(I===void 0||I===f)continue;const N=Z.get(I)??[];N.push(W),Z.set(I,N)}for(const W of $)te.set(W,f);for(const[W,I]of Z){const N=q.get(W),B=N?diffSourceSets(N,k,E,F):j;B&&(C(I,N?E:d,B),w("info",`source file set changed \u2014 ${I.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:I,added:B.added,removed:B.removed,truncated:B.truncated}))}if(!oe)continue;const G=diffSourceSets(oe,k,E,F);G&&(C($,E,G),w("info",`source file set changed in ${$.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:$,added:G.added,removed:G.removed,truncated:G.truncated}))}for(const f of[...q.keys()])u.has(f)||q.delete(f)})().catch(()=>{}).finally(()=>{J=!1})},"maybeScanSourceSet"),de=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){ee(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;O(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{ge().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),ge=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){O(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=M(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&P(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await K(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,p=podRecycleReason(e,Date.now(),d?0:c,ce);if(p==="stale-source-set"&&!de(e,c))continue;p&&pe(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:M,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:O,markUp:P,startSweeper:ue,rollPod:ee}}v(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var ge=Object.defineProperty;var y=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=y((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason");function eastWestEnv(t,n){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const g=require("../../../basic-ssl").caCertPath();fs.existsSync(g)&&(a.NODE_EXTRA_CA_CERTS=g)}return a}y(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}y(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}y(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}y(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const g=l?.ignoreTests??!0;let S=0,_=0,E=!1;const w=new Set,L=await gitIgnoredPaths(t),f=i(D=>path.relative(t,D).split(path.sep).join("/"),"rel"),C=i(async(D,U=!0)=>{if(S>=SCAN_FILE_CAP){E=!0;return}let x;try{x=await fs.promises.readdir(D,{withFileTypes:!0})}catch{return}for(const K of x){if(S>=SCAN_FILE_CAP){E=!0;return}const P=K.name,q=path.join(D,P);if(K.isDirectory()){if(!U||P.startsWith(".")||SRC_EXCLUDE.has(P)||a?.has(P)||P.endsWith("-devserver")||L.has(`${f(q)}/`))continue;await C(q)}else if(K.isFile()){const Y=P.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(P.slice(Y)))continue;const b=f(q);if(L.has(b)||g&&isTestSourcePath(b)||w.has(b))continue;w.add(b),S+=1,_^=fnv1a(b)}}},"walk");if(n&&n.length>0){await C(t,!1);for(const D of n)await C(D)}else await C(t);return{signature:`${S}:${(_>>>0).toString(36)}`,paths:w,truncated:E}}y(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}y(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let E=!0;for(const w of t.paths)if(!n.paths.has(w)){E=!1;break}if(E)return null}const g=[],S=[];let _=t.truncated||n.truncated;for(const E of n.paths)t.paths.has(E)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?g.push(E):_=!0);for(const E of t.paths)n.paths.has(E)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(E):_=!0);return{changedAt:a,added:g,removed:S,truncated:_,...l!==void 0?{windowMs:l}:{}}}y(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const g=n&&n>0?t-n:a;return Math.max(g,a)+l}y(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,g){const S=i(w=>{const L=path.relative(t,w).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(n);if(!S)return[];const _=new Set(a.filter(Boolean)),E=new Set([S]);for(const w of l)w.startsWith(".")||SRC_EXCLUDE.has(w)||g?.has(w)||w.endsWith("-devserver")||_.has(w)||E.add(w);return[...E].sort().map(w=>path.join(t,w))}y(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}y(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,g){return!(l-n<g.cooldownMs||a<=0||a-t<=g.graceMs||l-a<g.settleMs)}y(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const g=n-a,S=new Set(t.recentEvents.filter(_=>_.at>=g).map(_=>path.resolve(_.path)));return l.every(_=>S.has(path.resolve(_)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}y(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}y(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const g=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<g||a-n<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}y(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}y(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,g=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),_=a.rollReadyMs??types_1.POD_ROLL_READY_MS,E=a.rollPollMs??750,{emit:w}=n,L=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),f=i((e,o,s,r,c)=>w(o,s,r,{wsKey:e.wsKey,...c},void 0,L(e)),"emitSvc"),C=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),D=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const d of s.split(`
2
+ `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const j=Number(p[1]),$=Number(p[2]),A=p[3];if(A.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(A)&&!(A.includes("/node_modules/nx/")&&!A.includes(e.root))&&!(j===process.pid||j===r)&&!(r!==void 0&&$===r)){try{process.kill(-$,"SIGKILL")}catch{try{process.kill(j,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),f(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),U=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[p,j]=fs.existsSync(d)?[process.execPath,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],$=l(p,j,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1);const A=i(u=>{const v=u.toString();for(const F of v.split(`
3
+ `)){const{clean:M,broken:h,healthy:T}=(0,pod_output_1.classifyPodLine)(F),m=M;if(h){e.lastErrors.push(M),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const k=Date.now();(M!==e.lastErrorEmitSig||k-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=M,e.lastErrorEmitAt=k,f(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:M}))}T&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(m)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),f(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(O(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),D(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.())},250).unref?.())}},"sniff"),R=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return $.stdout?.on("data",u=>{if(t.stopped())return;A(u);const v=`[${e.project}] ${u}`;process.stdout.write(v),R?.(v)}),$.stderr?.on("data",u=>{if(t.stopped())return;A(u);const v=`[${e.project}] ${u}`;process.stderr.write(v),R?.(v)}),$.on("exit",(u,v)=>{if(e.proc!==$||(e.proc=void 0,e.state="down",t.stopped()))return;const F=(0,exit_reason_1.describeExit)(u,v);if(console.log(`[gateway] ${r} exited (${u??"no code"}${v?`, ${v}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${F?` \u2014 ${F}`:""}`),f(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u,...v?{signal:v}:{}}),u===0||e.buildFailed)return;const M=Date.now();if(e.restarts=(e.restarts??[]).filter(T=>M-T<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){f(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(M);const h=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),f(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),x(e))},h)}),$},"launchServiceProcess"),x=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&D(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),f(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=U(e,e.generation,"active")},"spawnService"),K=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),q=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,P(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return f(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(C(e.pool)>0){const r=await q(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),f(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&O(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&x(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),f(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),f(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),O=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&f(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{H(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),H=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),ee=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),f(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const p=U(e,c,"standby"),j=(async()=>{const $=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],A=d+_;let R;for(;!t.stopped()&&Date.now()<A&&!(p.exitCode!==null||p.signalCode!==null);){C(e.pool);for(const u of e.pool.socks)if((await g(u,void 0,$))?.podGeneration===c){R=u;break}if(R)break;await sleep(E)}if(!R||t.stopped()){const u=t.stopped()?"gateway stopping":p.exitCode!==null||p.signalCode!==null?`standby exited (${p.exitCode??p.signalCode})`:`standby not ready within ${Math.round(_/1e3)}s`;try{p.stdout?.removeAllListeners("data"),p.stderr?.removeAllListeners("data")}catch{}return S(p,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${u} \u2014 old generation g${r} kept serving.`),f(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:u,durationMs:Date.now()-d}),setTimeout(()=>H(e),1200).unref?.(),!1}if(e.proc!==s)return S(p,"SIGTERM"),!1;e.proc=p,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return S(s,"SIGTERM"),setTimeout(()=>{H(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(R)} in ${Date.now()-d}ms; retiring g${r}.`),f(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=j,j},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){f(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),O(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),f(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),O(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let z;const V=new Map,te=new Map;let J=!1,Q=0,X=0;const ie=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ae=i((e,o,s,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const p=sourceScopeDirs(t.root,d,s,o,r);return p.length>0?p:void 0},"scopeFor"),le=i(()=>{if(J||Date.now()-Q<types_1.POD_STALE_SCAN_MS)return;J=!0,Q=Date.now();const e=Q;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),d=Date.now(),p=sourceChangeWindowMs(d,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),j=z?diffSourceSets(z,c,d,p):null;if(j&&(t.sourceSetChangedAt=d,t.sourceSetChange=j,w("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:j.added,removed:j.removed,truncated:j.truncated})),z=c,!t.projectRoots){X=e;return}let $;try{$=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(h=>h.isDirectory()).map(h=>h.name)}catch{return}const A=ie(),R=new Set(t.services.map(h=>h.project));for(const h of t.routes)h.lens&&R.add(h.lens.project);const u=new Map;for(const h of R){const T=ae(h,$,A,s);if(!T)continue;const m=T.join("|"),k=u.get(m)??{dirs:T,projects:[]};k.projects.push(h),u.set(m,k)}const v=Date.now(),F=sourceChangeWindowMs(v,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);X=e;const M=i((h,T,m)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const k of h)t.sourceSetChangedFor.set(k,T),t.sourceSetChangesFor.set(k,m)},"stampScoped");for(const[h,{dirs:T,projects:m}]of u){const k=await sourceSetSnapshot(t.root,T,s,r),oe=V.get(h);V.set(h,k);const Z=new Map;for(const W of m){const I=te.get(W);if(I===void 0||I===h)continue;const N=Z.get(I)??[];N.push(W),Z.set(I,N)}for(const W of m)te.set(W,h);for(const[W,I]of Z){const N=V.get(W),B=N?diffSourceSets(N,k,v,F):j;B&&(M(I,N?v:d,B),w("info",`source file set changed \u2014 ${I.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:I,added:B.added,removed:B.removed,truncated:B.truncated}))}if(!oe)continue;const G=diffSourceSets(oe,k,v,F);G&&(M(m,v,G),w("info",`source file set changed in ${m.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:m,added:G.added,removed:G.removed,truncated:G.truncated}))}for(const h of[...V.keys()])u.has(h)||V.delete(h)})().catch(()=>{}).finally(()=>{J=!1})},"maybeScanSourceSet"),de=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,f(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){ee(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),f(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;O(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),he=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){O(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=C(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&b(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await K(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,p=podRecycleReason(e,Date.now(),d?0:c,ce);if(p==="stale-source-set"&&!de(e,c))continue;p&&pe(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:C,spawnService:x,ensureUp:re,scaleUp:se,killSpawned:O,markUp:b,startSweeper:ue,rollPod:ee}}y(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}y(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/schema",
3
3
  "version": 2,
4
4
  "title": "Gateway executor",
5
- "description": "Workspace https front door. Reads every project's top-level `davnx` declaration ({ host, port|upstream, prependPrefix?, default? }), builds the hostname route table, serves a local-CA TLS cert with SANs for every host, and proxies requests + websocket upgrades. The optional middleware hook (JWT etc.) runs on every public request. Service-to-service traffic goes through each service's internal.<host>: the caller presents its own x-api-key, the middleware is skipped, and the x-tenant-id the caller names reaches the callee (as it does over production's direct service-to-service path). A service's internalPort is a loopback diagnostics listener, not the service-to-service path.",
5
+ "description": "Workspace https front door. Reads every project's top-level `davnx` declaration ({ host, port|upstream, prependPrefix?, default? }), builds the hostname route table, serves a local-CA TLS cert with SANs for every host, and proxies requests + websocket upgrades. The optional middleware hook (JWT etc.) runs on every public request. Service-to-service traffic goes through each service's internal.<host>: the caller presents its own x-api-key, the middleware is skipped, and the x-tenant-id and x-user-id the caller names reach the callee (as they do over production's direct service-to-service path; roles and permissions never do). A service's internalPort is a loopback diagnostics listener, not the service-to-service path.",
6
6
  "type": "object",
7
7
  "properties": {
8
8
  "ports": {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Why a pod died, in words (issues/foodguard/22). "socket hang up" was the only trace a pod left when
3
+ * it died mid-work: a crash, a SIGKILL and an OOM kill read the same, and the stderr that explained
4
+ * which was gone with the process. Pure helpers so the devserver's exit handler stays a few lines.
5
+ */
6
+ /** Keeps the last `max` lines of a child's output — a ring, so a chatty pod costs a bounded amount. */
7
+ export declare class TailBuffer {
8
+ private readonly max;
9
+ private readonly maxLineChars;
10
+ private readonly lines;
11
+ constructor(max?: number, maxLineChars?: number);
12
+ push(line: string): void;
13
+ snapshot(): string[];
14
+ }
15
+ /**
16
+ * A one-clause reading of an exit status. `null` for a status that needs no explaining (a clean exit
17
+ * or the stop signals the parent itself sends).
18
+ */
19
+ export declare function describeExit(code: number | null, signal: NodeJS.Signals | null): string | null;
20
+ /** The lines to print under a crash report: the reading (if any) and what the pod said last. */
21
+ export declare function crashReport(head: string, code: number | null, signal: NodeJS.Signals | null, tail: string[]): string;
package/exit-reason.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var l=Object.defineProperty;var s=(t,e)=>l(t,"name",{value:e,configurable:!0});var h=Object.defineProperty,i=s((t,e)=>h(t,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TailBuffer=void 0,exports.describeExit=describeExit,exports.crashReport=crashReport;class TailBuffer{static{s(this,"TailBuffer")}static{i(this,"TailBuffer")}constructor(e=20,o=500){this.max=e,this.maxLineChars=o,this.lines=[]}push(e){e&&(this.lines.push(e.length>this.maxLineChars?`${e.slice(0,this.maxLineChars)}\u2026`:e),this.lines.length>this.max&&this.lines.shift())}snapshot(){return[...this.lines]}}exports.TailBuffer=TailBuffer;function describeExit(t,e){return e==="SIGKILL"||t===137?"killed with SIGKILL \u2014 the kernel OOM killer, or something outside the dev cluster (`kill -9`, a container memory limit); not a crash in the app":e==="SIGSEGV"||e==="SIGBUS"||e==="SIGABRT"?`died on ${e} \u2014 a native crash or a V8 abort (a heap out-of-memory prints "FATAL ERROR" on stderr first)`:e==="SIGPIPE"?"died on SIGPIPE \u2014 it wrote to a closed pipe":e==="SIGHUP"?"received SIGHUP \u2014 its terminal or parent session went away":e==="SIGINT"||e==="SIGTERM"||t===0?null:t===134?"aborted (exit 134, SIGABRT) \u2014 usually a V8 heap out-of-memory":t===1?"exited 1 \u2014 an uncaught exception or an explicit process.exit(1)":t!==null?`exited ${t}`:null}s(describeExit,"describeExit"),i(describeExit,"describeExit");function crashReport(t,e,o,n){const a=describeExit(e,o),r=[t+(a?` \u2014 ${a}`:"")];if(n.length){r.push(` last ${n.length} line(s) of output before it died:`);for(const u of n)r.push(` | ${u}`)}else r.push(" (it wrote nothing to stdout/stderr before it died)");return r.join(`
2
+ `)}s(crashReport,"crashReport"),i(crashReport,"crashReport");
package/main.devserver.js CHANGED
@@ -1,10 +1,10 @@
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)+`
1
+ "use strict";var ge=Object.defineProperty;var g=(o,d)=>ge(o,"name",{value:d,configurable:!0});var he=Object.defineProperty,a=g((o,d)=>he(o,"name",{value:d,configurable:!0}),"a");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")),exit_reason_1=require("./exit-reason"),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"),a(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"),a(hostMatches,"hostMatches");function matchGatewayRoute(o){if(GATEWAY_ROUTES.length===0)return;const d=(o.headers.host||"").split(":")[0],f=o.url||"/";for(const E of GATEWAY_ROUTES)if(!(E.host&&!hostMatches(E.host,d))&&!(E.prefix&&!f.startsWith(E.prefix)))return E}g(matchGatewayRoute,"matchGatewayRoute"),a(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"),a(childSockPath,"childSockPath");function sockOwnerPath(o){return`${o}.owner`}g(sockOwnerPath,"sockOwnerPath"),a(sockOwnerPath,"sockOwnerPath");function isOwnerOnlyTransport(o){try{return typeof o.socket?.server?.address?.()=="string"}catch{return!1}}g(isOwnerOnlyTransport,"isOwnerOnlyTransport"),a(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"),a(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(o,d,f=process.env){if(!usesFixedInspectorPorts(f))return 0;const E=Math.trunc(o)+Math.trunc(d);return Number.isInteger(E)&&E>1024&&E<=65535?E:0}g(inspectorPortFor,"inspectorPortFor"),a(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"),a(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"),a(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"),a(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"),a(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"),a(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"),a(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"),a(importFresh,"importFresh");function debounce(o,d){let f=null;return(...E)=>{f&&clearTimeout(f),f=setTimeout(()=>o(...E),d)}}if(g(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){const o=a(f=>{if(!inspector.url())try{inspector.open(f,"127.0.0.1",!1)}catch(S){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,S.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const E=inspector.url();E&&process.send&&process.send({type:"inspector-url",url:E})},"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,E=!1,S,C=!1,k={app:null,handler:a((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=a(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})(),T=new Set;async function R(r,s){if(!r.closed&&(r.closed=!0,T.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"),a(R,"closeGeneration");async function j(r){for(;T.size>=M;){const y=T.values().next().value;if(!y)break;await R(y,`closing a still-draining generation early \u2014 ${T.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.`)}T.add(r);const s=Date.now(),h=a(()=>({inflight:r.inflight,work:G(r.workGen).count}),"busy");for(;W>0&&Date.now()-s<W;){if(r.closed)return;const y=h();if(y.inflight===0&&y.work===0)break;await new Promise(w=>{setTimeout(w,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(j,"V"),a(j,"drainThenClose");async function $(){if(E)return C=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),f;E=!0,process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(y,w)=>{console.error(`[child] uncaughtException (${w}):`,y)}),process.on("unhandledRejection",y=>{const w=y instanceof Error?y.stack||y.message:String(y);w.includes("FronteggContextScope")||w.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",y)});try{try{await importFresh(BUNDLE_PATH)}catch(y){if(!(y instanceof SyntaxError))throw y;console.warn(`[child] bundle was unreadable mid-write (${y.message}) \u2014 retrying the swap once`),await new Promise(w=>setTimeout(w,400)),await importFresh(BUNDLE_PATH)}}catch(y){throw E=!1,y}if(typeof global.createChildApp!="function")throw E=!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(y){throw E=!1,y}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&&j(m).catch(y=>console.error("[child] background close error:",y)),E=!1,C?(C=!1,$()):h}g($,"G"),a($,"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"],y=process.env.LENSMCP_SERVICE_KEY;if(y&&m!==y){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const w=G();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:E,inflight:k.inflight,oldestInflightAt:k.oldestAt,activeWork:w.count,oldestWorkAt:w.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...S?{watcher:S}:{},...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"&&(S=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=a(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){T.push(u),T.length>M&&T.splice(0,T.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");a(o,"emitLogLine");const d=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],f="\x1B[0m",E=Math.max(1,Number(process.env.CHILD_COUNT||1));let S=(()=>{const u=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(u)&&u>1024?u:0})();const C=20;S&&(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 (${S}+slot, workers ${S}+${C}+i). Any account on this host can connect to 127.0.0.1:${S} 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,T=[],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 j=a(u=>d[(u-1)%d.length],"colorFor"),$=a(u=>{if(E===1)return"";const r=`[child#${u}] `;return`${j(u)}${r}${f}`},"tagFor"),J=a(u=>u.startsWith("{")&&u.endsWith("}"),"isJsonLog"),U=a((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:a(w=>`
3
+ `+String(w),"stack")}}),m=a((w,N)=>{const I=w==="stdout"?process.stdout:process.stderr;try{s?.(N)}catch{}const A=J(N)?h(N):`${N}
4
+ `;I.write(`${r}${A}`),o(`${r}${A}`)},"write"),y=a((w,N)=>{if(!w)return;const I=readline.createInterface({input:w});I.on("line",A=>m(N,A)),I.on("close",()=>{})},"attach");y(u.stdout,"stdout"),y(u.stderr,"stderr")},"wireProcLogging"),b=a(u=>U(u.proc,$(u.id),r=>u.tail.push(r)),"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,"w"),a(m,"allocDebugSlot");let y=1,w=0,N=!1,I=E,A=[];function z(){return s.filter(e=>e.healthy&&typeof e.socketPath=="string")}g(z,"q"),a(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"),a(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"),a(Z,"readJsonBody");function L(){const e=z();if(e.length===0)return null;const t=w%e.length;return w=(w+1)%e.length,e[t]}g(L,"B"),a(L,"pickChild");async function q(){const e=y++,t=childSockPath(e);cleanupSock(t);const p=["--enable-source-maps"];__filename.endsWith(".ts")&&p.unshift("--require","@swc-node/register");const c=S?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(S,c))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:p}),n={id:e,proc:l,healthy:!1,lastError:null,debugSlot:c,tail:new exit_reason_1.TailBuffer};return l.on("message",_=>{if(!_||typeof _!="object")return;const i=_;if(i.type==="ready"){if(n.socketPath=String(i.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 i.type==="inspector-url"?(n.inspectorUrl=String(i.url),console.log(`[parent] Child#${n.id} Debugger listening on ${i.url}`)):i.type==="reloaded"?(n.socketPath=String(i.socketPath),n.healthy=!0,console.log(`[parent] Child#${n.id} hot-swapped on socket ${n.socketPath}`)):i.type==="reload-error"?(n.lastError=String(i.error||"unknown reload error"),console.error(`[parent] Child#${n.id} reload error: ${n.lastError}`),n.healthy=!1):i.type==="boot-error"&&(n.lastError=String(i.error||"unknown boot error"),console.error(`[parent] Child#${n.id} boot error: ${n.lastError}`),n.healthy=!1)}),l.on("exit",(_,i)=>{n.debugSlot>=0&&(h.delete(n.debugSlot),n.debugSlot=-1);const P=i==="SIGINT"||i==="SIGTERM"||_===0;if(P?console.log(`[parent] Child#${n.id} exited cleanly (code=${_}, signal=${i??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${n.id} crashed (code=${_}, signal=${i??"none"}, pid=${n.proc.pid}, ${n.healthy?"was serving":"was not ready"})`,_,i,n.tail.snapshot())),n.lastError||(n.lastError=`Child exited abnormally (code=${_}, signal=${i??"none"})`)),n.healthy=!1,cleanupSock(n.socketPath,n.proc.pid),n.socketPath=void 0,!P&&!N){const v=s.indexOf(n);v>=0&&s.splice(v,1);const B=Date.now();if(A=A.filter(D=>B-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(B),setTimeout(()=>{const D=s.filter(fe=>fe.proc.killed===!1).length;!N&&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"),a(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"),a(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"),a(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(S||9229,C+Math.max(0,WORKER_NAMES.indexOf(e)));c.push(S?`--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`,i=>{const P=/Debugger listening on (wss?:\/\/\S+)/.exec(i);P&&(_.inspectorUrl=P[1])}),n.on("exit",(i,P)=>{P==="SIGINT"||P==="SIGTERM"||i===0||console.error(`[parent] Worker "${e}" crashed (code=${i}, 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"),a(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"),a(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=a(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",i=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?"":T.slice(-n).join(""),B=i?v.replace(G,""):v;if(t.write(P?redactInspectorUrls(B):B),!_){t.end();return}const D={res:t,plain:i,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&&(S=l),S||(S=9229);const n=s.filter(i=>i.proc.exitCode===null&&i.proc.signalCode===null);for(const i of n){i.debugSlot<0&&(i.debugSlot=m());try{i.proc.send?.({type:"debug-open",port:inspectorPortFor(S,i.debugSlot)})}catch{}}for(const i of O)try{i.proc.kill("SIGUSR1")}catch{}const _=Date.now()+2e3;for(;Date.now()<_&&n.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:S,fixedPorts:usesFixedInspectorPorts(),pods:n.map(i=>({id:i.id,inspectorUrl:i.inspectorUrl??null})),workers:O.map(i=>({name:i.name,port:i.debugPort||inspectorPortOf(i.inspectorUrl),inspectorUrl:i.inspectorUrl??null}))},null,2)+`
5
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
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()}})()}
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 ae=a((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",ae);const ie=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 ${ie/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!N&&!x.listening&&x.listen(r)},ie).unref?.();return}console.error("[parent] public server error:",e)}),await ee(E);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=a((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",ae),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=a(async e=>{N=!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.23.1",
3
+ "version": "1.23.3",
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,9 +57,9 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lensmcp/core": "1.23.1",
61
- "@lensmcp/node-instrumentation": "1.23.1",
62
- "@lensmcp/nx-plugin": "1.23.1",
60
+ "@lensmcp/core": "1.23.3",
61
+ "@lensmcp/node-instrumentation": "1.23.3",
62
+ "@lensmcp/nx-plugin": "1.23.3",
63
63
  "fastify": "^5.8.5",
64
64
  "@fastify/reply-from": "^12.6.2",
65
65
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",