@lensmcp/cluster 1.23.7 → 1.23.9
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/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/pod-output.d.ts +32 -1
- package/executors/gateway/runtime/pod-output.js +1 -1
- package/executors/gateway/runtime/types.d.ts +4 -0
- package/main.devserver.js +7 -7
- package/package.json +4 -4
- package/stdio-guard.d.ts +19 -0
- package/stdio-guard.js +1 -0
- package/tsgo-check-plugin.d.ts +3 -0
|
@@ -1,3 +1,3 @@
|
|
|
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,
|
|
2
|
-
`)){const
|
|
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
|
+
"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,E=!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){E=!0;return}let D;try{D=await fs.promises.readdir(R,{withFileTypes:!0})}catch{return}for(const q of D){if(S>=SCAN_FILE_CAP){E=!0;return}const x=q.name,V=path.join(R,x);if(q.isDirectory()){if(!X||x.startsWith(".")||SRC_EXCLUDE.has(x)||a?.has(x)||x.endsWith("-devserver")||L.has(`${h(V)}/`))continue;await T(V)}else if(q.isFile()){const Y=x.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(x.slice(Y)))continue;const P=h(V);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:E}}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 E=!0;for(const g of t.paths)if(!n.paths.has(g)){E=!1;break}if(E)return null}const f=[],S=[];let y=t.truncated||n.truncated;for(const E of n.paths)t.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?f.push(E):y=!0);for(const E of t.paths)n.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(E):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)),E=new Set([S]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||E.add(g);return[...E].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,E=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 p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const $=Number(p[1]),b=Number(p[2]),m=p[3];if(m.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(m)&&!(m.includes("/node_modules/nx/")&&!m.includes(e.root))&&!($===process.pid||$===r)&&!(r!==void 0&&b===r)){try{process.kill(-b,"SIGKILL")}catch{try{process.kill($,"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"),p=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[$,b]=fs.existsSync(d)?[p,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=l($,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,e.faults=new pod_output_1.PodFaults);const C=i(w=>{const _=w.toString();for(const O of _.split(`
|
|
3
|
+
`)){const u=(0,pod_output_1.classifyPodLine)(O),{clean:j,broken:k,healthy:M}=u,z=j;if(k){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const W=Date.now();(j!==e.lastErrorEmitSig||W-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=W,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j}))}M&&(e.buildFailed=!1),(e.faults??=new pod_output_1.PodFaults).observe(u)&&(e.lastErrorEmitSig=void 0,h(e,"info",`service recovered: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-recovered",project:e.project,line:j})),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(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||(F(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"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",w=>{if(t.stopped())return;C(w);const _=`[${e.project}] ${w}`;process.stdout.write(_),A?.(_)}),m.stderr?.on("data",w=>{if(t.stopped())return;C(w);const _=`[${e.project}] ${w}`;process.stderr.write(_),A?.(_)}),m.on("exit",(w,_)=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped()))return;const O=(0,exit_reason_1.describeExit)(w,_);if(console.log(`[gateway] ${r} exited (${w??"no code"}${_?`, ${_}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${O?` \u2014 ${O}`:""}`),h(e,w===0?"info":"warning",`service down: ${e.project} (exit ${w})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:w,..._?{signal:_}:{}}),w===0||e.buildFailed)return;const u=Date.now();if(e.restarts=(e.restarts??[]).filter(k=>u-k<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(u);const j=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))},j)}),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=X(e,e.generation,"active")},"spawnService"),q=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(T(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&&F(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"),F=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(()=>{J(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),J=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 p=X(e,c,"standby"),$=(async()=>{const b=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],m=d+y;let C;for(;!t.stopped()&&Date.now()<m&&!(p.exitCode!==null||p.signalCode!==null);){T(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,b))?.podGeneration===c){C=A;break}if(C)break;await sleep(E)}if(!C||t.stopped()){const A=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}: ${A} \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:A,durationMs:Date.now()-d}),setTimeout(()=>J(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(()=>{J(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=$,$},"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}),F(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}),F(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 Q;const G=new Map,oe=new Map;let Z=!1,ee=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 p=sourceScopeDirs(t.root,d,s,o,r);return p.length>0?p:void 0},"scopeFor"),le=i(()=>{if(Z||Date.now()-ee<types_1.POD_STALE_SCAN_MS)return;Z=!0,ee=Date.now();const e=ee;(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,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),$=Q?diffSourceSets(Q,c,d,p):null;if($&&(t.sourceSetChangedAt=d,t.sourceSetChange=$,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:$.added,removed:$.removed,truncated:$.truncated})),Q=c,!t.projectRoots){H=e;return}let b;try{b=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(u=>u.isDirectory()).map(u=>u.name)}catch{return}const m=ie(),C=new Set(t.services.map(u=>u.project));for(const u of t.routes)u.lens&&C.add(u.lens.project);const A=new Map;for(const u of C){const j=ae(u,b,m,s);if(!j)continue;const k=j.join("|"),M=A.get(k)??{dirs:j,projects:[]};M.projects.push(u),A.set(k,M)}const w=Date.now(),_=sourceChangeWindowMs(w,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const O=i((u,j,k)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of u)t.sourceSetChangedFor.set(M,j),t.sourceSetChangesFor.set(M,k)},"stampScoped");for(const[u,{dirs:j,projects:k}]of A){const M=await sourceSetSnapshot(t.root,j,s,r),z=G.get(u);G.set(u,M);const W=new Map;for(const N of k){const I=oe.get(N);if(I===void 0||I===u)continue;const K=W.get(I)??[];K.push(N),W.set(I,K)}for(const N of k)oe.set(N,u);for(const[N,I]of W){const K=G.get(N),U=K?diffSourceSets(K,M,w,_):$;U&&(O(I,K?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(!z)continue;const B=diffSourceSets(z,M,w,_);B&&(O(k,w,B),g("info",`source file set changed in ${k.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:k,added:B.added,removed:B.removed,truncated:B.truncated}))}for(const u of[...G.keys()])A.has(u)||G.delete(u)})().catch(()=>{}).finally(()=>{Z=!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;F(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){F(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 q(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:T,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:F,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");
|
|
@@ -15,13 +15,44 @@
|
|
|
15
15
|
*
|
|
16
16
|
* So the line is de-coloured FIRST and matched after, and the patterns are written against
|
|
17
17
|
* what the tools actually print.
|
|
18
|
+
*
|
|
19
|
+
* ## A fault is closed by evidence about the same thing
|
|
20
|
+
*
|
|
21
|
+
* "Broken" was reported and "healthy again" never was: an error line became a `service-error`
|
|
22
|
+
* event, a healthy line lowered the gateway's own flag and told nobody. A pod that hot-swaps
|
|
23
|
+
* never restarts, so nothing else told the lens either. Measured (foodguard, 2026-10-06): a
|
|
24
|
+
* type error that existed for five minutes left six services reading "failed to build" an hour
|
|
25
|
+
* later, each with seven clean checks behind it.
|
|
26
|
+
*
|
|
27
|
+
* And the healthy lines are not interchangeable. In that same log the type error is followed,
|
|
28
|
+
* a line later, by the NEXT build's `webpack 5.108.4 compiled successfully in 102 ms` — true of
|
|
29
|
+
* the bundle, silent about the types. So each line names WHICH fault it speaks to, and
|
|
30
|
+
* {@link PodFaults} closes a fault only on a line about that fault.
|
|
18
31
|
*/
|
|
32
|
+
/** What a pod line can say is broken. */
|
|
33
|
+
export type PodFault = 'build' | 'types' | 'process';
|
|
19
34
|
export interface PodLine {
|
|
20
35
|
/** The line with colour codes removed and whitespace trimmed — what gets reported. */
|
|
21
36
|
clean: string;
|
|
22
37
|
/** The build or the process is broken. */
|
|
23
38
|
broken: boolean;
|
|
24
|
-
/**
|
|
39
|
+
/** Something is healthy again — see `closes` for what. */
|
|
25
40
|
healthy: boolean;
|
|
41
|
+
/** What this line says is broken. */
|
|
42
|
+
fault?: PodFault;
|
|
43
|
+
/** The faults this line is evidence AGAINST. */
|
|
44
|
+
closes: readonly PodFault[];
|
|
26
45
|
}
|
|
27
46
|
export declare function classifyPodLine(raw: string): PodLine;
|
|
47
|
+
/**
|
|
48
|
+
* The faults a pod has open, as REPORTED to the lens.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately separate from the gateway's `buildFailed`: that flag decides whether a dead pod
|
|
51
|
+
* is respawned and how long a cold start waits, any healthy line lowers it, and an advisory
|
|
52
|
+
* type error must not hold it up. This one decides what the lens goes on believing.
|
|
53
|
+
*/
|
|
54
|
+
export declare class PodFaults {
|
|
55
|
+
private readonly open;
|
|
56
|
+
/** Fold one line. True when it closed the LAST open fault — the edge worth announcing. */
|
|
57
|
+
observe(line: PodLine): boolean;
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var n=(s,e)=>l(s,"name",{value:e,configurable:!0});var d=Object.defineProperty,r=n((s,e)=>d(s,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PodFaults=void 0,exports.classifyPodLine=classifyPodLine;const ANSI_SGR=/\x1b\[[0-9;]*m/g,OPENS=[["build",/ERROR in |Module not found/],["types",/error TS\d+|Found [1-9]\d* errors?/],["process",/Unhandled 'error' event|EADDRINUSE/]],CLOSES=[["build",/compiled successfully/],["types",/No typescript errors found|Found 0 errors|\[tsgo\] .+: types clean/],["process",/Child#\d+ (ready|hot-swapped)/]];function classifyPodLine(s){const e=s.replace(ANSI_SGR,"").trim(),t=OPENS.find(([,o])=>o.test(e))?.[0],i=t?[]:CLOSES.filter(([,o])=>o.test(e)).map(([o])=>o);return{clean:e,broken:t!==void 0,healthy:i.length>0,...t?{fault:t}:{},closes:i}}n(classifyPodLine,"classifyPodLine"),r(classifyPodLine,"classifyPodLine");class PodFaults{static{n(this,"PodFaults")}static{r(this,"PodFaults")}constructor(){this.open=new Set}observe(e){if(e.fault)return this.open.add(e.fault),!1;if(this.open.size===0)return!1;for(const t of e.closes)this.open.delete(t);return this.open.size===0}}exports.PodFaults=PodFaults;
|
|
@@ -13,6 +13,7 @@ import type { GatewayExecutorSchema } from '../schema';
|
|
|
13
13
|
import type { AuthPolicy, Attrs } from '../manifest';
|
|
14
14
|
import type { GatewayErrorReason } from '../gateway-errors';
|
|
15
15
|
import type { RouteRegistry } from './route-registry';
|
|
16
|
+
import type { PodFaults } from './pod-output';
|
|
16
17
|
export type { Attrs };
|
|
17
18
|
export interface GatewayContext {
|
|
18
19
|
root: string;
|
|
@@ -230,6 +231,9 @@ export interface ServiceCtl {
|
|
|
230
231
|
spawnAt?: number;
|
|
231
232
|
lastErrors?: string[];
|
|
232
233
|
buildFailed?: boolean;
|
|
234
|
+
/** The faults REPORTED to the lens and not yet taken back — what decides when `service-recovered` is
|
|
235
|
+
* announced. Not `buildFailed`, which is the gateway's own control flag (pod-output.ts). */
|
|
236
|
+
faults?: PodFaults;
|
|
233
237
|
/** Throttle state for the 'service error' event: the last-emitted error signature + when. Stops a
|
|
234
238
|
* crash-looping pod (re-printing the same error line every restart) from flooding the event bus —
|
|
235
239
|
* we re-emit only when the error line CHANGES or after {@link SERVICE_ERROR_THROTTLE_MS}. */
|
package/main.devserver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var Oe=Object.defineProperty;var u=(n,h)=>Oe(n,"name",{value:h,configurable:!0});var Ce=Object.defineProperty,i=u((n,h)=>Ce(n,"name",{value:h,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")),v8=tslib_1.__importStar(require("node:v8")),vm=tslib_1.__importStar(require("node:vm")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),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 n=process.env.GATEWAY_CONFIG;if(!n)return{};try{return JSON.parse(n)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const n=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof n=="function"?n:typeof n.default=="function"?n.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(n){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,n)}function applyGatewayMiddleware(n){if(gatewayMiddleware)try{gatewayMiddleware(n,GATEWAY_CONFIG)}catch(h){console.error("[gateway] Middleware error:",h)}}u(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(n,h){if(n.startsWith("*.")){const g=n.slice(1);return h.endsWith(g)||h===n.slice(2)}return h===n}u(hostMatches,"hostMatches"),i(hostMatches,"hostMatches");function matchGatewayRoute(n){if(GATEWAY_ROUTES.length===0)return;const h=(n.headers.host||"").split(":")[0],g=n.url||"/";for(const _ of GATEWAY_ROUTES)if(!(_.host&&!hostMatches(_.host,h))&&!(_.prefix&&!g.startsWith(_.prefix)))return _}u(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 n=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(n)&&n>1?Math.floor(n):1})();function childSockPath(n){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${n}-g${POD_GENERATION}.sock`:`child-${n}.sock`)}u(childSockPath,"childSockPath"),i(childSockPath,"childSockPath");function sockOwnerPath(n){return`${n}.owner`}u(sockOwnerPath,"sockOwnerPath"),i(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(n){return`${n}.retiring`}u(sockRetiringPath,"sockRetiringPath"),i(sockRetiringPath,"sockRetiringPath");function isOwnerOnlyTransport(n){try{return typeof n.socket?.server?.address?.()=="string"}catch{return!1}}u(isOwnerOnlyTransport,"isOwnerOnlyTransport"),i(isOwnerOnlyTransport,"isOwnerOnlyTransport"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(n=process.env){const h=(n[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return h==="1"||h==="true"||h==="yes"||h==="on"}u(usesFixedInspectorPorts,"usesFixedInspectorPorts"),i(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(n,h,g=process.env){if(!usesFixedInspectorPorts(g))return 0;const _=Math.trunc(n)+Math.trunc(h);return Number.isInteger(_)&&_>1024&&_<=65535?_:0}u(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(n){return n.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}u(redactInspectorUrls,"redactInspectorUrls"),i(redactInspectorUrls,"redactInspectorUrls");function inspectorPortOf(n){const h=/^wss?:\/\/[^/]*?:(\d+)\//.exec(n??""),g=h?Number(h[1]):NaN;return Number.isInteger(g)&&g>0?g:null}u(inspectorPortOf,"inspectorPortOf"),i(inspectorPortOf,"inspectorPortOf");function ensureOwnerOnlyDir(n){try{fs.mkdirSync(n,{recursive:!0,mode:448})}catch{}try{const h=fs.lstatSync(n);h.isDirectory()&&(h.mode&63)!==0&&fs.chmodSync(n,448)}catch{}}u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),i(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(n,h=process.pid){ensureOwnerOnlyDir(path.dirname(n));try{const g=sockOwnerPath(n);try{fs.unlinkSync(g)}catch{}fs.writeFileSync(g,String(h),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(sockRetiringPath(n))}catch{}}u(claimSock,"claimSock"),i(claimSock,"claimSock");function cleanupSock(n,h=process.pid){if(n){try{const g=fs.readFileSync(sockOwnerPath(n),"utf8").trim();if(g&&g!==String(h))return}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(sockOwnerPath(n))}catch{}try{fs.unlinkSync(sockRetiringPath(n))}catch{}}}u(cleanupSock,"cleanupSock"),i(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const n=process.env.LENSMCP_SERVICE_KEY;if(!n)return;const{createHash:h}=require("node:crypto");return h("sha256").update(n).digest("hex").slice(0,8)}u(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(`(() => {
|
|
1
|
+
"use strict";var Oe=Object.defineProperty;var u=(n,h)=>Oe(n,"name",{value:h,configurable:!0});var Ce=Object.defineProperty,i=u((n,h)=>Ce(n,"name",{value:h,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")),v8=tslib_1.__importStar(require("node:v8")),vm=tslib_1.__importStar(require("node:vm")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),stdio_guard_1=require("./stdio-guard"),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 n=process.env.GATEWAY_CONFIG;if(!n)return{};try{return JSON.parse(n)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const n=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof n=="function"?n:typeof n.default=="function"?n.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(n){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,n)}function applyGatewayMiddleware(n){if(gatewayMiddleware)try{gatewayMiddleware(n,GATEWAY_CONFIG)}catch(h){console.error("[gateway] Middleware error:",h)}}u(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(n,h){if(n.startsWith("*.")){const g=n.slice(1);return h.endsWith(g)||h===n.slice(2)}return h===n}u(hostMatches,"hostMatches"),i(hostMatches,"hostMatches");function matchGatewayRoute(n){if(GATEWAY_ROUTES.length===0)return;const h=(n.headers.host||"").split(":")[0],g=n.url||"/";for(const _ of GATEWAY_ROUTES)if(!(_.host&&!hostMatches(_.host,h))&&!(_.prefix&&!g.startsWith(_.prefix)))return _}u(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 n=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(n)&&n>1?Math.floor(n):1})();function childSockPath(n){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${n}-g${POD_GENERATION}.sock`:`child-${n}.sock`)}u(childSockPath,"childSockPath"),i(childSockPath,"childSockPath");function sockOwnerPath(n){return`${n}.owner`}u(sockOwnerPath,"sockOwnerPath"),i(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(n){return`${n}.retiring`}u(sockRetiringPath,"sockRetiringPath"),i(sockRetiringPath,"sockRetiringPath");function isOwnerOnlyTransport(n){try{return typeof n.socket?.server?.address?.()=="string"}catch{return!1}}u(isOwnerOnlyTransport,"isOwnerOnlyTransport"),i(isOwnerOnlyTransport,"isOwnerOnlyTransport"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(n=process.env){const h=(n[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return h==="1"||h==="true"||h==="yes"||h==="on"}u(usesFixedInspectorPorts,"usesFixedInspectorPorts"),i(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(n,h,g=process.env){if(!usesFixedInspectorPorts(g))return 0;const _=Math.trunc(n)+Math.trunc(h);return Number.isInteger(_)&&_>1024&&_<=65535?_:0}u(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(n){return n.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}u(redactInspectorUrls,"redactInspectorUrls"),i(redactInspectorUrls,"redactInspectorUrls");function inspectorPortOf(n){const h=/^wss?:\/\/[^/]*?:(\d+)\//.exec(n??""),g=h?Number(h[1]):NaN;return Number.isInteger(g)&&g>0?g:null}u(inspectorPortOf,"inspectorPortOf"),i(inspectorPortOf,"inspectorPortOf");function ensureOwnerOnlyDir(n){try{fs.mkdirSync(n,{recursive:!0,mode:448})}catch{}try{const h=fs.lstatSync(n);h.isDirectory()&&(h.mode&63)!==0&&fs.chmodSync(n,448)}catch{}}u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),i(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(n,h=process.pid){ensureOwnerOnlyDir(path.dirname(n));try{const g=sockOwnerPath(n);try{fs.unlinkSync(g)}catch{}fs.writeFileSync(g,String(h),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(sockRetiringPath(n))}catch{}}u(claimSock,"claimSock"),i(claimSock,"claimSock");function cleanupSock(n,h=process.pid){if(n){try{const g=fs.readFileSync(sockOwnerPath(n),"utf8").trim();if(g&&g!==String(h))return}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(sockOwnerPath(n))}catch{}try{fs.unlinkSync(sockRetiringPath(n))}catch{}}}u(cleanupSock,"cleanupSock"),i(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const n=process.env.LENSMCP_SERVICE_KEY;if(!n)return;const{createHash:h}=require("node:crypto");return h("sha256").update(n).digest("hex").slice(0,8)}u(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(`(() => {
|
|
2
2
|
const key = ${JSON.stringify(resolved)};
|
|
3
3
|
const previous = require.cache[key];
|
|
4
4
|
delete require.cache[key]; // delete from CJS cache
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
const at = owner && Array.isArray(owner.children) ? owner.children.indexOf(previous) : -1;
|
|
10
10
|
if (at >= 0) owner.children.splice(at, 1);
|
|
11
11
|
}
|
|
12
|
-
})()`),eval(`require(${JSON.stringify(resolved)})`)}u(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(n,h){let g=null;return(..._)=>{g&&clearTimeout(g),g=setTimeout(()=>n(..._),h)}}if(u(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){const n=i(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(B){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,B.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const _=inspector.url();_&&process.send&&process.send({type:"inspector-url",url:_})},"openInspector"),h=process.env.CHILD_DEBUG_PORT;if(h!==void 0&&h!==""){const g=Number(h);Number.isInteger(g)&&(g===0||g>1024)&&n(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,_=!1,B,j=!1;const v=(0,hot_swap_registries_1.registryPruningEnabled)(),Z=i((t,s)=>{const f=(0,hot_swap_registries_1.describePrune)(t,s);f&&console.log(f)},"logPrune");let $={app:null,handler:i((t,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const ee=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),Y=i(t=>{const s=globalThis.__lensmcpActiveWork;if(typeof s!="function")return{count:0};try{const f=s(t);return{count:Number(f?.count??0),...f?.oldestStartedAt?{oldestStartedAt:f.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),te=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),x=new Set,P=(0,swap_recycle_1.swapRecyclePolicy)();let oe,le=0;const ge=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let K=!1,V=!1,d;const E=i(()=>{if(d===void 0)try{v8.setFlagsFromString("--expose-gc");const t=vm.runInNewContext("gc");d=typeof t=="function"?t:null}catch{d=null}try{d?.()}catch{}return process.memoryUsage().heapUsed},"liveHeap"),w=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,s=`${path.sep}node_modules${path.sep}`,f=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:i(y=>f.has(y)||y.startsWith(t)&&!y.slice(t.length).includes(s)&&!y.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let O=!1;function W(){if(!(!w||O))try{const t=w();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),O=!0}}u(W,"z"),i(W,"releaseLibraryErrorFrames");const D=new Map;let b=Date.now();const T=i(()=>P.settleMs<=0||!_&&!j&&D.size===0&&Date.now()-b>=P.settleMs,"settled");let R;function A(t){if(!(!P.enabled||K||V)){if(P.settleMs<=0){F(t);return}R&&clearTimeout(R),R=setTimeout(()=>{R=void 0,T()?F(t):A(t)},P.settleMs),R.unref?.()}}u(A,"N"),i(A,"recycleCheckWhenSettled");const ne=i(()=>{if(P.stuckCloseMs<=0)return 0;const t=Date.now();let s=0;for(const f of D.values())t-f>=P.stuckCloseMs&&(s+=1);return s},"stuckCloses");function F(t){if(!P.enabled||K||V||!process.send)return;const s=v8.getHeapStatistics().heap_size_limit,f=i(m=>({swaps:le,usedBytes:m,limitBytes:s,stuckCloses:ne(),settled:T(),...oe===void 0?{}:{baselineBytes:oe}}),"state");if(!(0,swap_recycle_1.recycleReason)(P,f(process.memoryUsage().heapUsed)))return;const y=(0,swap_recycle_1.recycleReason)(P,f(E()));y&&(K=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${y}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),process.send({type:"recycle-request",reason:y}))}u(F,"K"),i(F,"considerRecycle");async function J(t,s){if(t.closed||(t.closed=!0,x.delete(t),s&&console.error(`[child] ${s}`),!t.app))return;D.set(t,Date.now());const f=P.enabled&&P.stuckCloseMs>0?setTimeout(()=>{D.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(P.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),F("with a previous app stuck in close()"))},P.stuckCloseMs):void 0;f?.unref?.();try{await t.app.close()}catch(y){console.error("[child] error closing a previous app:",y)}finally{D.delete(t),b=Date.now(),f&&clearTimeout(f),t.app=null,t.handler=(y,m)=>{m.statusCode=503,m.end("closed")}}if(t.superseded){try{Z((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(y){console.error("[child] could not release the previous app's class registrations:",y)}t.superseded=void 0}A("after closing the previous app")}u(J,"Q"),i(J,"closeGeneration");async function pe(t){for(;x.size>=te;){const m=x.values().next().value;if(!m)break;await J(m,`closing a still-draining generation early \u2014 ${x.size} were pending and the cap is ${te} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}x.add(t);const s=Date.now(),f=i(()=>({inflight:t.inflight,work:Y(t.workGen).count}),"busy");for(;ee>0&&Date.now()-s<ee;){if(t.closed)return;const m=f();if(m.inflight===0&&m.work===0)break;await new Promise(C=>{setTimeout(C,250).unref?.()})}if(t.closed)return;const y=f();y.inflight>0||y.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${y.inflight} in-flight request(s) and ${y.work} background job(s) still running. A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${ee}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 J(t)}u(pe,"he"),i(pe,"drainThenClose");async function M(){if(_)return j=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;_=!0,b=Date.now(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(S,L)=>{console.error(`[child] uncaughtException (${L}):`,S)}),process.on("unhandledRejection",S=>{const L=S instanceof Error?S.stack||S.message:String(S);L.includes("FronteggContextScope")||L.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",S)});let t;if(v)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}try{try{await importFresh(BUNDLE_PATH)}catch(S){if(!(S instanceof SyntaxError))throw S;console.warn(`[child] bundle was unreadable mid-write (${S.message}) \u2014 retrying the swap once`),await new Promise(L=>setTimeout(L,400)),await importFresh(BUNDLE_PATH)}}catch(S){throw _=!1,b=Date.now(),S}if(typeof global.createChildApp!="function")throw _=!1,b=Date.now(),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(t)try{s=(0,hot_swap_registries_1.supersededSince)(t),Z((0,hot_swap_registries_1.pruneRegistries)(s,"build"),"at the swap")}catch(S){console.error("[child] could not release superseded class registrations:",S)}const f=global.__lensmcpBeginWorkGeneration,y=typeof f=="function"?f():void 0;let m;try{m=await global.createChildApp()}catch(S){throw _=!1,b=Date.now(),S}const C=$;return s?.size&&(C.app?C.superseded=s:Z((0,hot_swap_registries_1.pruneRegistries)(s,"serve"),"at the swap")),g=m,$={app:m,handler:m.handler,inflight:0,...y===void 0?{}:{workGen:y}},console.log("[child] Swapped to fresh app (previous draining in background)"),W(),C.app&&(le+=1,pe(C).catch(S=>console.error("[child] background close error:",S)),setTimeout(()=>F("30s after a hot swap"),3e4).unref?.()),_=!1,b=Date.now(),j?(j=!1,M()):m}u(M,"V"),i(M,"swapNow");const re=debounce(async()=>{try{console.log("[child] /webpack/reload"),await M()}catch(t){console.error("[child] reload failed:",t)}},250),G=http.createServer((t,s)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({ok:!0})),re();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const y=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&y!==m){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const C=Y();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:_,inflight:$.inflight,oldestInflightAt:$.oldestAt,activeWork:C.count,oldestWorkAt:C.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...B?{watcher:B}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const f=$;f.inflight+=1,f.oldestAt===void 0&&(f.oldestAt=Date.now()),s.on("close",()=>{f.inflight-=1,f.inflight===0&&(f.oldestAt=void 0)}),f.handler(t,s)});G.keepAliveTimeout=0;const I=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(I),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const s=(0,schema_first_instance_1.prewarmZodFor)(t);s?.created&&console.log(`[child] zod${s.version?` ${s.version}`:""}: built ${s.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const s=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);s?.patched&&console.log(`[child] ioredis${s.versions.length?` ${s.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await M(),P.enabled&&P.heapFactor>0&&(oe=E()),console.log(`[child] Using bundle: ${t}`),claimSock(I),G.listen(I,()=>{console.log(`[child] Listening on ${I}`),process.send&&process.send({type:"ready",socketPath:I})})}catch(t){console.error("[child] Startup error:",t),process.send&&process.send({type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const s=Number(t.port);n(Number.isInteger(s)&&s>1024?s:0);return}if(t.type==="watcher"){const s=t.testimony;s&&typeof s=="object"&&(B=s);return}if(t.type==="recycle-declined"){K=!1;return}if(t.type==="retire"){U();return}if(t.type==="reload"){if(V)return;try{await M(),process.send&&process.send({type:"reloaded",socketPath:I})}catch(s){console.error("[child] reload error:",s),process.send&&process.send({type:"reload-error",error:String(s?.stack||s)})}}}});async function U(){if(V)return;V=!0,console.log("[child] retiring \u2014 a fresh pod has taken over; finishing in-flight work, then exiting");try{fs.writeFileSync(sockRetiringPath(I),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(s=>{setTimeout(s,ge).unref?.()}),cleanupSock(I),G.close();const t=Date.now();for(;Date.now()-t<ee&&(G.closeIdleConnections?.(),!($.inflight===0&&Y().count===0));)await new Promise(s=>{setTimeout(s,250).unref?.()});setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(s){console.error("[child] close error while retiring:",s)}process.exit(0)}u(U,"ee"),i(U,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const X=i(async t=>{console.log(`[child ${t}] shutting down\u2026`);const s=g?.close().catch(y=>console.error("[child] close error:",y)),f=new Promise(y=>G.close(()=>y()));cleanupSock(I),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([s,f]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{inspector?.close?.(),X(t)})})})()}else{let n=u(function(d){x.push(d),x.length>te&&x.splice(0,x.length-te),ee?.write(d.replace(Y,""));for(const E of P)try{const w=E.plain?d.replace(Y,""):d;E.res.write(E.redact?redactInspectorUrls(w):w)}catch{P.delete(E)}},"P");i(n,"emitLogLine");const h=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],g="\x1B[0m",_=Math.max(1,Number(process.env.CHILD_COUNT||1)),B=LENSMCP_WS_KEY||path.basename(process.cwd()),j=i(d=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(B,SERVICE_NAME||"service",d)),"execPathFor");let v=(()=>{const d=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(d)&&d>1024?d:0})();const Z=20;v&&(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 (${v}+slot, workers ${v}+${Z}+i). Any account on this host can connect to 127.0.0.1:${v} 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 $=process.env.LENSMCP_LOG_FILE||"",ee=$?(()=>{try{fs.mkdirSync(path.dirname($),{recursive:!0});const d=fs.createWriteStream($,{flags:"a",mode:384});try{fs.chmodSync($,384)}catch{}return d}catch(d){return console.warn(`[parent] cannot open log file ${$}:`,d.message),null}})():null,Y=/\x1b\[[0-9;]*m/g,te=2e3,x=[],P=new Set;for(const d of["log","info","warn","error"]){const E=console[d].bind(console);console[d]=(...w)=>{E(...w),n(w.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
-
`)}}const oe=i(
|
|
12
|
+
})()`),eval(`require(${JSON.stringify(resolved)})`)}u(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(n,h){let g=null;return(..._)=>{g&&clearTimeout(g),g=setTimeout(()=>n(..._),h)}}if(u(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const n=i(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(B){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,B.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const _=inspector.url();_&&process.send&&process.send({type:"inspector-url",url:_})},"openInspector"),h=process.env.CHILD_DEBUG_PORT;if(h!==void 0&&h!==""){const g=Number(h);Number.isInteger(g)&&(g===0||g>1024)&&n(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,_=!1,B,j=!1;const v=(0,hot_swap_registries_1.registryPruningEnabled)(),Z=i((t,s)=>{const f=(0,hot_swap_registries_1.describePrune)(t,s);f&&console.log(f)},"logPrune");let $={app:null,handler:i((t,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const ee=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),Y=i(t=>{const s=globalThis.__lensmcpActiveWork;if(typeof s!="function")return{count:0};try{const f=s(t);return{count:Number(f?.count??0),...f?.oldestStartedAt?{oldestStartedAt:f.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),te=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),x=new Set,P=(0,swap_recycle_1.swapRecyclePolicy)();let oe,le=0;const ge=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let K=!1,V=!1,p;const E=i(()=>{if(p===void 0)try{v8.setFlagsFromString("--expose-gc");const t=vm.runInNewContext("gc");p=typeof t=="function"?t:null}catch{p=null}try{p?.()}catch{}return process.memoryUsage().heapUsed},"liveHeap"),w=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,s=`${path.sep}node_modules${path.sep}`,f=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:i(y=>f.has(y)||y.startsWith(t)&&!y.slice(t.length).includes(s)&&!y.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let O=!1;function W(){if(!(!w||O))try{const t=w();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),O=!0}}u(W,"z"),i(W,"releaseLibraryErrorFrames");const D=new Map;let b=Date.now();const T=i(()=>P.settleMs<=0||!_&&!j&&D.size===0&&Date.now()-b>=P.settleMs,"settled");let R;function A(t){if(!(!P.enabled||K||V)){if(P.settleMs<=0){F(t);return}R&&clearTimeout(R),R=setTimeout(()=>{R=void 0,T()?F(t):A(t)},P.settleMs),R.unref?.()}}u(A,"N"),i(A,"recycleCheckWhenSettled");const ne=i(()=>{if(P.stuckCloseMs<=0)return 0;const t=Date.now();let s=0;for(const f of D.values())t-f>=P.stuckCloseMs&&(s+=1);return s},"stuckCloses");function F(t){if(!P.enabled||K||V||!process.send)return;const s=v8.getHeapStatistics().heap_size_limit,f=i(m=>({swaps:le,usedBytes:m,limitBytes:s,stuckCloses:ne(),settled:T(),...oe===void 0?{}:{baselineBytes:oe}}),"state");if(!(0,swap_recycle_1.recycleReason)(P,f(process.memoryUsage().heapUsed)))return;const y=(0,swap_recycle_1.recycleReason)(P,f(E()));y&&(K=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${y}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),process.send({type:"recycle-request",reason:y}))}u(F,"K"),i(F,"considerRecycle");async function J(t,s){if(t.closed||(t.closed=!0,x.delete(t),s&&console.error(`[child] ${s}`),!t.app))return;D.set(t,Date.now());const f=P.enabled&&P.stuckCloseMs>0?setTimeout(()=>{D.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(P.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),F("with a previous app stuck in close()"))},P.stuckCloseMs):void 0;f?.unref?.();try{await t.app.close()}catch(y){console.error("[child] error closing a previous app:",y)}finally{D.delete(t),b=Date.now(),f&&clearTimeout(f),t.app=null,t.handler=(y,m)=>{m.statusCode=503,m.end("closed")}}if(t.superseded){try{Z((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(y){console.error("[child] could not release the previous app's class registrations:",y)}t.superseded=void 0}A("after closing the previous app")}u(J,"Q"),i(J,"closeGeneration");async function de(t){for(;x.size>=te;){const m=x.values().next().value;if(!m)break;await J(m,`closing a still-draining generation early \u2014 ${x.size} were pending and the cap is ${te} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}x.add(t);const s=Date.now(),f=i(()=>({inflight:t.inflight,work:Y(t.workGen).count}),"busy");for(;ee>0&&Date.now()-s<ee;){if(t.closed)return;const m=f();if(m.inflight===0&&m.work===0)break;await new Promise(C=>{setTimeout(C,250).unref?.()})}if(t.closed)return;const y=f();y.inflight>0||y.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${y.inflight} in-flight request(s) and ${y.work} background job(s) still running. A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${ee}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 J(t)}u(de,"he"),i(de,"drainThenClose");async function M(){if(_)return j=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;_=!0,b=Date.now(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(S,L)=>{console.error(`[child] uncaughtException (${L}):`,S)}),process.on("unhandledRejection",S=>{const L=S instanceof Error?S.stack||S.message:String(S);L.includes("FronteggContextScope")||L.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",S)});let t;if(v)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}try{try{await importFresh(BUNDLE_PATH)}catch(S){if(!(S instanceof SyntaxError))throw S;console.warn(`[child] bundle was unreadable mid-write (${S.message}) \u2014 retrying the swap once`),await new Promise(L=>setTimeout(L,400)),await importFresh(BUNDLE_PATH)}}catch(S){throw _=!1,b=Date.now(),S}if(typeof global.createChildApp!="function")throw _=!1,b=Date.now(),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(t)try{s=(0,hot_swap_registries_1.supersededSince)(t),Z((0,hot_swap_registries_1.pruneRegistries)(s,"build"),"at the swap")}catch(S){console.error("[child] could not release superseded class registrations:",S)}const f=global.__lensmcpBeginWorkGeneration,y=typeof f=="function"?f():void 0;let m;try{m=await global.createChildApp()}catch(S){throw _=!1,b=Date.now(),S}const C=$;return s?.size&&(C.app?C.superseded=s:Z((0,hot_swap_registries_1.pruneRegistries)(s,"serve"),"at the swap")),g=m,$={app:m,handler:m.handler,inflight:0,...y===void 0?{}:{workGen:y}},console.log("[child] Swapped to fresh app (previous draining in background)"),W(),C.app&&(le+=1,de(C).catch(S=>console.error("[child] background close error:",S)),setTimeout(()=>F("30s after a hot swap"),3e4).unref?.()),_=!1,b=Date.now(),j?(j=!1,M()):m}u(M,"V"),i(M,"swapNow");const re=debounce(async()=>{try{console.log("[child] /webpack/reload"),await M()}catch(t){console.error("[child] reload failed:",t)}},250),G=http.createServer((t,s)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({ok:!0})),re();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const y=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&y!==m){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const C=Y();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:_,inflight:$.inflight,oldestInflightAt:$.oldestAt,activeWork:C.count,oldestWorkAt:C.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...B?{watcher:B}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const f=$;f.inflight+=1,f.oldestAt===void 0&&(f.oldestAt=Date.now()),s.on("close",()=>{f.inflight-=1,f.inflight===0&&(f.oldestAt=void 0)}),f.handler(t,s)});G.keepAliveTimeout=0;const I=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(I),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const s=(0,schema_first_instance_1.prewarmZodFor)(t);s?.created&&console.log(`[child] zod${s.version?` ${s.version}`:""}: built ${s.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const s=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);s?.patched&&console.log(`[child] ioredis${s.versions.length?` ${s.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await M(),P.enabled&&P.heapFactor>0&&(oe=E()),console.log(`[child] Using bundle: ${t}`),claimSock(I),G.listen(I,()=>{console.log(`[child] Listening on ${I}`),process.send&&process.send({type:"ready",socketPath:I})})}catch(t){console.error("[child] Startup error:",t),process.send&&process.send({type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const s=Number(t.port);n(Number.isInteger(s)&&s>1024?s:0);return}if(t.type==="watcher"){const s=t.testimony;s&&typeof s=="object"&&(B=s);return}if(t.type==="recycle-declined"){K=!1;return}if(t.type==="retire"){U();return}if(t.type==="reload"){if(V)return;try{await M(),process.send&&process.send({type:"reloaded",socketPath:I})}catch(s){console.error("[child] reload error:",s),process.send&&process.send({type:"reload-error",error:String(s?.stack||s)})}}}});async function U(){if(V)return;V=!0,console.log("[child] retiring \u2014 a fresh pod has taken over; finishing in-flight work, then exiting");try{fs.writeFileSync(sockRetiringPath(I),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(s=>{setTimeout(s,ge).unref?.()}),cleanupSock(I),G.close();const t=Date.now();for(;Date.now()-t<ee&&(G.closeIdleConnections?.(),!($.inflight===0&&Y().count===0));)await new Promise(s=>{setTimeout(s,250).unref?.()});setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(s){console.error("[child] close error while retiring:",s)}process.exit(0)}u(U,"ee"),i(U,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const X=i(async t=>{console.log(`[child ${t}] shutting down\u2026`);const s=g?.close().catch(y=>console.error("[child] close error:",y)),f=new Promise(y=>G.close(()=>y()));cleanupSock(I),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([s,f]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{inspector?.close?.(),X(t)})})})()}else{let n=u(function(p){x.push(p),x.length>te&&x.splice(0,x.length-te),ee?.write(p.replace(Y,""));for(const E of P)try{const w=E.plain?p.replace(Y,""):p;E.res.write(E.redact?redactInspectorUrls(w):w)}catch{P.delete(E)}},"P");i(n,"emitLogLine");const h=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],g="\x1B[0m",_=Math.max(1,Number(process.env.CHILD_COUNT||1)),B=LENSMCP_WS_KEY||path.basename(process.cwd()),j=i(p=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(B,SERVICE_NAME||"service",p)),"execPathFor");let v=(()=>{const p=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(p)&&p>1024?p:0})();const Z=20;v&&(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 (${v}+slot, workers ${v}+${Z}+i). Any account on this host can connect to 127.0.0.1:${v} 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 $=process.env.LENSMCP_LOG_FILE||"",ee=$?(()=>{try{fs.mkdirSync(path.dirname($),{recursive:!0});const p=fs.createWriteStream($,{flags:"a",mode:384});try{fs.chmodSync($,384)}catch{}return p}catch(p){return console.warn(`[parent] cannot open log file ${$}:`,p.message),null}})():null,Y=/\x1b\[[0-9;]*m/g,te=2e3,x=[],P=new Set;for(const p of["log","info","warn","error"]){const E=console[p].bind(console);console[p]=(...w)=>{E(...w),n(w.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
+
`)}}const oe=i(p=>h[(p-1)%h.length],"colorFor"),le=i(p=>{if(_===1)return"";const E=`[child#${p}] `;return`${oe(p)}${E}${g}`},"tagFor"),ge=i(p=>p.startsWith("{")&&p.endsWith("}"),"isJsonLog"),K=i((p,E,w)=>{const O=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(b=>`
|
|
14
14
|
`+String(b),"stack")}}),W=i((b,T)=>{const R=b==="stdout"?process.stdout:process.stderr;try{w?.(T)}catch{}const A=ge(T)?O(T):`${T}
|
|
15
|
-
`;R.write(`${E}${A}`),n(`${E}${A}`)},"write"),D=i((b,T)=>{if(!b)return;const R=readline.createInterface({input:b});R.on("line",A=>W(T,A)),R.on("close",()=>{})},"attach");D(d.stdout,"stdout"),D(d.stderr,"stderr")},"wireProcLogging"),V=i(d=>K(d.proc,le(d.id),E=>d.tail.push(E)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const d=httpProxy.createProxyServer({});let E=Number(process.env.PORT)||0;E||(E=9090);const w=[],O=new Set;function W(){let e=0;for(;O.has(e);)e++;return O.add(e),e}u(W,"z"),i(W,"allocDebugSlot");let D=1,b=0,T=!1,R=_,A=[],ne=0;const F=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),J=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),pe=i(e=>e.booting&&!J(e)&&Date.now()-e.spawnedAt<F,"stillBooting");function M(){return w.filter(e=>e.healthy&&typeof e.socketPath=="string")}u(M,"V"),i(M,"healthyChildren");let re;function G(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){re=r;for(const p of w)try{p.proc.send?.({type:"watcher",testimony:r})}catch{}}}u(G,"U"),i(G,"recordWatcherTestimony");function I(e,r=256*1024){return new Promise(p=>{let a="",c=!1;e.on("data",o=>{c||(a+=o,a.length>r&&(c=!0))}),e.on("end",()=>{if(c||!a)return p(void 0);try{p(JSON.parse(a))}catch{p(void 0)}}),e.on("error",()=>p(void 0))})}u(I,"v"),i(I,"readJsonBody");function U(){const e=M();if(e.length===0)return null;const r=b%e.length;return b=(b+1)%e.length,e[r]}u(U,"ee"),i(U,"pickChild");async function X(){const e=D++,r=childSockPath(e);cleanupSock(r);const p=["--enable-source-maps"];__filename.endsWith(".ts")&&p.unshift("--require","@swc-node/register");const a=v?W():-1,c=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...a>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(v,a))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:p,execPath:j("pod")}),o={id:e,proc:c,healthy:!1,lastError:null,debugSlot:a,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ne};return c.on("message",k=>{if(!k||typeof k!="object")return;const l=k;if(l.type==="ready"){if(o.socketPath=String(l.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),re)try{c.send?.({type:"watcher",testimony:re})}catch{}if(ne!==o.bootReloadSeq)try{c.send?.({type:"reload"})}catch{}}else l.type==="recycle-request"?L(o,String(l.reason||"it has outgrown its bound")):l.type==="inspector-url"?(o.inspectorUrl=String(l.url),console.log(`[parent] Child#${o.id} Debugger listening on ${l.url}`)):l.type==="reloaded"?(o.socketPath=String(l.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`)):l.type==="reload-error"?(o.lastError=String(l.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):l.type==="boot-error"&&(o.lastError=String(l.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1)}),c.on("exit",(k,l)=>{o.booting=!1,o.debugSlot>=0&&(O.delete(o.debugSlot),o.debugSlot=-1);const N=l==="SIGINT"||l==="SIGTERM"||k===0;if(N?console.log(`[parent] Child#${o.id} exited cleanly (code=${k}, signal=${l??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${k}, signal=${l??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,k,l,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${k}, signal=${l??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!N&&!T){const q=w.indexOf(o);q>=0&&w.splice(q,1);const ae=Date.now();if(A=A.filter(Q=>ae-Q<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(ae),setTimeout(()=>{const Q=w.filter($e=>$e.proc.killed===!1).length;!T&&Q<R&&(console.log(`[parent] respawning a crashed pod (${Q}/${R} live)`),X().catch(()=>{}))},1500).unref?.()}}),w.push(o),V(o),o}u(X,"te"),i(X,"spawnChild");async function t(e){R=Math.max(R,e);const r=w.filter(a=>a.proc.killed===!1),p=e-r.length;for(let a=0;a<p;a++)await X()}u(t,"r"),i(t,"ensurePoolSize");async function s(){const e=w.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!pe(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const p=w.indexOf(r);p>=0&&w.splice(p,1),await X()}))}u(s,"c"),i(s,"respawnUnhealthy");const f=new Set;let y=!1,m=0;const C=3e4,S=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function L(e,r){const p=i(c=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${c}`);try{e.proc.send?.({type:"recycle-declined"})}catch{}},"decline");if(T||!w.includes(e))return;if(y)return p("another pod is already being replaced");const a=Date.now()-m;if(a<C)return p(`a pod was replaced ${Math.round(a/1e3)}s ago`);y=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const c=await X(),o=Date.now()+F;for(;!c.healthy&&!c.lastError&&!J(c)&&Date.now()<o;)await new Promise(N=>{setTimeout(N,100).unref?.()});if(T)return;if(!c.healthy){const N=w.indexOf(c);N>=0&&w.splice(N,1);try{c.proc.kill("SIGTERM")}catch{}return p(`its successor Child#${c.id} did not come up (${c.lastError??"no answer in time"})`)}const k=w.indexOf(e);k>=0&&w.splice(k,1),e.healthy=!1,f.add(e);const l=setTimeout(()=>{if(!J(e)){console.error(`[parent] Child#${e.id} was still running ${S}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},S);l.unref?.(),e.proc.once("exit",()=>{clearTimeout(l),f.delete(e)});try{e.proc.send?.({type:"retire"})}catch{try{e.proc.kill("SIGTERM")}catch{}}m=Date.now(),console.log(`[parent] Child#${c.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${c.proc.pid})`)}catch(c){p(`could not start a successor: ${c?.message??String(c)}`)}finally{y=!1}}u(L,"x"),i(L,"recyclePod");const H=[];function ye(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),p=path.join(r,`${e}.js`);if(!fs.existsSync(p))return console.error(`[parent] Worker "${e}" bundle not found: ${p}`),null;const a=["--enable-source-maps"];__filename.endsWith(".ts")&&a.unshift("--require","@swc-node/register");try{a.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const c=inspectorPortFor(v||9229,Z+Math.max(0,WORKER_NAMES.indexOf(e)));a.push(v?`--inspect=127.0.0.1:${c}`:`--inspect-port=127.0.0.1:${c}`);const o=(0,node_child_process_1.fork)(p,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:a,execPath:j(`w-${e}`)}),k={name:e,proc:o,debugPort:c};return H.push(k),K(o,`\x1B[35m[worker:${e}] \x1B[0m`,l=>{const N=/Debugger listening on (wss?:\/\/\S+)/.exec(l);N&&(k.inspectorUrl=N[1])}),o.on("exit",(l,N)=>{N==="SIGINT"||N==="SIGTERM"||l===0||console.error(`[parent] Worker "${e}" crashed (code=${l}, signal=${N??"none"})`);const q=H.indexOf(k);q>=0&&H.splice(q,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),k}u(ye,"we"),i(ye,"spawnWorkerProcess");const me=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),Se=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),se=new Set,Pe=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function de(e,r){if(se.delete(e),!Pe(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}u(de,"fe"),i(de,"killDrainingWorker");function ke(e){try{e.proc.kill("SIGTERM")}catch{}if(Pe(e))return;const r=[...se].filter(a=>a.name===e.name);for(;r.length>=Se;)de(r.shift(),`was still running when a later reload replaced its successor (cap ${Se})`);se.add(e);const p=setTimeout(()=>de(e,`was still running ${me}ms after SIGTERM`),me);p.unref?.(),e.proc.once("exit",()=>{clearTimeout(p),se.delete(e)})}u(ke,"Ae"),i(ke,"retireWorker");function be(){for(const e of[...H])ke(e);H.length=0;for(const e of WORKER_NAMES)ye(e)}u(be,"Te"),i(be,"restartWorkers");const Ae=debounce(async()=>{ne+=1;const e=M();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)r.proc.send?.({type:"reload"})}await s(),WORKER_NAMES.length>0&&be()},200),ie=i(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),I(e).then(c=>G(c?.watcher)),Ae();return}if(e.method==="POST"&&e.url==="/webpack/watching"){const c=await I(e);G(c),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:re!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){let c="";e.on("data",o=>{c+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(c||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await t(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const c=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(c.get("tail")??"100")||0,0),te),k=c.get("follow")!=="0",l=c.get("plain")==="1",N=!isOwnerOnlyTransport(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const q=o===0?"":x.slice(-o).join(""),ae=l?q.replace(Y,""):q;if(r.write(N?redactInspectorUrls(ae):ae),!k){r.end();return}const Q={res:r,plain:l,redact:N};P.add(Q),e.on("close",()=>P.delete(Q));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!isOwnerOnlyTransport(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const c=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(c)&&c>1024&&(v=c),v||(v=9229);const o=w.filter(l=>l.proc.exitCode===null&&l.proc.signalCode===null);for(const l of o){l.debugSlot<0&&(l.debugSlot=W());try{l.proc.send?.({type:"debug-open",port:inspectorPortFor(v,l.debugSlot)})}catch{}}for(const l of H)try{l.proc.kill("SIGUSR1")}catch{}const k=Date.now()+2e3;for(;Date.now()<k&&o.some(l=>!l.inspectorUrl);)await new Promise(l=>setTimeout(l,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:v,fixedPorts:usesFixedInspectorPorts(),pods:o.map(l=>({id:l.id,inspectorUrl:l.inspectorUrl??null})),workers:H.map(l=>({name:l.name,port:l.debugPort||inspectorPortOf(l.inspectorUrl),inspectorUrl:l.inspectorUrl??null}))},null,2)+`
|
|
16
|
-
`);return}const
|
|
15
|
+
`;R.write(`${E}${A}`),n(`${E}${A}`)},"write"),D=i((b,T)=>{if(!b)return;const R=readline.createInterface({input:b});R.on("line",A=>W(T,A)),R.on("close",()=>{})},"attach");D(p.stdout,"stdout"),D(p.stderr,"stderr")},"wireProcLogging"),V=i(p=>K(p.proc,le(p.id),E=>p.tail.push(E)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const p=httpProxy.createProxyServer({});let E=Number(process.env.PORT)||0;E||(E=9090);const w=[],O=new Set;function W(){let e=0;for(;O.has(e);)e++;return O.add(e),e}u(W,"z"),i(W,"allocDebugSlot");let D=1,b=0,T=!1,R=_,A=[],ne=0;const F=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),J=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),de=i(e=>e.booting&&!J(e)&&Date.now()-e.spawnedAt<F,"stillBooting");function M(){return w.filter(e=>e.healthy&&typeof e.socketPath=="string")}u(M,"V"),i(M,"healthyChildren");let re;function G(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){re=r;for(const d of w)try{d.proc.send?.({type:"watcher",testimony:r})}catch{}}}u(G,"U"),i(G,"recordWatcherTestimony");function I(e,r=256*1024){return new Promise(d=>{let a="",c=!1;e.on("data",o=>{c||(a+=o,a.length>r&&(c=!0))}),e.on("end",()=>{if(c||!a)return d(void 0);try{d(JSON.parse(a))}catch{d(void 0)}}),e.on("error",()=>d(void 0))})}u(I,"v"),i(I,"readJsonBody");function U(){const e=M();if(e.length===0)return null;const r=b%e.length;return b=(b+1)%e.length,e[r]}u(U,"ee"),i(U,"pickChild");async function X(){const e=D++,r=childSockPath(e);cleanupSock(r);const d=["--enable-source-maps"];__filename.endsWith(".ts")&&d.unshift("--require","@swc-node/register");const a=v?W():-1,c=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...a>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(v,a))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:d,execPath:j("pod")}),o={id:e,proc:c,healthy:!1,lastError:null,debugSlot:a,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ne};return c.on("message",k=>{if(!k||typeof k!="object")return;const l=k;if(l.type==="ready"){if(o.socketPath=String(l.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),re)try{c.send?.({type:"watcher",testimony:re})}catch{}if(ne!==o.bootReloadSeq)try{c.send?.({type:"reload"})}catch{}}else l.type==="recycle-request"?L(o,String(l.reason||"it has outgrown its bound")):l.type==="inspector-url"?(o.inspectorUrl=String(l.url),console.log(`[parent] Child#${o.id} Debugger listening on ${l.url}`)):l.type==="reloaded"?(o.socketPath=String(l.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`)):l.type==="reload-error"?(o.lastError=String(l.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):l.type==="boot-error"&&(o.lastError=String(l.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1)}),c.on("exit",(k,l)=>{o.booting=!1,o.debugSlot>=0&&(O.delete(o.debugSlot),o.debugSlot=-1);const N=l==="SIGINT"||l==="SIGTERM"||k===0;if(N?console.log(`[parent] Child#${o.id} exited cleanly (code=${k}, signal=${l??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${k}, signal=${l??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,k,l,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${k}, signal=${l??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!N&&!T){const q=w.indexOf(o);q>=0&&w.splice(q,1);const ae=Date.now();if(A=A.filter(Q=>ae-Q<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(ae),setTimeout(()=>{const Q=w.filter($e=>$e.proc.killed===!1).length;!T&&Q<R&&(console.log(`[parent] respawning a crashed pod (${Q}/${R} live)`),X().catch(()=>{}))},1500).unref?.()}}),w.push(o),V(o),o}u(X,"te"),i(X,"spawnChild");async function t(e){R=Math.max(R,e);const r=w.filter(a=>a.proc.killed===!1),d=e-r.length;for(let a=0;a<d;a++)await X()}u(t,"r"),i(t,"ensurePoolSize");async function s(){const e=w.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!de(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const d=w.indexOf(r);d>=0&&w.splice(d,1),await X()}))}u(s,"c"),i(s,"respawnUnhealthy");const f=new Set;let y=!1,m=0;const C=3e4,S=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function L(e,r){const d=i(c=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${c}`);try{e.proc.send?.({type:"recycle-declined"})}catch{}},"decline");if(T||!w.includes(e))return;if(y)return d("another pod is already being replaced");const a=Date.now()-m;if(a<C)return d(`a pod was replaced ${Math.round(a/1e3)}s ago`);y=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const c=await X(),o=Date.now()+F;for(;!c.healthy&&!c.lastError&&!J(c)&&Date.now()<o;)await new Promise(N=>{setTimeout(N,100).unref?.()});if(T)return;if(!c.healthy){const N=w.indexOf(c);N>=0&&w.splice(N,1);try{c.proc.kill("SIGTERM")}catch{}return d(`its successor Child#${c.id} did not come up (${c.lastError??"no answer in time"})`)}const k=w.indexOf(e);k>=0&&w.splice(k,1),e.healthy=!1,f.add(e);const l=setTimeout(()=>{if(!J(e)){console.error(`[parent] Child#${e.id} was still running ${S}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},S);l.unref?.(),e.proc.once("exit",()=>{clearTimeout(l),f.delete(e)});try{e.proc.send?.({type:"retire"})}catch{try{e.proc.kill("SIGTERM")}catch{}}m=Date.now(),console.log(`[parent] Child#${c.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${c.proc.pid})`)}catch(c){d(`could not start a successor: ${c?.message??String(c)}`)}finally{y=!1}}u(L,"x"),i(L,"recyclePod");const H=[];function ye(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),d=path.join(r,`${e}.js`);if(!fs.existsSync(d))return console.error(`[parent] Worker "${e}" bundle not found: ${d}`),null;const a=["--enable-source-maps"];__filename.endsWith(".ts")&&a.unshift("--require","@swc-node/register");try{a.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const c=inspectorPortFor(v||9229,Z+Math.max(0,WORKER_NAMES.indexOf(e)));a.push(v?`--inspect=127.0.0.1:${c}`:`--inspect-port=127.0.0.1:${c}`);const o=(0,node_child_process_1.fork)(d,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:a,execPath:j(`w-${e}`)}),k={name:e,proc:o,debugPort:c};return H.push(k),K(o,`\x1B[35m[worker:${e}] \x1B[0m`,l=>{const N=/Debugger listening on (wss?:\/\/\S+)/.exec(l);N&&(k.inspectorUrl=N[1])}),o.on("exit",(l,N)=>{N==="SIGINT"||N==="SIGTERM"||l===0||console.error(`[parent] Worker "${e}" crashed (code=${l}, signal=${N??"none"})`);const q=H.indexOf(k);q>=0&&H.splice(q,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),k}u(ye,"we"),i(ye,"spawnWorkerProcess");const me=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),Se=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),se=new Set,Pe=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function pe(e,r){if(se.delete(e),!Pe(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}u(pe,"fe"),i(pe,"killDrainingWorker");function ke(e){try{e.proc.kill("SIGTERM")}catch{}if(Pe(e))return;const r=[...se].filter(a=>a.name===e.name);for(;r.length>=Se;)pe(r.shift(),`was still running when a later reload replaced its successor (cap ${Se})`);se.add(e);const d=setTimeout(()=>pe(e,`was still running ${me}ms after SIGTERM`),me);d.unref?.(),e.proc.once("exit",()=>{clearTimeout(d),se.delete(e)})}u(ke,"Ae"),i(ke,"retireWorker");function be(){for(const e of[...H])ke(e);H.length=0;for(const e of WORKER_NAMES)ye(e)}u(be,"Te"),i(be,"restartWorkers");const Ae=debounce(async()=>{ne+=1;const e=M();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)r.proc.send?.({type:"reload"})}await s(),WORKER_NAMES.length>0&&be()},200),ie=i(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),I(e).then(c=>G(c?.watcher)),Ae();return}if(e.method==="POST"&&e.url==="/webpack/watching"){const c=await I(e);G(c),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:re!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){let c="";e.on("data",o=>{c+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(c||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await t(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const c=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(c.get("tail")??"100")||0,0),te),k=c.get("follow")!=="0",l=c.get("plain")==="1",N=!isOwnerOnlyTransport(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const q=o===0?"":x.slice(-o).join(""),ae=l?q.replace(Y,""):q;if(r.write(N?redactInspectorUrls(ae):ae),!k){r.end();return}const Q={res:r,plain:l,redact:N};P.add(Q),e.on("close",()=>P.delete(Q));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!isOwnerOnlyTransport(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const c=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(c)&&c>1024&&(v=c),v||(v=9229);const o=w.filter(l=>l.proc.exitCode===null&&l.proc.signalCode===null);for(const l of o){l.debugSlot<0&&(l.debugSlot=W());try{l.proc.send?.({type:"debug-open",port:inspectorPortFor(v,l.debugSlot)})}catch{}}for(const l of H)try{l.proc.kill("SIGUSR1")}catch{}const k=Date.now()+2e3;for(;Date.now()<k&&o.some(l=>!l.inspectorUrl);)await new Promise(l=>setTimeout(l,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:v,fixedPorts:usesFixedInspectorPorts(),pods:o.map(l=>({id:l.id,inspectorUrl:l.inspectorUrl??null})),workers:H.map(l=>({name:l.name,port:l.debugPort||inspectorPortOf(l.inspectorUrl),inspectorUrl:l.inspectorUrl??null}))},null,2)+`
|
|
16
|
+
`);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,r,{target:d.target,autoRewrite:typeof d.target=="string",changeOrigin:!0},c=>{console.error(`[parent] proxy error to ${d.target}:`,c),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from ${d.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){r.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),r.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const a=U();if(!a){r.statusCode=503,r.setHeader("content-type","text/plain; charset=utf-8");const c=w.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
|
|
17
17
|
`);r.end(`No healthy children available. POST /webpack/reload to recover.
|
|
18
18
|
|
|
19
19
|
Last errors:
|
|
20
|
-
${c}`);return}
|
|
21
|
-
${a.lastError}`)})},"requestHandler");let z,ce,we;const ue=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),r=GATEWAY_ROUTES.map(a=>a.host).filter(a=>!!a),
|
|
20
|
+
${c}`);return}p.web(e,r,{target:{socketPath:a.socketPath},headers:{"x-proxy-child-id":String(a.id)}},c=>{console.error(`[parent] proxy error to Child#${a.id}:`,c),a.healthy=!1,a.lastError=String(c?.stack||c),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from Child#${a.id}. Try /webpack/reload.
|
|
21
|
+
${a.lastError}`)})},"requestHandler");let z,ce,we;const ue=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),r=GATEWAY_ROUTES.map(a=>a.host).filter(a=>!!a),d=path.join(process.cwd(),"node_modules",".cache","davnx-webpack");ce=e.getCertificateSync(d,SERVICE_NAME||"lensmcp.dev",r),we=e.caCertPath(),z=require("node:https").createServer({key:ce,cert:ce},ie)}else z=http.createServer(ie);const Ne=i((e,r,d)=>{const a=matchGatewayRoute(e);if(a?.prependPrefix&&!e.url?.startsWith(a.prependPrefix)&&(e.url=a.prependPrefix+(e.url??"/")),a?.target){p.ws(e,r,d,{target:a.target,changeOrigin:!0},()=>r.destroy());return}const c=U();if(!c){r.destroy();return}p.ws(e,r,d,{target:{socketPath:c.socketPath}},()=>r.destroy())},"upgradeHandler");z.on("upgrade",Ne);const Te=2e3;let ve=!1;z.on("error",e=>{if(e.code==="EADDRINUSE"){ve||(ve=!0,console.warn(`[parent] public port ${E} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${Te/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!T&&!z.listening&&z.listen(E)},Te).unref?.();return}console.error("[parent] public server error:",e)}),await t(_);for(const e of WORKER_NAMES)ye(e);z.listen(E,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${E}`),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(r=>`${r.host??"*"}${r.prefix??"/"}\u2192${r.target??"children"}`).join(" ")}`),we&&(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 ${we}`)),console.log(`POST ${e}://localhost:${E}/webpack/reload to trigger rolling swap/respawn`)});const he=Number(process.env.INTERNAL_PORT||0);if(he>0){const e=i((d,a)=>{const c=U();if(!c){a.statusCode=503,a.setHeader("content-type","text/plain; charset=utf-8"),a.end("No healthy children available.");return}p.web(d,a,{target:{socketPath:c.socketPath}},o=>{c.healthy=!1,c.lastError=String(o?.stack||o),a.statusCode=502,a.end(`Upstream error from Child#${c.id}.`)})},"internalHandler"),r=http.createServer(e);r.on("upgrade",(d,a,c)=>{const o=U();if(!o){a.destroy();return}p.ws(d,a,c,{target:{socketPath:o.socketPath}},()=>a.destroy())}),r.on("error",d=>{console.warn(`[parent] internal port ${he} unavailable (${d.code}).`)}),r.listen(he,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${he}`)}),ue.push(r)}const Ie=(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of Ie){if(!Number.isInteger(e)||e===Number(E))continue;const r=GATEWAY_HTTPS?require("node:https").createServer({key:ce,cert:ce},ie):http.createServer(ie);r.on("upgrade",Ne),r.on("error",d=>{console.warn(`[parent] extra port ${e} unavailable (${d.code}) \u2014 main port still up.`)}),r.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),ue.push(r)}const _e=path.join(SOCK_DIR,"parent.sock");claimSock(_e);const Ee=http.createServer(ie);Ee.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),Ee.listen(_e),ue.push(Ee);const Re=i(async e=>{T=!0,console.log(`[${e}] [parent] shutting down\u2026`),cleanupSock(_e);for(const r of ue)try{r.close()}catch{}for(const r of H)try{r.proc.kill("SIGTERM")}catch{}for(const r of[...se])pe(r,"was still draining when its devserver shut down");for(const r of f)try{r.proc.kill("SIGTERM")}catch{}for(const r of w){try{r.proc.kill("SIGTERM")}catch{}r.socketPath&&cleanupSock(r.socketPath,r.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,()=>{Re(e)}));const fe=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(fe)&&fe>0){const e=setInterval(()=>{try{process.kill(fe,0)}catch(r){if(r.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${fe}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),Re("gateway-gone")}},5e3);e.unref()}})()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.9",
|
|
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.
|
|
61
|
-
"@lensmcp/node-instrumentation": "1.23.
|
|
62
|
-
"@lensmcp/nx-plugin": "1.23.
|
|
60
|
+
"@lensmcp/core": "1.23.9",
|
|
61
|
+
"@lensmcp/node-instrumentation": "1.23.9",
|
|
62
|
+
"@lensmcp/nx-plugin": "1.23.9",
|
|
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",
|
package/stdio-guard.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pod child's stdout and stderr are PIPES to its devserver parent, and the parent can go first
|
|
3
|
+
* (issues/foodguard/25). A write to a pipe nobody reads fails with EPIPE, which Node delivers as an
|
|
4
|
+
* `'error'` EVENT on the stream; with no listener that is an uncaught exception. The devserver's
|
|
5
|
+
* catch-all then printed `[child] uncaughtException (uncaughtException): Error: write EPIPE`, and the
|
|
6
|
+
* stdout tap filed it — stack and all — as a server-side error. So a `lensmcp setup` or a gateway
|
|
7
|
+
* restart that caught a pod mid-boot left the lens reporting a failing workspace over a pod that had
|
|
8
|
+
* only been told to stop.
|
|
9
|
+
*
|
|
10
|
+
* `console.*` already ignores stream errors. What does not is a DIRECT `process.stdout.write`, which
|
|
11
|
+
* is how Nest's `ConsoleLogger` prints — so the guard sits on the streams themselves.
|
|
12
|
+
*
|
|
13
|
+
* EPIPE only. It means exactly "the reader is gone", which for a pod is its own teardown: the IPC
|
|
14
|
+
* `disconnect` guard or the parent's SIGTERM ends the process either way, and there is nobody left to
|
|
15
|
+
* tell. Any other stream error is re-thrown, so it reaches the handler it reached before.
|
|
16
|
+
*/
|
|
17
|
+
import type { EventEmitter } from 'node:events';
|
|
18
|
+
/** Swallow EPIPE on this process's stdout/stderr. Idempotent — a stream is guarded once. */
|
|
19
|
+
export declare function guardBrokenStdio(streams?: readonly EventEmitter[]): void;
|
package/stdio-guard.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var c=Object.defineProperty;var r=(o,e)=>c(o,"name",{value:e,configurable:!0});var d=Object.defineProperty,s=r((o,e)=>d(o,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.guardBrokenStdio=guardBrokenStdio;function guardBrokenStdio(o=[process.stdout,process.stderr]){for(const e of o){const t=e;t.__lensmcpStdioGuard||(t.__lensmcpStdioGuard=!0,e.on("error",n=>{if(n?.code!=="EPIPE")throw n}))}}r(guardBrokenStdio,"guardBrokenStdio"),s(guardBrokenStdio,"guardBrokenStdio");
|
package/tsgo-check-plugin.d.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
|
|
12
12
|
* "service error" signal — the same wire the fork-ts-checker fed. Advisory like
|
|
13
13
|
* `typeCheckOptions.async: true`: a type error never fails or blocks the webpack build.
|
|
14
|
+
* The CLEAN line is the other half of that contract: `[tsgo] <app>: types clean` is the only
|
|
15
|
+
* thing that takes the signal back (webpack's `compiled successfully` is about the bundle), so
|
|
16
|
+
* the gateway matches it too (`pod-output.ts`).
|
|
14
17
|
*
|
|
15
18
|
* SHIM tsconfig: tsgo is TS7 — `moduleResolution: node10` (what node-app tsconfigs often pin) is
|
|
16
19
|
* REMOVED there, so the check runs against a generated shim extending the app tsconfig with
|