@lensmcp/cluster 1.23.2 → 1.23.4

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
@@ -104,6 +104,8 @@ A declaration must route *somewhere*: either a pod `service` or a TCP `upstream`
104
104
 
105
105
  **`gateway`** — `ports` (default `[443]` so dev domains need no `:port`); `https` (default `true`, terminate TLS with the local CA); `middleware` (workspace-relative JS module `(req) => void` run on every request — the JWT/auth seam; throwing rejects with 401); `trafficFlushMs` (traffic-edge aggregation window before flushing to the LensMCP bus; default 5000).
106
106
 
107
+ **Hot swap and process names** — a pod hot-swaps by re-requiring its bundle in the same process. Global class registries in libraries loaded once from node_modules (`@nestjs/typeorm`'s entity storage, TypeORM's metadata args) have the classes the new bundle replaced removed, so the previous generation can be collected (`LENSMCP_SWAP_PRUNE_REGISTRIES=0` turns it off). Pods, devservers and nx runners run under a hard link named `<workspace>-<service>-pod|dev|nx`, so Activity Monitor and `top` show which service and workspace a process belongs to (`LENSMCP_PROCESS_NAMES=0` turns it off).
108
+
107
109
  **`trust`** — `serveTarget` (whose `gateway.routes` hostnames to set up, default `serve-hmr`); `hosts` (extra hostnames to add to `/etc/hosts`).
108
110
 
109
111
  **`build`** — `entryFile`, `tsConfigFile` (required); `outputPath`, `assets`, `workers`; `generatePackageJson`; `runtimeDependencies`; `migrationsDir` / `ormConfigPath`; the same bundling controls as `serve`.
@@ -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 Re=Object.defineProperty;var P=(r,E)=>Re(r,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=P((r,E)=>Pe(r,"name",{value:E,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(r,E){return new Promise(u=>{const _=net.createServer();_.unref(),_.once("error",p=>{const f=p.code??"";u(f==="EADDRINUSE"||f==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:r}:{port:r,host:E},()=>_.close(()=>u("free")))})}P(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(r){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,E)==="in-use")return!1;return!0}P(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(r,E,u){return new Promise(_=>{let p=!1;const f=net.createConnection({host:r,port:E});f.unref();const C=setTimeout(()=>v(!1),u);C.unref?.();function v(O){p||(p=!0,clearTimeout(C),f.destroy(),_(O))}P(v,"P"),d(v,"finish"),f.once("connect",()=>v(!0)),f.once("error",()=>v(!1))})}P(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(r,E=1e3){return await probeConnect("127.0.0.1",r,E)?!0:probeConnect("::1",r,E)}P(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(r,E=1500){try{const u=new AbortController,_=setTimeout(()=>u.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${r}/__lensmcp/watcher-health`,{signal:u.signal});if(!p.ok)return null;const f=await p.json();if(typeof f?.lastFsSetEventAt!="number")return null;const C=Array.isArray(f.recentEvents)?f.recentEvents.filter(v=>v&&typeof v.path=="string"&&typeof v.at=="number"&&typeof v.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(v.kind)):[];return{schemaVersion:f.schemaVersion===2?2:1,ready:f.ready!==!1,sequence:typeof f.sequence=="number"?f.sequence:0,lastFsSetEventAt:f.lastFsSetEventAt,fsSetEvents:typeof f.fsSetEvents=="number"?f.fsSetEvents:0,recentEvents:C}}finally{clearTimeout(_)}}catch{return null}}P(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(r=>new Promise(E=>{setTimeout(E,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,E,u={}){const _=u.isPortFree??probePortFree,p=Math.max(1,u.intervalMs??types_1.PORT_FREE_POLL_MS),f=Date.now()+Math.max(0,E);for(;;){if(await _(r))return!0;const C=f-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(p,C))}}P(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(r){const E=d(u=>Number.isInteger(u)&&u>0&&u<65536?u:void 0,"valid");for(let u=0;u<r.length;u+=1){const _=r[u];if(_==="--port")return E(Number(r[u+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}P(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(r){return r.signalCode?`killed by ${r.signalCode}`:r.exitCode===null?"exit status unknown":`exit code ${r.exitCode}`}P(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");async function claimPort(r,E,u,_){let p=r;for(;E.has(p)||_?.has(p)||!await u(p);)p+=1;return E.add(p),p}P(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(r,E,u,_){const{emit:p}=E,f=_?.isPortFree??probePortFree,C=(u.ports?.length?u.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,O=[],K=new Map,T=new Map,F=new Map,Y=new Map,B=new Set;let D;const N=new Map,$=new Set,Q=new Set,q=new Set,m=new Set,L=new Map,j=new WeakMap,W=new Set,Z=d((e,s,n)=>{if(W.has(e))return;W.add(e);const t=setInterval(()=>{(async()=>{if(r.stopped()||$.has(e)){clearInterval(t),W.delete(e);return}await f(s)&&(clearInterval(t),W.delete(e),console.log(`[gateway] lens child ${e}: :${s} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=d(async(e,s,n)=>{const t=await claimPort(e,s,f,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ce=d((e,s,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=s:e.endsWith(":browser-capture")&&(a.capture=s))},"attachRespawnedGenerationChild"),G=d(e=>{if(!(e.exitCode!==null||e.signalCode!==null)){Q.add(e);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{e.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},"retireChild"),de=d((e,s)=>{const n=N.get(e);!n||n===s||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),G(n))},"retireLabelHolder"),k=d((e,s,n,t,o=!1,a,i,l)=>{const h=Date.now(),c=(0,node_child_process_1.spawn)(s,n,{cwd:i??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,i??r.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});O.push(c),de(e,c),N.set(e,c),ce(e,c,t);const S=e==="vite"||e.endsWith(":vite"),M=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=M&&M!==r.eventFile?M:void 0,w=S?viteArgPort(n):l;S&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??r.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??r.root,spawnedAt:h,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const g=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return g&&w!==void 0&&(p("info",`lens child starting: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-starting",project:g,label:e,pid:c.pid,port:w},void 0,y),(async()=>{const b=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<b;){if(c.exitCode!==null||c.signalCode!==null||$.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-up",project:g,label:e,pid:c.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),c.on("exit",(b,H)=>{const se=O.indexOf(c);se>=0&&O.splice(se,1),N.get(e)===c&&N.delete(e);const A=T.get(c);if(T.delete(c),Q.delete(c))return;const ve=$.delete(e);if(r.stopped()||ve||(g?p("warning",`lens child exited: ${g}`,`cluster-lens-child:${g}`,{kind:"lens-child-exit",project:g,label:e,pid:c.pid,code:b,signal:H},void 0,y):A&&p("warning",`lens frontend exited: ${A.project}`,`cluster-lens-child:${A.project}`,{kind:"lens-child-exit",project:A.project,label:e,pid:c.pid,code:b,signal:H,generation:A.generation,port:A.port,ranMs:Date.now()-h},void 0,y),A&&Ee(c,A)))return;const ae=Date.now()-h,R=K.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await f(w),ie=(0,lifecycle_1.isPortConflictExit)({code:b,signal:H,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${b}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){R.portConflicts=0,K.set(e,R),console.warn(`[gateway] lens child ${e}: :${w} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Z(e,w,()=>k(e,s,n,t,o,a,i,l));return}else{R.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(R.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${b} sig=${H}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}r.stopped()||$.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:f,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(r.stopped()||$.has(e))&&k(e,s,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),ee=d(e=>{const s=`${e.wsKey}:${e.project}`,n=L.get(s);if(n)return n;const t={key:s,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(s,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,j.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,j.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const s=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==s);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,s,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;$.delete(a),$.delete(i);const l=d((S,M,y,w,g,b)=>k(S,M,y,w,g,b,e.workspaceRoot),"spawnInRoot"),h=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:s,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(S=>console.log(S),"log")},l),c={ownerKey:e.key,generation:t,port:s,labelPrefix:o,vite:h?.vite??N.get(a),capture:N.get(i)};return e.generations.add(c),c},"spawnFrontendGeneration"),x=d((e,s)=>{if(!e)return;L.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)$.add(t),o&&G(o);s&&m.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const s=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<s;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await v(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),V=d(async e=>{const s=e?.vite;return!!(e&&s&&s.exitCode===null&&s.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),U=d(e=>!e.disposed&&!r.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),z=d((e,s)=>{if(!U(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=s,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const g of L.values())g!==e&&l.add(g.desiredPort);const h=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,c=await I(e.desiredPort,m,{reserved:l,heldBy:h});if(a=c,!U(e))return m.delete(c),!1;o=te(e,c,"replacement"),e.candidate=o;const S=await V(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${s}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:S},void 0,e.eventFile===r.eventFile?void 0:e.eventFile);const M=await ue(o);if(!U(e))return x(o,!0),e.candidate=void 0,!1;if(!M){x(o,!0),e.candidate=void 0;const g=await V(n);return e.state=g?"ready":"failed",g&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:g},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}for(const g of e.routes)g.target=`http://localhost:${o.port}`,g.lens&&(g.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const y=o.vite?T.get(o.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(r,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${w>0?`; reloading ${w} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:w},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),n){const g=`${n.labelPrefix}browser-capture`;$.add(g),n.capture&&G(n.capture),setTimeout(()=>x(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){x(o,!0),!o&&a!==void 0&&m.delete(a),e.candidate=void 0;const h=await V(n);e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0);const c=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${c}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:h,error:c},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),oe=d((e,s)=>{e.active=te(e,s,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Ee=d((e,s)=>{const n=L.get(s.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${s.generation} :${s.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),z(n,"child-exit")),!0)},"handleManagedViteExit");r.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const s=j.get(e);return s?{state:s.state,generation:s.candidate?.generation??s.active?.generation??s.generation,reason:s.reason,startedAt:s.startedAt}:void 0},recover(e,s){const n=j.get(e);return n?z(n,s):Promise.resolve(!1)}}:void 0;let X=!1,ne=0;const fe=d(()=>{X||Date.now()-ne<types_1.POD_STALE_SCAN_MS||(X=!0,ne=Date.now(),(async()=>{for(const e of[...L.values()]){if(r.stopped())return;const s=Date.now(),n=(0,scope_1.readSourceSetOptions)(e.workspaceRoot),t=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,n.exclude,{ignoreTests:n.ignoreTests});if(L.get(e.key)!==e)continue;const o=e.sourceSnapshot,a=e.sourceScanStartedAt;if(e.sourceSnapshot=t,e.sourceScanStartedAt=s,!o)continue;const i=Date.now(),l=(0,lifecycle_1.diffSourceSets)(o,t,i,(0,lifecycle_1.sourceChangeWindowMs)(i,a,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));l&&(e.sourceChange=l,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:l.added,removed:l.removed,truncated:l.truncated},void 0,e.eventFile===r.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{X=!1}))},"scanFrontendSourceSets");let J=!1;const he=d(async(e,s)=>{if(!J){J=!0;try{for(const[n,t]of[...T]){if(r.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const l=L.get(t.key);l?.active?.vite===n&&(l.state="ready",l.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!s)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},F.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;F.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus);try{n.kill("SIGKILL")}catch{}}}finally{J=!1}}},"probeLensChildren"),ge=d(()=>{if(D||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};D=setInterval(()=>{if(r.stopped()||T.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;fe();const s=Date.now();for(const[n,t]of T){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=F.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,s,e)&&((Y.get(t.key)??0)>=a||B.has(t.key)||(B.add(t.key),(async()=>{try{const l=o?.sourceChange,h=l&&!l.truncated?[...l.added,...l.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],c=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await v(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,h):"unknown";if(c==="heard"){Y.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(c==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;F.set(t.key,Date.now());const S=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${c==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:S.wsKey,project:t.project,reason:"stale-source-set",watcher:c,added:l?.added,removed:l?.removed},void 0,t.bus),await z(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${c==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:c},void 0,t.bus);const M=(0,hmr_reload_1.broadcastFullReload)(r,t.key);M>0&&console.log(`[gateway] \u2192 told ${M} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(n.exitCode===null&&n.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{B.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(r.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],s=u.dashboard!==!1,n=u.mcp!==!1&&e.length>0;if(s){const t=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(r.root),a=await I(u.dashboardPort??o.dashboardPort,q),i=u.lensHost??"lensmcp.local",l=o.basePath;r.registry.appendRoutes(r.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:r.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_GATEWAY_PORTS:C},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(r.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(r.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),k("lens-mcp",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,a)},"spawnMcp");await f(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),Z("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(r.registry.get(r.wsKey)?.routes??r.routes).filter(h=>h.lens?.project===t.project),i=await I(t.port,m);for(const h of a)h.lens&&(h.lens.port=i),h.target=`http://localhost:${i}`;t.port=i;const l=ee({wsKey:r.wsKey,project:t.project,workspaceRoot:r.root,projectRoot:t.projectRoot,eventFile:r.eventFile,routes:a,frontend:t,desiredPort:o});try{oe(l,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(h){m.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${h.message}`)}}ge()},"bootSingletonsAndApps"),_e=d(async e=>{if(u.dashboard===!1)return;const s=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!s)return;const n=(0,scope_1.readLensScope)(e.root),t=u.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,q),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return $.delete(i),r.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o}),k(i,process.execPath,[s],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_GATEWAY_PORTS:C},!1,void 0,e.root,a),()=>{$.add(i),q.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const h=N.get(i);h&&G(h),N.delete(i)}},"hostWorkspaceDashboard"),Se=d(async e=>{const s=new Map;for(const o of e.routes){if(!o.lens)continue;const a=s.get(o.lens.project)??[];a.push(o),s.set(o.lens.project,a)}if(s.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of s){const i=a[0].lens;if(i.started)continue;const l=await I(i.port,m),h=i.port;for(const S of a)S.lens.started=!0,S.lens.port=l,S.target=`http://localhost:${l}`;const c=ee({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:h});t.push(c),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{oe(c,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(S){m.delete(l),c.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])x(a,!0);for(const a of o.routes)a.lens&&j.delete(a.lens);j.delete(o.frontend),F.delete(o.key),Y.delete(o.key),B.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const s=[...O];for(const o of s)try{o.kill("SIGTERM")}catch{}const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&s.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of s)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),re=d(()=>he({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}P(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
1
+ "use strict";var Re=Object.defineProperty;var P=(r,E)=>Re(r,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=P((r,E)=>Pe(r,"name",{value:E,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(r,E){return new Promise(u=>{const _=net.createServer();_.unref(),_.once("error",p=>{const f=p.code??"";u(f==="EADDRINUSE"||f==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:r}:{port:r,host:E},()=>_.close(()=>u("free")))})}P(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(r){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,E)==="in-use")return!1;return!0}P(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(r,E,u){return new Promise(_=>{let p=!1;const f=net.createConnection({host:r,port:E});f.unref();const C=setTimeout(()=>v(!1),u);C.unref?.();function v(O){p||(p=!0,clearTimeout(C),f.destroy(),_(O))}P(v,"P"),d(v,"finish"),f.once("connect",()=>v(!0)),f.once("error",()=>v(!1))})}P(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(r,E=1e3){return await probeConnect("127.0.0.1",r,E)?!0:probeConnect("::1",r,E)}P(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(r,E=1500){try{const u=new AbortController,_=setTimeout(()=>u.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${r}/__lensmcp/watcher-health`,{signal:u.signal});if(!p.ok)return null;const f=await p.json();if(typeof f?.lastFsSetEventAt!="number")return null;const C=Array.isArray(f.recentEvents)?f.recentEvents.filter(v=>v&&typeof v.path=="string"&&typeof v.at=="number"&&typeof v.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(v.kind)):[];return{schemaVersion:f.schemaVersion===2?2:1,ready:f.ready!==!1,sequence:typeof f.sequence=="number"?f.sequence:0,lastFsSetEventAt:f.lastFsSetEventAt,fsSetEvents:typeof f.fsSetEvents=="number"?f.fsSetEvents:0,recentEvents:C}}finally{clearTimeout(_)}}catch{return null}}P(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(r=>new Promise(E=>{setTimeout(E,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,E,u={}){const _=u.isPortFree??probePortFree,p=Math.max(1,u.intervalMs??types_1.PORT_FREE_POLL_MS),f=Date.now()+Math.max(0,E);for(;;){if(await _(r))return!0;const C=f-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(p,C))}}P(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(r){const E=d(u=>Number.isInteger(u)&&u>0&&u<65536?u:void 0,"valid");for(let u=0;u<r.length;u+=1){const _=r[u];if(_==="--port")return E(Number(r[u+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}P(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(r){return r.signalCode?`killed by ${r.signalCode}`:r.exitCode===null?"exit status unknown":`exit code ${r.exitCode}`}P(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");async function claimPort(r,E,u,_){let p=r;for(;E.has(p)||_?.has(p)||!await u(p);)p+=1;return E.add(p),p}P(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(r,E,u,_){const{emit:p}=E,f=_?.isPortFree??probePortFree,C=(u.ports?.length?u.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,O=[],K=new Map,T=new Map,F=new Map,Y=new Map,B=new Set;let D;const m=new Map,$=new Set,Q=new Set,q=new Set,N=new Set,L=new Map,j=new WeakMap,W=new Set,Z=d((e,s,n)=>{if(W.has(e))return;W.add(e);const t=setInterval(()=>{(async()=>{if(r.stopped()||$.has(e)){clearInterval(t),W.delete(e);return}await f(s)&&(clearInterval(t),W.delete(e),console.log(`[gateway] lens child ${e}: :${s} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=d(async(e,s,n)=>{const t=await claimPort(e,s,f,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ce=d((e,s,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=s:e.endsWith(":browser-capture")&&(a.capture=s))},"attachRespawnedGenerationChild"),G=d(e=>{if(!(e.exitCode!==null||e.signalCode!==null)){Q.add(e);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{e.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},"retireChild"),de=d((e,s)=>{const n=m.get(e);!n||n===s||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),G(n))},"retireLabelHolder"),k=d((e,s,n,t,o=!1,a,i,l)=>{const h=Date.now(),c=(0,node_child_process_1.spawn)(s,n,{cwd:i??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,i??r.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});O.push(c),de(e,c),m.set(e,c),ce(e,c,t);const S=e==="vite"||e.endsWith(":vite"),M=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=M&&M!==r.eventFile?M:void 0,w=S?viteArgPort(n):l;S&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??r.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??r.root,spawnedAt:h,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const g=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return g&&w!==void 0&&(p("info",`lens child starting: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-starting",project:g,label:e,pid:c.pid,port:w},void 0,y),(async()=>{const b=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<b;){if(c.exitCode!==null||c.signalCode!==null||$.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-up",project:g,label:e,pid:c.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),c.on("exit",(b,H)=>{const se=O.indexOf(c);se>=0&&O.splice(se,1),m.get(e)===c&&m.delete(e);const A=T.get(c);if(T.delete(c),Q.delete(c))return;const ve=$.delete(e);if(r.stopped()||ve||(g?p("warning",`lens child exited: ${g}`,`cluster-lens-child:${g}`,{kind:"lens-child-exit",project:g,label:e,pid:c.pid,code:b,signal:H},void 0,y):A&&p("warning",`lens frontend exited: ${A.project}`,`cluster-lens-child:${A.project}`,{kind:"lens-child-exit",project:A.project,label:e,pid:c.pid,code:b,signal:H,generation:A.generation,port:A.port,ranMs:Date.now()-h},void 0,y),A&&Ee(c,A)))return;const ae=Date.now()-h,R=K.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await f(w),ie=(0,lifecycle_1.isPortConflictExit)({code:b,signal:H,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${b}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){R.portConflicts=0,K.set(e,R),console.warn(`[gateway] lens child ${e}: :${w} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Z(e,w,()=>k(e,s,n,t,o,a,i,l));return}else{R.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(R.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${b} sig=${H}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}r.stopped()||$.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:f,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(r.stopped()||$.has(e))&&k(e,s,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),ee=d(e=>{const s=`${e.wsKey}:${e.project}`,n=L.get(s);if(n)return n;const t={key:s,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(s,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,j.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,j.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const s=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==s);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,s,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;$.delete(a),$.delete(i);const l=d((S,M,y,w,g,b)=>k(S,M,y,w,g,b,e.workspaceRoot),"spawnInRoot"),h=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:s,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(S=>console.log(S),"log")},l),c={ownerKey:e.key,generation:t,port:s,labelPrefix:o,vite:h?.vite??m.get(a),capture:m.get(i)};return e.generations.add(c),c},"spawnFrontendGeneration"),x=d((e,s)=>{if(!e)return;L.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)$.add(t),o&&G(o);s&&N.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const s=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<s;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await v(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),V=d(async e=>{const s=e?.vite;return!!(e&&s&&s.exitCode===null&&s.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),U=d(e=>!e.disposed&&!r.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),z=d((e,s)=>{if(!U(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=s,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const g of L.values())g!==e&&l.add(g.desiredPort);const h=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,c=await I(e.desiredPort,N,{reserved:l,heldBy:h});if(a=c,!U(e))return N.delete(c),!1;o=te(e,c,"replacement"),e.candidate=o;const S=await V(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${s}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:S},void 0,e.eventFile===r.eventFile?void 0:e.eventFile);const M=await ue(o);if(!U(e))return x(o,!0),e.candidate=void 0,!1;if(!M){x(o,!0),e.candidate=void 0;const g=await V(n);return e.state=g?"ready":"failed",g&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:g},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}for(const g of e.routes)g.target=`http://localhost:${o.port}`,g.lens&&(g.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const y=o.vite?T.get(o.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(r,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${w>0?`; reloading ${w} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:w},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),n){const g=`${n.labelPrefix}browser-capture`;$.add(g),n.capture&&G(n.capture),setTimeout(()=>x(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){x(o,!0),!o&&a!==void 0&&N.delete(a),e.candidate=void 0;const h=await V(n);e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0);const c=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${c}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:h,error:c},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),oe=d((e,s)=>{e.active=te(e,s,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Ee=d((e,s)=>{const n=L.get(s.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${s.generation} :${s.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),z(n,"child-exit")),!0)},"handleManagedViteExit");r.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const s=j.get(e);return s?{state:s.state,generation:s.candidate?.generation??s.active?.generation??s.generation,reason:s.reason,startedAt:s.startedAt}:void 0},recover(e,s){const n=j.get(e);return n?z(n,s):Promise.resolve(!1)}}:void 0;let X=!1,ne=0;const fe=d(()=>{X||Date.now()-ne<types_1.POD_STALE_SCAN_MS||(X=!0,ne=Date.now(),(async()=>{for(const e of[...L.values()]){if(r.stopped())return;const s=Date.now(),n=(0,scope_1.readSourceSetOptions)(e.workspaceRoot),t=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,n.exclude,{ignoreTests:n.ignoreTests});if(L.get(e.key)!==e)continue;const o=e.sourceSnapshot,a=e.sourceScanStartedAt;if(e.sourceSnapshot=t,e.sourceScanStartedAt=s,!o)continue;const i=Date.now(),l=(0,lifecycle_1.diffSourceSets)(o,t,i,(0,lifecycle_1.sourceChangeWindowMs)(i,a,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));l&&(e.sourceChange=l,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:l.added,removed:l.removed,truncated:l.truncated},void 0,e.eventFile===r.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{X=!1}))},"scanFrontendSourceSets");let J=!1;const he=d(async(e,s)=>{if(!J){J=!0;try{for(const[n,t]of[...T]){if(r.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const l=L.get(t.key);l?.active?.vite===n&&(l.state="ready",l.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!s)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},F.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;F.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus);try{n.kill("SIGKILL")}catch{}}}finally{J=!1}}},"probeLensChildren"),ge=d(()=>{if(D||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};D=setInterval(()=>{if(r.stopped()||T.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;fe();const s=Date.now();for(const[n,t]of T){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=F.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,s,e)&&((Y.get(t.key)??0)>=a||B.has(t.key)||(B.add(t.key),(async()=>{try{const l=o?.sourceChange,h=l&&!l.truncated?[...l.added,...l.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],c=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await v(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,h):"unknown";if(c==="heard"){Y.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(c==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;F.set(t.key,Date.now());const S=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${c==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:S.wsKey,project:t.project,reason:"stale-source-set",watcher:c,added:l?.added,removed:l?.removed},void 0,t.bus),await z(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${c==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:c},void 0,t.bus);const M=(0,hmr_reload_1.broadcastFullReload)(r,t.key);M>0&&console.log(`[gateway] \u2192 told ${M} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(n.exitCode===null&&n.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{B.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(r.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],s=u.dashboard!==!1,n=u.mcp!==!1&&e.length>0;if(s){const t=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(r.root),a=await I(u.dashboardPort??o.dashboardPort,q),i=u.lensHost??"lensmcp.local",l=o.basePath;r.registry.appendRoutes(r.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:r.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_GATEWAY_PORTS:C},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(r.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(r.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),k("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"mcp")),[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,a)},"spawnMcp");await f(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),Z("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(r.registry.get(r.wsKey)?.routes??r.routes).filter(h=>h.lens?.project===t.project),i=await I(t.port,N);for(const h of a)h.lens&&(h.lens.port=i),h.target=`http://localhost:${i}`;t.port=i;const l=ee({wsKey:r.wsKey,project:t.project,workspaceRoot:r.root,projectRoot:t.projectRoot,eventFile:r.eventFile,routes:a,frontend:t,desiredPort:o});try{oe(l,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(h){N.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${h.message}`)}}ge()},"bootSingletonsAndApps"),_e=d(async e=>{if(u.dashboard===!1)return;const s=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!s)return;const n=(0,scope_1.readLensScope)(e.root),t=u.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,q),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return $.delete(i),r.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o}),k(i,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[s],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_GATEWAY_PORTS:C},!1,void 0,e.root,a),()=>{$.add(i),q.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const h=m.get(i);h&&G(h),m.delete(i)}},"hostWorkspaceDashboard"),Se=d(async e=>{const s=new Map;for(const o of e.routes){if(!o.lens)continue;const a=s.get(o.lens.project)??[];a.push(o),s.set(o.lens.project,a)}if(s.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of s){const i=a[0].lens;if(i.started)continue;const l=await I(i.port,N),h=i.port;for(const S of a)S.lens.started=!0,S.lens.port=l,S.target=`http://localhost:${l}`;const c=ee({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:h});t.push(c),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{oe(c,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(S){N.delete(l),c.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])x(a,!0);for(const a of o.routes)a.lens&&j.delete(a.lens);j.delete(o.frontend),F.delete(o.key),Y.delete(o.key),B.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const s=[...O];for(const o of s)try{o.kill("SIGTERM")}catch{}const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&s.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of s)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),re=d(()=>he({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}P(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
@@ -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 v=(t,n)=>ge(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"),exit_reason_1=require("../../../exit-reason"),process_name_1=require("../../../process-name");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 f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(a.NODE_EXTRA_CA_CERTS=f)}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 f=l?.ignoreTests??!0;let S=0,y=0,_=!1;const g=new Set,L=await gitIgnoredPaths(t),h=i(R=>path.relative(t,R).split(path.sep).join("/"),"rel"),T=i(async(R,X=!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,q=path.join(R,x);if(K.isDirectory()){if(!X||x.startsWith(".")||SRC_EXCLUDE.has(x)||a?.has(x)||x.endsWith("-devserver")||L.has(`${h(q)}/`))continue;await T(q)}else if(K.isFile()){const Y=x.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(x.slice(Y)))continue;const P=h(q);if(L.has(P)||f&&isTestSourcePath(P)||g.has(P))continue;g.add(P),S+=1,y^=fnv1a(P)}}},"walk");if(n&&n.length>0){await T(t,!1);for(const R of n)await T(R)}else await T(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:g,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 g of t.paths)if(!n.paths.has(g)){_=!1;break}if(_)return null}const f=[],S=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(_):y=!0);return{changedAt:a,added:f,removed:S,truncated:y,...l!==void 0?{windowMs:l}:{}}}v(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const f=n&&n>0?t-n:a;return Math.max(f,a)+l}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const S=i(g=>{const L=path.relative(t,g).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),_=new Set([S]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||_.add(g);return[..._].sort().map(g=>path.join(t,g))}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,f){return!(l-n<f.cooldownMs||a<=0||a-t<=f.graceMs||l-a<f.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 f=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=f).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 f=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<f||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,f=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:g}=n,L=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>g(o,s,r,{wsKey:e.wsKey,...c},void 0,L(e)),"emitSvc"),T=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 u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const j=Number(u[1]),b=Number(u[2]),$=u[3];if($.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test($)&&!($.includes("/node_modules/nx/")&&!$.includes(e.root))&&!(j===process.pid||j===r)&&!(r!==void 0&&b===r)){try{process.kill(-b,"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"),X=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"),u=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[j,b]=fs.existsSync(d)?[u,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],$=l(j,b,{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 C=i(w=>{const E=w.toString();for(const F of E.split(`
3
+ `)){const{clean:p,broken:A,healthy:m}=(0,pod_output_1.classifyPodLine)(F),M=p;if(A){e.lastErrors.push(p),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const G=Date.now();(p!==e.lastErrorEmitSig||G-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=p,e.lastErrorEmitAt=G,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:p}))}m&&(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`),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"),k=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return $.stdout?.on("data",w=>{if(t.stopped())return;C(w);const E=`[${e.project}] ${w}`;process.stdout.write(E),k?.(E)}),$.stderr?.on("data",w=>{if(t.stopped())return;C(w);const E=`[${e.project}] ${w}`;process.stderr.write(E),k?.(E)}),$.on("exit",(w,E)=>{if(e.proc!==$||(e.proc=void 0,e.state="down",t.stopped()))return;const F=(0,exit_reason_1.describeExit)(w,E);if(console.log(`[gateway] ${r} exited (${w??"no code"}${E?`, ${E}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${F?` \u2014 ${F}`:""}`),h(e,w===0?"info":"warning",`service down: ${e.project} (exit ${w})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:w,...E?{signal:E}:{}}),w===0||e.buildFailed)return;const p=Date.now();if(e.restarts=(e.restarts??[]).filter(m=>p-m<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(p);const A=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))},A)}),$},"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=X(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"),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,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(T(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.`),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(T(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(()=>{z(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),z=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"),te=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 u=X(e,c,"standby"),j=(async()=>{const b=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],$=d+y;let C;for(;!t.stopped()&&Date.now()<$&&!(u.exitCode!==null||u.signalCode!==null);){T(e.pool);for(const k of e.pool.socks)if((await f(k,void 0,b))?.podGeneration===c){C=k;break}if(C)break;await sleep(_)}if(!C||t.stopped()){const k=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{u.stdout?.removeAllListeners("data"),u.stderr?.removeAllListeners("data")}catch{}return S(u,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${k} \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:k,durationMs:Date.now()-d}),setTimeout(()=>z(e),1200).unref?.(),!1}if(e.proc!==s)return S(u,"SIGTERM"),!1;e.proc=u,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(()=>{z(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(C)} 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}; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),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 J;const V=new Map,oe=new Map;let Q=!1,Z=0,H=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 u=sourceScopeDirs(t.root,d,s,o,r);return u.length>0?u:void 0},"scopeFor"),le=i(()=>{if(Q||Date.now()-Z<types_1.POD_STALE_SCAN_MS)return;Q=!0,Z=Date.now();const e=Z;(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(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),j=J?diffSourceSets(J,c,d,u):null;if(j&&(t.sourceSetChangedAt=d,t.sourceSetChange=j,g("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})),J=c,!t.projectRoots){H=e;return}let b;try{b=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(p=>p.isDirectory()).map(p=>p.name)}catch{return}const $=ie(),C=new Set(t.services.map(p=>p.project));for(const p of t.routes)p.lens&&C.add(p.lens.project);const k=new Map;for(const p of C){const A=ae(p,b,$,s);if(!A)continue;const m=A.join("|"),M=k.get(m)??{dirs:A,projects:[]};M.projects.push(p),k.set(m,M)}const w=Date.now(),E=sourceChangeWindowMs(w,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const F=i((p,A,m)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of p)t.sourceSetChangedFor.set(M,A),t.sourceSetChangesFor.set(M,m)},"stampScoped");for(const[p,{dirs:A,projects:m}]of k){const M=await sourceSetSnapshot(t.root,A,s,r),G=V.get(p);V.set(p,M);const ee=new Map;for(const N of m){const I=oe.get(N);if(I===void 0||I===p)continue;const W=ee.get(I)??[];W.push(N),ee.set(I,W)}for(const N of m)oe.set(N,p);for(const[N,I]of ee){const W=V.get(N),U=W?diffSourceSets(W,M,w,E):j;U&&(F(I,W?w:d,U),g("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:U.added,removed:U.removed,truncated:U.truncated}))}if(!G)continue;const B=diffSourceSets(G,M,w,E);B&&(F(m,w,B),g("info",`source file set changed in ${m.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:m,added:B.added,removed:B.removed,truncated:B.truncated}))}for(const p of[...V.keys()])k.has(p)||V.delete(p)})().catch(()=>{}).finally(()=>{Q=!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){te(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(()=>{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=T(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,u=podRecycleReason(e,Date.now(),d?0:c,ce);if(u==="stale-source-set"&&!de(e,c))continue;u&&pe(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:T,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:O,markUp:P,startSweeper:ue,rollPod:te}}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 +1 @@
1
- "use strict";var X=Object.defineProperty;var l=(e,r)=>X(e,"name",{value:r,configurable:!0});var J=Object.defineProperty,a=l((e,r)=>J(e,"name",{value:r,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),create_webpack_dev_1=require("../../create-webpack-dev"),webpack=require("webpack");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const D=r.projectsConfigurations.projects[r.projectName],i=r.root,d=path.join(i,D.root),R=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",D.root),T=e.configEnv||"development",_=path.join(i,"config",`config.${T}.yaml`),S=3050;let u=S,n=r.projectName,h={};fs.existsSync(_)?(h=yaml.load(fs.readFileSync(_,"utf8")),u=Number(h.port)||u,n=h.serviceName||n):console.warn(`[serve] ${r.projectName}: no config file at ${_} \u2014 public devserver port defaults to ${S}. If another service also lacks its config, both bind ${S} \u2192 EADDRINUSE. Add config/config.${T}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(n=e.serviceName);const y=e.servePrefix??"",p=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),x=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),c=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(c){fs.mkdirSync(path.dirname(c),{recursive:!0}),fs.writeFileSync(c,"",{mode:384});try{fs.chmodSync(c,384)}catch{}}let N,P;e.gateway?.middleware&&(N=path.resolve(d,e.gateway.middleware),P=JSON.stringify(h));const A=e.entryFile||"./src/deployments/service/main.ts",$=e.tsConfigFile||"./tsconfig.app.json",G=path.resolve(d,A),F=path.resolve(d,$),U=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(d,t.entryPath)})),C=(0,create_webpack_dev_1.createDevWebpackConfig)({appName:r.projectName,appRoot:d,outputDir:R,main:G,tsConfig:F,assets:e.assets||[],additionalEntryPoints:U,port:u,serviceName:n,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0});let s=null,b=!1;const O=path.join(__dirname,"../../main.devserver.js"),k=path.join(__dirname,"../../main.devserver.ts"),I=!fs.existsSync(O)&&fs.existsSync(k),w=I?k:O,M=path.join(R,"main.js");function v(){s&&!s.killed&&(s.kill("SIGTERM"),s=null)}l(v,"g"),a(v,"cleanup");const W=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const o=a(()=>v(),"handler");return process.on(t,o),{sig:t,handler:o}});process.on("exit",v);function g(){if(!fs.existsSync(w)){console.error(`[serve] devserver script not found at ${w}`);return}const t=["--enable-source-maps"];I&&t.unshift("--require","@swc-node/register"),s=(0,node_child_process_1.fork)(w,{cwd:i,env:{...process.env,PORT:String(u),SERVICE_NAME:n,SERVE_PREFIX:y,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||n,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:M,...N&&{GATEWAY_MIDDLEWARE:N},...P&&{GATEWAY_CONFIG:P},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...p&&{LENSMCP_DEBUG_PORT:String(p)},...c&&{LENSMCP_LOG_FILE:c},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(o=>o.name))}},stdio:["inherit","inherit","inherit","ipc"],execArgv:t}),s.on("exit",(o,f)=>{s=null,!f&&(console.error(`[serve] devserver exited unexpectedly (code=${o}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!s&&b&&g()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${u} (service: ${n}${y?`, prefix: /${y}`:""})`),p?(console.warn(`[serve] DEBUGGER ENABLED for ${n}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),x?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${p}+slot (workers ${p}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${p}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port \u2014 click the "Debugger listening" link in this console, or run \`lensmcp debug ${n}\` to get the ws:// URLs over the owner-only control socket. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${n}\` opens pod inspectors without a restart`),c&&console.log(`[serve] Logs teed to ${c} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}l(g,"v"),a(g,"startDevServer");function B(){if(!s||s.killed){console.log("[serve] Devserver not running, restarting..."),g();return}fetch(`http://localhost:${u}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),v(),g()})}l(B,"V"),a(B,"_notifyDevServerReload");const{watch:Y,...q}=C,H=webpack(q);let E=null;const L=[];function m(t){if(E){const o=E;E=null,o(t)}else L.push(t)}l(m,"b"),a(m,"pushResult");function j(){return L.length>0?Promise.resolve(L.shift()):new Promise(t=>{E=t})}l(j,"W"),a(j,"waitForNext");const V=H.watch(C.watchOptions||{},(t,o)=>{if(t){console.error("[serve] Webpack error:",t),m({success:!1});return}const f=o.hasErrors();if(console.info(o.toString({colors:!0,chunks:!1,assets:!0})),f){m({success:!1});return}(!b||!s)&&(b=!0,g()),m({success:!0,baseUrl:`http://localhost:${u}`})});try{for(;;)yield await j()}finally{V.close(()=>{}),v(),W.forEach(({sig:t,handler:o})=>process.removeListener(t,o))}}l(serveExecutor,"serveExecutor"),a(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
1
+ "use strict";var V=Object.defineProperty;var l=(e,r)=>V(e,"name",{value:r,configurable:!0});var J=Object.defineProperty,a=l((e,r)=>J(e,"name",{value:r,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),create_webpack_dev_1=require("../../create-webpack-dev"),process_name_1=require("../../process-name"),webpack=require("webpack");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const D=r.projectsConfigurations.projects[r.projectName],i=r.root,d=path.join(i,D.root),R=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",D.root),T=e.configEnv||"development",_=path.join(i,"config",`config.${T}.yaml`),S=3050;let p=S,s=r.projectName,m={};fs.existsSync(_)?(m=yaml.load(fs.readFileSync(_,"utf8")),p=Number(m.port)||p,s=m.serviceName||s):console.warn(`[serve] ${r.projectName}: no config file at ${_} \u2014 public devserver port defaults to ${S}. If another service also lacks its config, both bind ${S} \u2192 EADDRINUSE. Add config/config.${T}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(s=e.serviceName);const y=e.servePrefix??"",u=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),x=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),c=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(c){fs.mkdirSync(path.dirname(c),{recursive:!0}),fs.writeFileSync(c,"",{mode:384});try{fs.chmodSync(c,384)}catch{}}let P,N;e.gateway?.middleware&&(P=path.resolve(d,e.gateway.middleware),N=JSON.stringify(m));const A=e.entryFile||"./src/deployments/service/main.ts",$=e.tsConfigFile||"./tsconfig.app.json",G=path.resolve(d,A),F=path.resolve(d,$),M=(e.workers||[]).map(o=>({entryName:o.name,entryPath:path.resolve(d,o.entryPath)})),C=(0,create_webpack_dev_1.createDevWebpackConfig)({appName:r.projectName,appRoot:d,outputDir:R,main:G,tsConfig:F,assets:e.assets||[],additionalEntryPoints:M,port:p,serviceName:s,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0});let n=null,b=!1;const O=path.join(__dirname,"../../main.devserver.js"),k=path.join(__dirname,"../../main.devserver.ts"),I=!fs.existsSync(O)&&fs.existsSync(k),w=I?k:O,U=path.join(R,"main.js");function v(){n&&!n.killed&&(n.kill("SIGTERM"),n=null)}l(v,"g"),a(v,"cleanup");const W=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(o=>{const t=a(()=>v(),"handler");return process.on(o,t),{sig:o,handler:t}});process.on("exit",v);function h(){if(!fs.existsSync(w)){console.error(`[serve] devserver script not found at ${w}`);return}const o=["--enable-source-maps"];I&&o.unshift("--require","@swc-node/register"),n=(0,node_child_process_1.fork)(w,{cwd:i,env:{...process.env,PORT:String(p),SERVICE_NAME:s,SERVE_PREFIX:y,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||s,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:U,...P&&{GATEWAY_MIDDLEWARE:P},...N&&{GATEWAY_CONFIG:N},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...u&&{LENSMCP_DEBUG_PORT:String(u)},...c&&{LENSMCP_LOG_FILE:c},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(t=>t.name))}},stdio:["inherit","inherit","inherit","ipc"],execArgv:o,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),s,"dev"))}),n.on("exit",(t,f)=>{n=null,!f&&(console.error(`[serve] devserver exited unexpectedly (code=${t}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!n&&b&&h()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${p} (service: ${s}${y?`, prefix: /${y}`:""})`),u?(console.warn(`[serve] DEBUGGER ENABLED for ${s}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),x?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${u}+slot (workers ${u}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${u}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port \u2014 click the "Debugger listening" link in this console, or run \`lensmcp debug ${s}\` to get the ws:// URLs over the owner-only control socket. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${s}\` opens pod inspectors without a restart`),c&&console.log(`[serve] Logs teed to ${c} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}l(h,"v"),a(h,"startDevServer");function q(){if(!n||n.killed){console.log("[serve] Devserver not running, restarting..."),h();return}fetch(`http://localhost:${p}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(o=>{console.warn("[serve] Failed to notify devserver, restarting...",o.message),v(),h()})}l(q,"Y"),a(q,"_notifyDevServerReload");const{watch:X,...B}=C,Y=webpack(B);let g=null;const L=[];function E(o){if(g){const t=g;g=null,t(o)}else L.push(o)}l(E,"w"),a(E,"pushResult");function j(){return L.length>0?Promise.resolve(L.shift()):new Promise(o=>{g=o})}l(j,"B"),a(j,"waitForNext");const H=Y.watch(C.watchOptions||{},(o,t)=>{if(o){console.error("[serve] Webpack error:",o),E({success:!1});return}const f=t.hasErrors();if(console.info(t.toString({colors:!0,chunks:!1,assets:!0})),f){E({success:!1});return}(!b||!n)&&(b=!0,h()),E({success:!0,baseUrl:`http://localhost:${p}`})});try{for(;;)yield await j()}finally{H.close(()=>{}),v(),W.forEach(({sig:o,handler:t})=>process.removeListener(o,t))}}l(serveExecutor,"serveExecutor"),a(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
@@ -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");
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Why a pod's heap grew by a whole bundle on every hot swap (issues/foodguard/24).
3
+ *
4
+ * A swap re-requires the webpack bundle in the SAME process, so every decorated class is created again.
5
+ * The old app is closed and its module graph should go with it — but a library loaded from node_modules
6
+ * is loaded ONCE, and a library that keeps a process-global registry of classes keeps the old ones:
7
+ *
8
+ * - `@nestjs/typeorm`'s `EntitiesMetadataStorage` (a static Map) is appended to by every
9
+ * `TypeOrmModule.forFeature()` — `includes()` compares by identity, so a fresh class is always new —
10
+ * and nothing ever removes an entry. With `autoLoadEntities: true` the NEXT app's DataSource is built
11
+ * from that list, so it builds metadata for every past generation and pins them a second time.
12
+ * - TypeORM's `typeormMetadataArgsStorage` (on globalThis) keeps every `@Entity`/`@Column`/… record.
13
+ *
14
+ * One old class is enough to keep its whole generation alive: its closure reaches that generation's
15
+ * `__webpack_require__`, and that reaches every module the bundle evaluated — code, source strings,
16
+ * inline source maps. Measured on foodguard (2026-10-01): +190–360 MB per swap, three copies of the
17
+ * code alive after two swaps with ONE Nest app among them, the 4.4 GB V8 limit reached within an hour
18
+ * of edits, 58 heap-out-of-memory crashes in eight hours.
19
+ *
20
+ * The rule: an entry is SUPERSEDED when the bundle that was just imported registered a class of the
21
+ * same name in the same registry. Only those entries are removed. A class nothing re-registered — one
22
+ * that lives in node_modules and was evaluated once — has no successor and is kept.
23
+ */
24
+ /** A class as a registry holds it: compared by identity, matched to its successor by `name`. */
25
+ export type RegisteredClass = {
26
+ readonly name: string;
27
+ };
28
+ /**
29
+ * When a registry may lose its superseded entries.
30
+ *
31
+ * - `build`: read only while an app is being CREATED. Pruned at the swap, after the new bundle is
32
+ * imported and before the new app is built — otherwise the new app is built from the old classes too.
33
+ * - `serve`: read while an app SERVES requests (TypeORM's custom-repository lookup reads its registry
34
+ * per call). Pruned only once the app that registered the classes has been closed.
35
+ */
36
+ export type RegistryPhase = 'build' | 'serve';
37
+ export interface ClassRegistry {
38
+ readonly id: string;
39
+ readonly phase: RegistryPhase;
40
+ /** Every class the registry holds right now. */
41
+ classes(): RegisteredClass[];
42
+ /** Remove every entry whose class is in `stale`; returns how many entries went. */
43
+ prune(stale: ReadonlySet<RegisteredClass>): number;
44
+ }
45
+ export type PruneResult = {
46
+ registry: string;
47
+ removed: number;
48
+ };
49
+ /** Classes per registry id, taken before a bundle import. */
50
+ export type RegistrySnapshot = ReadonlyMap<string, ReadonlySet<RegisteredClass>>;
51
+ /**
52
+ * `@nestjs/typeorm`'s `EntitiesMetadataStorage.storage`: data-source token → entities. One registry per
53
+ * LOADED copy of the package (a workspace can resolve two), found in the CJS module cache.
54
+ */
55
+ export declare function nestTypeormEntityRegistries(cache: NodeJS.Dict<NodeJS.Module>): ClassRegistry[];
56
+ /**
57
+ * TypeORM's `MetadataArgsStorage` on `globalThis.typeormMetadataArgsStorage`: one array per decorator
58
+ * kind (`tables`, `columns`, `relations`, `entityRepositories`, …), each entry carrying its `target`.
59
+ * Read generically, so a TypeORM version that adds a kind is covered without a change here.
60
+ */
61
+ export declare function typeormArgsRegistries(scope: object): ClassRegistry[];
62
+ /** The registries a pod process holds right now — re-read on every call, since a library loads late. */
63
+ export declare function defaultRegistries(cache?: NodeJS.Dict<NodeJS.Module>, scope?: object): ClassRegistry[];
64
+ /** What every registry holds before a bundle import. */
65
+ export declare function captureRegistries(registries?: ClassRegistry[]): RegistrySnapshot;
66
+ /**
67
+ * The classes the import since `before` replaced: held before it, with a same-named class added by it
68
+ * in the same registry. A nameless class is never matched.
69
+ */
70
+ export declare function supersededSince(before: RegistrySnapshot, registries?: ClassRegistry[]): Set<RegisteredClass>;
71
+ /** Remove `stale` from every registry of `phase`; reports the registries that lost entries. */
72
+ export declare function pruneRegistries(stale: ReadonlySet<RegisteredClass>, phase: RegistryPhase, registries?: ClassRegistry[]): PruneResult[];
73
+ /** `LENSMCP_SWAP_PRUNE_REGISTRIES=0` turns the pruning off — the escape hatch if it ever removes too much. */
74
+ export declare function registryPruningEnabled(env?: NodeJS.ProcessEnv): boolean;
75
+ /** One log line for a prune, or `null` when nothing was removed. */
76
+ export declare function describePrune(results: PruneResult[], when: string): string | null;
@@ -0,0 +1 @@
1
+ "use strict";var l=Object.defineProperty;var c=(t,e)=>l(t,"name",{value:e,configurable:!0});var d=Object.defineProperty,o=c((t,e)=>d(t,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.nestTypeormEntityRegistries=nestTypeormEntityRegistries,exports.typeormArgsRegistries=typeormArgsRegistries,exports.defaultRegistries=defaultRegistries,exports.captureRegistries=captureRegistries,exports.supersededSince=supersededSince,exports.pruneRegistries=pruneRegistries,exports.registryPruningEnabled=registryPruningEnabled,exports.describePrune=describePrune;const isClass=o(t=>typeof t=="function","isClass");function filterInPlace(t,e){const r=t.filter(e),n=t.length-r.length;return n>0&&t.splice(0,t.length,...r),n}c(filterInPlace,"filterInPlace"),o(filterInPlace,"filterInPlace");const NEST_TYPEORM_STORAGE=/[\\/]@nestjs[\\/]typeorm[\\/]dist[\\/]entities-metadata\.storage\.js$/;function nestTypeormEntityRegistries(t){const e=[];for(const[r,n]of Object.entries(t)){if(!n||!NEST_TYPEORM_STORAGE.test(r))continue;const s=n.exports?.EntitiesMetadataStorage?.storage;if(!(s instanceof Map))continue;const i=o(()=>[...s.values()].filter(u=>Array.isArray(u)),"lists");e.push({id:"@nestjs/typeorm entities",phase:"build",classes:o(()=>i().flat().filter(isClass),"classes"),prune:o(u=>i().reduce((a,f)=>a+filterInPlace(f,p=>!(isClass(p)&&u.has(p))),0),"prune")})}return e}c(nestTypeormEntityRegistries,"nestTypeormEntityRegistries"),o(nestTypeormEntityRegistries,"nestTypeormEntityRegistries");function typeormArgsRegistries(t){const e=t.typeormMetadataArgsStorage;if(!e||typeof e!="object")return[];const r=o(()=>Object.values(e).filter(s=>Array.isArray(s)),"lists"),n=o(s=>s&&typeof s=="object"?s.target:void 0,"targetOf");return[{id:"typeorm metadata",phase:"serve",classes:o(()=>r().flat().map(n).filter(isClass),"classes"),prune:o(s=>r().reduce((i,u)=>i+filterInPlace(u,a=>{const f=n(a);return!(isClass(f)&&s.has(f))}),0),"prune")}]}c(typeormArgsRegistries,"typeormArgsRegistries"),o(typeormArgsRegistries,"typeormArgsRegistries");function defaultRegistries(t=require.cache,e=globalThis){return[...nestTypeormEntityRegistries(t),...typeormArgsRegistries(e)]}c(defaultRegistries,"defaultRegistries"),o(defaultRegistries,"defaultRegistries");function captureRegistries(t=defaultRegistries()){const e=new Map;for(const r of t){const n=e.get(r.id)??new Set;for(const s of r.classes())n.add(s);e.set(r.id,n)}return e}c(captureRegistries,"captureRegistries"),o(captureRegistries,"captureRegistries");function supersededSince(t,e=defaultRegistries()){const r=new Set;for(const[n,s]of captureRegistries(e)){const i=t.get(n)??new Set,u=new Set;for(const a of s)!i.has(a)&&a.name&&u.add(a.name);for(const a of i)a.name&&u.has(a.name)&&r.add(a)}return r}c(supersededSince,"supersededSince"),o(supersededSince,"supersededSince");function pruneRegistries(t,e,r=defaultRegistries()){if(t.size===0)return[];const n=[];for(const s of r){if(s.phase!==e)continue;const i=s.prune(t);i>0&&n.push({registry:s.id,removed:i})}return n}c(pruneRegistries,"pruneRegistries"),o(pruneRegistries,"pruneRegistries");function registryPruningEnabled(t=process.env){const e=(t.LENSMCP_SWAP_PRUNE_REGISTRIES??"").trim().toLowerCase();return!(e==="0"||e==="false"||e==="no"||e==="off")}c(registryPruningEnabled,"registryPruningEnabled"),o(registryPruningEnabled,"registryPruningEnabled");function describePrune(t,e){const r=t.reduce((s,i)=>s+i.removed,0);if(r===0)return null;const n=t.map(s=>`${s.registry} ${s.removed}`).join(", ");return`[child] released ${r} superseded class registration(s) ${e} (${n})`}c(describePrune,"describePrune"),o(describePrune,"describePrune");
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)+`
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(`
1
+ "use strict";var Ee=Object.defineProperty;var g=(o,u)=>Ee(o,"name",{value:u,configurable:!0});var ge=Object.defineProperty,i=g((o,u)=>ge(o,"name",{value:u,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")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),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(u){console.error("[gateway] Middleware error:",u)}}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,u){if(o.startsWith("*.")){const f=o.slice(1);return u.endsWith(f)||u===o.slice(2)}return u===o}g(hostMatches,"hostMatches"),i(hostMatches,"hostMatches");function matchGatewayRoute(o){if(GATEWAY_ROUTES.length===0)return;const u=(o.headers.host||"").split(":")[0],f=o.url||"/";for(const E of GATEWAY_ROUTES)if(!(E.host&&!hostMatches(E.host,u))&&!(E.prefix&&!f.startsWith(E.prefix)))return E}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 u=(o[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return u==="1"||u==="true"||u==="yes"||u==="on"}g(usesFixedInspectorPorts,"usesFixedInspectorPorts"),i(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(o,u,f=process.env){if(!usesFixedInspectorPorts(f))return 0;const E=Math.trunc(o)+Math.trunc(u);return Number.isInteger(E)&&E>1024&&E<=65535?E: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 u=/^wss?:\/\/[^/]*?:(\d+)\//.exec(o??""),f=u?Number(u[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 u=fs.lstatSync(o);u.isDirectory()&&(u.mode&63)!==0&&fs.chmodSync(o,448)}catch{}}g(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),i(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(o,u=process.pid){ensureOwnerOnlyDir(path.dirname(o));try{const f=sockOwnerPath(o);try{fs.unlinkSync(f)}catch{}fs.writeFileSync(f,String(u),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(o)}catch{}}g(claimSock,"claimSock"),i(claimSock,"claimSock");function cleanupSock(o,u=process.pid){if(o){try{const f=fs.readFileSync(sockOwnerPath(o),"utf8").trim();if(f&&f!==String(u))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:u}=require("node:crypto");return u("sha256").update(o).digest("hex").slice(0,8)}g(serviceKeyFingerprint,"serviceKeyFingerprint"),i(serviceKeyFingerprint,"serviceKeyFingerprint");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}return eval(`delete require.cache[${JSON.stringify(resolved)}]`),eval(`require(${JSON.stringify(resolved)})`)}g(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(o,u){let f=null;return(...E)=>{f&&clearTimeout(f),f=setTimeout(()=>o(...E),u)}}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($){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,$.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const E=inspector.url();E&&process.send&&process.send({type:"inspector-url",url:E})},"openInspector"),u=process.env.CHILD_DEBUG_PORT;if(u!==void 0&&u!==""){const f=Number(u);Number.isInteger(f)&&(f===0||f>1024)&&o(f)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let f=null,E=!1,$,G=!1;const k=(0,hot_swap_registries_1.registryPruningEnabled)(),M=i((r,s)=>{const h=(0,hot_swap_registries_1.describePrune)(r,s);h&&console.log(h)},"logPrune");let T={app:null,handler:i((r,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const L=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),W=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"),U=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),b=new Set;async function R(r,s){if(!r.closed&&(r.closed=!0,b.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)}if(r.superseded){try{M((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(h){console.error("[child] could not release the previous app's class registrations:",h)}r.superseded=void 0}}}g(R,"W"),i(R,"closeGeneration");async function Y(r){for(;b.size>=U;){const S=b.values().next().value;if(!S)break;await R(S,`closing a still-draining generation early \u2014 ${b.size} were pending and the cap is ${U} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}b.add(r);const s=Date.now(),h=i(()=>({inflight:r.inflight,work:W(r.workGen).count}),"busy");for(;L>0&&Date.now()-s<L;){if(r.closed)return;const S=h();if(S.inflight===0&&S.work===0)break;await new Promise(w=>{setTimeout(w,250).unref?.()})}if(r.closed)return;const _=h();_.inflight>0||_.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${_.inflight} in-flight request(s) and ${_.work} background job(s) still running. A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${L}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(Y,"J"),i(Y,"drainThenClose");async function x(){if(E)return G=!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,P)=>{console.error(`[child] uncaughtException (${P}):`,y)}),process.on("unhandledRejection",y=>{const P=y instanceof Error?y.stack||y.message:String(y);P.includes("FronteggContextScope")||P.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",y)});let r;if(k)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=void 0}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(P=>setTimeout(P,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'.`);let s;if(r)try{s=(0,hot_swap_registries_1.supersededSince)(r),M((0,hot_swap_registries_1.pruneRegistries)(s,"build"),"at the swap")}catch(y){console.error("[child] could not release superseded class registrations:",y)}const h=global.__lensmcpBeginWorkGeneration,_=typeof h=="function"?h():void 0;let S;try{S=await global.createChildApp()}catch(y){throw E=!1,y}const w=T;return s?.size&&(w.app?w.superseded=s:M((0,hot_swap_registries_1.pruneRegistries)(s,"serve"),"at the swap")),f=S,T={app:S,handler:S.handler,inflight:0,..._===void 0?{}:{workGen:_}},console.log("[child] Swapped to fresh app (previous draining in background)"),w.app&&Y(w).catch(y=>console.error("[child] background close error:",y)),E=!1,G?(G=!1,x()):S}g(x,"M"),i(x,"swapNow");const Q=debounce(async()=>{try{console.log("[child] /webpack/reload"),await x()}catch(r){console.error("[child] reload failed:",r)}},250),F=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})),Q();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const _=r.headers["x-internal-token"],S=process.env.LENSMCP_SERVICE_KEY;if(S&&_!==S){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const w=W();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:E,inflight:T.inflight,oldestInflightAt:T.oldestAt,activeWork:w.count,oldestWorkAt:w.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...$?{watcher:$}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const h=T;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)}),v=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(v),process.exit(0)});try{await x();const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);console.log(`[child] Using bundle: ${r}`),claimSock(v),F.listen(v,()=>{console.log(`[child] Listening on ${v}`),process.send&&process.send({type:"ready",socketPath:v})})}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);return}if(r.type==="reload")try{await x(),process.send&&process.send({type:"reloaded",socketPath:v})}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 p=i(async r=>{console.log(`[child ${r}] shutting down\u2026`);const s=f?.close().catch(_=>console.error("[child] close error:",_)),h=new Promise(_=>F.close(()=>_()));cleanupSock(v),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?.(),p(r)})})})()}else{let o=g(function(p){b.push(p),b.length>U&&b.splice(0,b.length-U),L?.write(p.replace(W,""));for(const r of R)try{const s=r.plain?p.replace(W,""):p;r.res.write(r.redact?redactInspectorUrls(s):s)}catch{R.delete(r)}},"W");i(o,"emitLogLine");const u=["\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)),$=LENSMCP_WS_KEY||path.basename(process.cwd()),G=i(p=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)($,SERVICE_NAME||"service",p)),"execPathFor");let k=(()=>{const p=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(p)&&p>1024?p:0})();const M=20;k&&(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 (${k}+slot, workers ${k}+${M}+i). Any account on this host can connect to 127.0.0.1:${k} 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 T=process.env.LENSMCP_LOG_FILE||"",L=T?(()=>{try{fs.mkdirSync(path.dirname(T),{recursive:!0});const p=fs.createWriteStream(T,{flags:"a",mode:384});try{fs.chmodSync(T,384)}catch{}return p}catch(p){return console.warn(`[parent] cannot open log file ${T}:`,p.message),null}})():null,W=/\x1b\[[0-9;]*m/g,U=2e3,b=[],R=new Set;for(const p of["log","info","warn","error"]){const r=console[p].bind(console);console[p]=(...s)=>{r(...s),o(s.map(h=>typeof h=="string"?h:util.inspect(h)).join(" ")+`
2
+ `)}}const Y=i(p=>u[(p-1)%u.length],"colorFor"),x=i(p=>{if(E===1)return"";const r=`[child#${p}] `;return`${Y(p)}${r}${f}`},"tagFor"),Q=i(p=>p.startsWith("{")&&p.endsWith("}"),"isJsonLog"),F=i((p,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(w=>`
3
+ `+String(w),"stack")}}),_=i((w,y)=>{const P=w==="stdout"?process.stdout:process.stderr;try{s?.(y)}catch{}const A=Q(y)?h(y):`${y}
4
+ `;P.write(`${r}${A}`),o(`${r}${A}`)},"write"),S=i((w,y)=>{if(!w)return;const P=readline.createInterface({input:w});P.on("line",A=>_(y,A)),P.on("close",()=>{})},"attach");S(p.stdout,"stdout"),S(p.stderr,"stderr")},"wireProcLogging"),v=i(p=>F(p.proc,x(p.id),r=>p.tail.push(r)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const p=httpProxy.createProxyServer({});let r=Number(process.env.PORT)||0;r||(r=9090);const s=[],h=new Set;function _(){let e=0;for(;h.has(e);)e++;return h.add(e),e}g(_,"w"),i(_,"allocDebugSlot");let S=1,w=0,y=!1,P=E,A=[];function Z(){return s.filter(e=>e.healthy&&typeof e.socketPath=="string")}g(Z,"te"),i(Z,"healthyChildren");let K;function ee(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){K=t;for(const d of s)try{d.proc.send?.({type:"watcher",testimony:t})}catch{}}}g(ee,"re"),i(ee,"recordWatcherTestimony");function te(e,t=256*1024){return new Promise(d=>{let c="",l=!1;e.on("data",n=>{l||(c+=n,c.length>t&&(l=!0))}),e.on("end",()=>{if(l||!c)return d(void 0);try{d(JSON.parse(c))}catch{d(void 0)}}),e.on("error",()=>d(void 0))})}g(te,"oe"),i(te,"readJsonBody");function H(){const e=Z();if(e.length===0)return null;const t=w%e.length;return w=(w+1)%e.length,e[t]}g(H,"Y"),i(H,"pickChild");async function V(){const e=S++,t=childSockPath(e);cleanupSock(t);const d=["--enable-source-maps"];__filename.endsWith(".ts")&&d.unshift("--require","@swc-node/register");const c=k?_():-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(k,c))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:d,execPath:G("pod")}),n={id:e,proc:l,healthy:!1,lastError:null,debugSlot:c,tail:new exit_reason_1.TailBuffer};return l.on("message",m=>{if(!m||typeof m!="object")return;const a=m;if(a.type==="ready"){if(n.socketPath=String(a.socketPath),n.healthy=!0,console.log(`[parent] Child#${n.id} ready on socket ${n.socketPath}`),K)try{l.send?.({type:"watcher",testimony:K})}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",(m,a)=>{n.debugSlot>=0&&(h.delete(n.debugSlot),n.debugSlot=-1);const N=a==="SIGINT"||a==="SIGTERM"||m===0;if(N?console.log(`[parent] Child#${n.id} exited cleanly (code=${m}, signal=${a??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${n.id} crashed (code=${m}, signal=${a??"none"}, pid=${n.proc.pid}, ${n.healthy?"was serving":"was not ready"})`,m,a,n.tail.snapshot())),n.lastError||(n.lastError=`Child exited abnormally (code=${m}, signal=${a??"none"})`)),n.healthy=!1,cleanupSock(n.socketPath,n.proc.pid),n.socketPath=void 0,!N&&!y){const O=s.indexOf(n);O>=0&&s.splice(O,1);const q=Date.now();if(A=A.filter(C=>q-C<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(q),setTimeout(()=>{const C=s.filter(ye=>ye.proc.killed===!1).length;!y&&C<P&&(console.log(`[parent] respawning a crashed pod (${C}/${P} live)`),V().catch(()=>{}))},1500).unref?.()}}),s.push(n),v(n),n}g(V,"Q"),i(V,"spawnChild");async function re(e){P=Math.max(P,e);const t=s.filter(c=>c.proc.killed===!1),d=e-t.length;for(let c=0;c<d;c++)await V()}g(re,"ne"),i(re,"ensurePoolSize");async function ce(){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 d=s.indexOf(t);d>=0&&s.splice(d,1),await V()}))}g(ce,"pe"),i(ce,"respawnUnhealthy");const I=[];function oe(e){const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),d=path.join(t,`${e}.js`);if(!fs.existsSync(d))return console.error(`[parent] Worker "${e}" bundle not found: ${d}`),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(k||9229,M+Math.max(0,WORKER_NAMES.indexOf(e)));c.push(k?`--inspect=127.0.0.1:${l}`:`--inspect-port=127.0.0.1:${l}`);const n=(0,node_child_process_1.fork)(d,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:c,execPath:G(`w-${e}`)}),m={name:e,proc:n,debugPort:l};return I.push(m),F(n,`\x1B[35m[worker:${e}] \x1B[0m`,a=>{const N=/Debugger listening on (wss?:\/\/\S+)/.exec(a);N&&(m.inspectorUrl=N[1])}),n.on("exit",(a,N)=>{N==="SIGINT"||N==="SIGTERM"||a===0||console.error(`[parent] Worker "${e}" crashed (code=${a}, signal=${N??"none"})`);const O=I.indexOf(m);O>=0&&I.splice(O,1)}),console.log(`[parent] Worker "${e}" started (pid=${n.pid})`),m}g(oe,"se"),i(oe,"spawnWorkerProcess");function ae(){for(const e of[...I])try{e.proc.kill("SIGTERM")}catch{}I.length=0;for(const e of WORKER_NAMES)oe(e)}g(ae,"de"),i(ae,"restartWorkers");const he=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 ce(),WORKER_NAMES.length>0&&ae()},200),B=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})),te(e).then(l=>ee(l?.watcher)),he();return}if(e.method==="POST"&&e.url==="/webpack/watching"){const l=await te(e);ee(l),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:K!==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 re(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),U),m=l.get("follow")!=="0",a=l.get("plain")==="1",N=!isOwnerOnlyTransport(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const O=n===0?"":b.slice(-n).join(""),q=a?O.replace(W,""):O;if(t.write(N?redactInspectorUrls(q):q),!m){t.end();return}const C={res:t,plain:a,redact:N};R.add(C),e.on("close",()=>R.delete(C));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&&(k=l),k||(k=9229);const n=s.filter(a=>a.proc.exitCode===null&&a.proc.signalCode===null);for(const a of n){a.debugSlot<0&&(a.debugSlot=_());try{a.proc.send?.({type:"debug-open",port:inspectorPortFor(k,a.debugSlot)})}catch{}}for(const a of I)try{a.proc.kill("SIGUSR1")}catch{}const m=Date.now()+2e3;for(;Date.now()<m&&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:k,fixedPorts:usesFixedInspectorPorts(),pods:n.map(a=>({id:a.id,inspectorUrl:a.inspectorUrl??null})),workers:I.map(a=>({name:a.name,port:a.debugPort||inspectorPortOf(a.inspectorUrl),inspectorUrl:a.inspectorUrl??null}))},null,2)+`
5
+ `);return}const d=matchGatewayRoute(e);if(d?.prependPrefix&&!e.url?.startsWith(d.prependPrefix)&&(e.url=d.prependPrefix+(e.url??"/")),d?.target){applyGatewayMiddleware(e),p.web(e,t,{target:d.target,autoRewrite:typeof d.target=="string",changeOrigin:!0},l=>{console.error(`[parent] proxy error to ${d.target}:`,l),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${d.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=H();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
- ${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()}})()}
9
+ ${l}`);return}p.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 D,j,ne;const J=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(c=>c.host).filter(c=>!!c),d=path.join(process.cwd(),"node_modules",".cache","davnx-webpack");j=e.getCertificateSync(d,SERVICE_NAME||"lensmcp.dev",t),ne=e.caCertPath(),D=require("node:https").createServer({key:j,cert:j},B)}else D=http.createServer(B);const le=i((e,t,d)=>{const c=matchGatewayRoute(e);if(c?.prependPrefix&&!e.url?.startsWith(c.prependPrefix)&&(e.url=c.prependPrefix+(e.url??"/")),c?.target){p.ws(e,t,d,{target:c.target,changeOrigin:!0},()=>t.destroy());return}const l=H();if(!l){t.destroy();return}p.ws(e,t,d,{target:{socketPath:l.socketPath}},()=>t.destroy())},"upgradeHandler");D.on("upgrade",le);const pe=2e3;let de=!1;D.on("error",e=>{if(e.code==="EADDRINUSE"){de||(de=!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 ${pe/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!y&&!D.listening&&D.listen(r)},pe).unref?.();return}console.error("[parent] public server error:",e)}),await re(E);for(const e of WORKER_NAMES)oe(e);D.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(" ")}`),ne&&(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 ${ne}`)),console.log(`POST ${e}://localhost:${r}/webpack/reload to trigger rolling swap/respawn`)});const X=Number(process.env.INTERNAL_PORT||0);if(X>0){const e=i((d,c)=>{const l=H();if(!l){c.statusCode=503,c.setHeader("content-type","text/plain; charset=utf-8"),c.end("No healthy children available.");return}p.web(d,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",(d,c,l)=>{const n=H();if(!n){c.destroy();return}p.ws(d,c,l,{target:{socketPath:n.socketPath}},()=>c.destroy())}),t.on("error",d=>{console.warn(`[parent] internal port ${X} unavailable (${d.code}).`)}),t.listen(X,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${X}`)}),J.push(t)}const fe=(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of fe){if(!Number.isInteger(e)||e===Number(r))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:j,cert:j},B):http.createServer(B);t.on("upgrade",le),t.on("error",d=>{console.warn(`[parent] extra port ${e} unavailable (${d.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),J.push(t)}const se=path.join(SOCK_DIR,"parent.sock");claimSock(se);const ie=http.createServer(B);ie.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),ie.listen(se),J.push(ie);const ue=i(async e=>{y=!0,console.log(`[${e}] [parent] shutting down\u2026`),cleanupSock(se);for(const t of J)try{t.close()}catch{}for(const t of I)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,()=>{ue(e)}));const z=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(z)&&z>0){const e=setInterval(()=>{try{process.kill(z,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${z}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),ue("gateway-gone")}},5e3);e.unref()}})()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.23.2",
3
+ "version": "1.23.4",
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.2",
61
- "@lensmcp/node-instrumentation": "1.23.2",
62
- "@lensmcp/nx-plugin": "1.23.2",
60
+ "@lensmcp/core": "1.23.4",
61
+ "@lensmcp/node-instrumentation": "1.23.4",
62
+ "@lensmcp/nx-plugin": "1.23.4",
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",
@@ -0,0 +1,21 @@
1
+ /** macOS keeps 31 characters (`p_name`, what Activity Monitor shows); `top` and Linux's comm show 15–16. */
2
+ export declare const PROCESS_NAME_MAX = 31;
3
+ /**
4
+ * `processName('foodguard', 'knowledge', 'pod')` → `foodguard-knowledge-pod`. The LAST part is the role
5
+ * and is kept whole; when the name is too long, the parts before it are shortened instead.
6
+ */
7
+ export declare function processName(...parts: Array<string | undefined>): string;
8
+ /** Where the links may live, in order — the first on the node binary's volume wins. */
9
+ export declare function defaultLinkDirs(env?: NodeJS.ProcessEnv): string[];
10
+ export declare function processNamesEnabled(env?: NodeJS.ProcessEnv): boolean;
11
+ export type NamedExecPathOptions = {
12
+ execPath?: string;
13
+ dirs?: string[];
14
+ env?: NodeJS.ProcessEnv;
15
+ platform?: NodeJS.Platform;
16
+ };
17
+ /**
18
+ * The path to execute node from so the process is listed as `name` — a hard link to `execPath` — or
19
+ * `execPath` itself when no link can be made. Never throws.
20
+ */
21
+ export declare function namedExecPath(name: string, options?: NamedExecPathOptions): string;
@@ -0,0 +1 @@
1
+ "use strict";var E=Object.defineProperty;var s=(t,e)=>E(t,"name",{value:e,configurable:!0});var m=Object.defineProperty,r=s((t,e)=>m(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PROCESS_NAME_MAX=void 0,exports.processName=processName,exports.defaultLinkDirs=defaultLinkDirs,exports.processNamesEnabled=processNamesEnabled,exports.namedExecPath=namedExecPath;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path"));exports.PROCESS_NAME_MAX=31;const sanitize=r(t=>(t??"").replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,""),"sanitize");function processName(...t){const e=t.map(sanitize).filter(Boolean),n=e.pop();if(n===void 0)return"";if(e.length===0)return n.slice(0,exports.PROCESS_NAME_MAX);const o=n.slice(0,exports.PROCESS_NAME_MAX-2),a=e.join("-").slice(0,exports.PROCESS_NAME_MAX-o.length-1).replace(/-+$/,"");return a?`${a}-${o}`:o}s(processName,"processName"),r(processName,"processName");function defaultLinkDirs(t=process.env){const e=t.LENSMCP_HOME||os.homedir();return[path.join(os.tmpdir(),"lensmcp-process-names"),path.join(e,".lensmcp","process-names")]}s(defaultLinkDirs,"defaultLinkDirs"),r(defaultLinkDirs,"defaultLinkDirs");function processNamesEnabled(t=process.env){const e=(t.LENSMCP_PROCESS_NAMES??"").trim().toLowerCase();return!(e==="0"||e==="false"||e==="no"||e==="off")}s(processNamesEnabled,"processNamesEnabled"),r(processNamesEnabled,"processNamesEnabled");function privateDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448});const e=fs.lstatSync(t);return!e.isDirectory()||typeof process.getuid=="function"&&e.uid!==process.getuid()?!1:((e.mode&63)!==0&&fs.chmodSync(t,448),!0)}catch{return!1}}s(privateDir,"privateDir"),r(privateDir,"privateDir");function isSameFile(t,e){try{const n=fs.lstatSync(t);return n.isFile()&&n.dev===e.dev&&n.ino===e.ino}catch{return!1}}s(isSameFile,"isSameFile"),r(isSameFile,"isSameFile");const resolved=new Map;function namedExecPath(t,e={}){const n=e.execPath??process.execPath,o=e.env??process.env,a=e.platform??process.platform,p=processName(t);if(!p||a==="win32"||!processNamesEnabled(o))return n;const l=e.dirs??defaultLinkDirs(o),f=`${n}|${p}|${l.join("|")}`,d=resolved.get(f);if(d)return d;let u,c;try{u=fs.realpathSync(n),c=fs.statSync(u)}catch{return n}for(const S of l){if(!privateDir(S))continue;const _=path.join(S,`${c.dev}-${c.ino}`);if(!privateDir(_))continue;const i=path.join(_,p);if(!isSameFile(i,c)){try{fs.unlinkSync(i)}catch{}try{fs.linkSync(u,i)}catch{}}if(isSameFile(i,c))return resolved.set(f,i),i}return n}s(namedExecPath,"namedExecPath"),r(namedExecPath,"namedExecPath");