@lensmcp/cluster 1.23.11 → 1.23.13
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/create-webpack-dev.d.ts +3 -1
- package/create-webpack-dev.js +1 -1
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/lifecycle.d.ts +25 -0
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/registration-guards.d.ts +32 -0
- package/executors/gateway/runtime/registration-guards.js +1 -0
- package/main.devserver.d.ts +18 -0
- package/main.devserver.js +9 -9
- package/package.json +4 -4
- package/tsgo-check-plugin.d.ts +47 -2
- package/tsgo-check-plugin.js +5 -5
- package/typecheck-queue.d.ts +36 -0
- package/typecheck-queue.js +1 -0
package/create-webpack-dev.d.ts
CHANGED
|
@@ -37,10 +37,12 @@ export declare class DevServerReloadPlugin {
|
|
|
37
37
|
private secure;
|
|
38
38
|
/** The devserver parent's CONTROL socket — preferred over the TCP port (see `post`). */
|
|
39
39
|
private controlSock;
|
|
40
|
+
/** Run by a gateway: the control socket is the ONLY way to the parent (see `post`). */
|
|
41
|
+
private readonly managed;
|
|
40
42
|
/** Bounded, newest-last paths of the last few `watchRun`s — diagnostics for the register, not policy. */
|
|
41
43
|
private static readonly RECENT_PATH_CAP;
|
|
42
44
|
private testimony;
|
|
43
|
-
constructor(port: number, secure?: boolean, controlSock?: string);
|
|
45
|
+
constructor(port: number, secure?: boolean, controlSock?: string, managed?: boolean);
|
|
44
46
|
/**
|
|
45
47
|
* Deliver to the parent's CONTROL SOCKET first (`$TMPDIR[/<wsKey>]/<service>-devserver/parent.sock`), then
|
|
46
48
|
* the TCP port. The socket path is bind-STOLEN by whichever parent generation started last, so during a
|
package/create-webpack-dev.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var R=Object.defineProperty;var d=(r,t)=>R(r,"name",{value:t,configurable:!0});var T=Object.defineProperty,u=d((r,t)=>T(r,"name",{value:t,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DevServerReloadPlugin=void 0,exports.devserverControlSock=devserverControlSock,exports.createDevWebpackConfig=createDevWebpackConfig;const tslib_1=require("tslib"),app_plugin_1=require("@nx/webpack/app-plugin"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),webpack_node_externals_1=tslib_1.__importDefault(require("webpack-node-externals")),build_scope_patterns_1=require("./build-scope-patterns"),tsgo_check_plugin_1=require("./tsgo-check-plugin"),fork_ts_check_reporter_1=require("./fork-ts-check-reporter"),webpack=require("webpack");class DevServerReloadPlugin{static{d(this,"DevServerReloadPlugin")}static{u(this,"DevServerReloadPlugin")}static{this.RECENT_PATH_CAP=32}constructor(t,o=!1,a,s=Number(process.env.LENSMCP_GATEWAY_PID??0)>0){this.testimony={schemaVersion:1,lastWatchRunAt:0,lastBuildAt:0,buildsSinceSpawn:0,degraded:!1,recentPaths:[]},this.managed=s,this.secure=o,this.base=`${o?"https":"http"}://localhost:${t}`,this.controlSock=a}post(t,o){const a=JSON.stringify(o),s=u(()=>new Promise((i,c)=>{const p=(this.secure?require("node:https"):require("node:http")).request(`${this.base}${t}`,{method:"POST",headers:{"content-type":"application/json"},rejectUnauthorized:!1},l=>{l.resume(),l.on("end",i)});p.on("error",c),p.end(a)}),"viaTcp"),n=this.controlSock;return!n||!fs.existsSync(n)?this.managed?Promise.reject(new Error("the devserver control socket is not up yet")):s():new Promise((i,c)=>{const p=require("node:http").request({socketPath:n,path:t,method:"POST",headers:{"content-type":"application/json"}},l=>{l.resume(),l.on("end",i)});p.on("error",c),p.end(a)}).catch(i=>{if(this.managed)throw i;return s()})}snapshot(){return{...this.testimony,recentPaths:[...this.testimony.recentPaths]}}stampWatchRun(t){if(this.testimony.lastWatchRunAt=Date.now(),t.length){const o=[...this.testimony.recentPaths,...t];this.testimony.recentPaths=o.slice(-DevServerReloadPlugin.RECENT_PATH_CAP)}}degrade(t){this.testimony.degraded=t}tellWatching(){this.post("/webpack/watching",this.testimony).catch(()=>{})}apply(t){const o="DevServerReloadPlugin";let a=!1;t.hooks.watchRun?.tap(o,s=>{const n=[...s?.modifiedFiles??[],...s?.removedFiles??[]].filter(i=>typeof i=="string");if(this.stampWatchRun(n),!a){const i=s?.watchFileSystem?.watcher;i&&typeof i.on=="function"&&(a=!0,i.on("error",c=>{this.degrade(`watcher error: ${c?.message??String(c)}`),this.tellWatching()}))}this.tellWatching()}),t.hooks.watchClose?.tap(o,()=>{this.degrade("watch closed"),this.tellWatching()}),t.hooks.failed?.tap(o,s=>{this.degrade(`compiler failed: ${s?.message??String(s)}`),this.tellWatching()}),t.hooks.done.tap(o,async s=>{try{if(s.hasErrors()){this.tellWatching();return}this.testimony.lastBuildAt=Date.now(),this.testimony.buildsSinceSpawn+=1,await this.post("/webpack/reload",{watcher:this.testimony}),console.log("[DevServerReloadPlugin] notified devserver to reload")}catch{console.log("[DevServerReloadPlugin] devserver not running yet")}})}}exports.DevServerReloadPlugin=DevServerReloadPlugin;function devserverControlSock(r,t=process.env){const o=t.LENSMCP_WS_KEY||"",a=o?path.join(os.tmpdir(),o,`${r}-devserver`):path.join(os.tmpdir(),`${r}-devserver`);return path.join(a,"parent.sock")}d(devserverControlSock,"devserverControlSock"),u(devserverControlSock,"devserverControlSock");function createDevWebpackConfig(r){const{appRoot:t,outputDir:o,main:a,tsConfig:s,workspaceRoot:n,assets:i=[],port:c,memoryLimit:p=8192,buildLibsFromSource:l=!0,orgScopes:w=[],bundlePackages:b=[],additionalEntryPoints:y=[],nodeExternalsConfig:m,webpackConfigPath:v}=r,{allowlistPatterns:C,scopePrefixes:E,scopePatterns:N}=(0,build_scope_patterns_1.buildScopePatterns)(w),P=process.env.LENSMCP_TYPECHECK,f=P==="tsgo"?(0,tsgo_check_plugin_1.resolveTsgoBin)(n,process.env.LENSMCP_TSGO_BIN):null;if(P==="tsgo"&&!f){const e=`${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only \u2014 so typecheck:// has no producer for this project until it is fixed`;console.warn(`[serve] ${r.appName}: LENSMCP_TYPECHECK=tsgo but ${e}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:r.appName,tsConfig:s,workspaceRoot:n,detail:e})}const _=!!f,S=b.map(e=>new RegExp(`^${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(/|$)`)),W=[/webpack\/hot\/poll\?100/,...C,...S,...m?.allowlist||[]],k={output:{path:o,...process.env.NODE_ENV!=="production"&&{devtoolModuleFilenameTemplate:"[absolute-resource-path]"},clean:!0},externals:[(0,webpack_node_externals_1.default)({allowlist:W,additionalModuleDirs:m?.additionalModuleDirs||[],...m?.importType&&{importType:m.importType}}),({request:e},g)=>{if(e&&path.isAbsolute(e)&&e.startsWith(n)&&!e.includes("node_modules")||e&&(e.startsWith(path.join(n,"apps"))||e.startsWith(path.join(n,"libs")))||e&&E.some(h=>e.startsWith(h))||e&&N.some(h=>h.test(e))||e&&S.some(h=>h.test(e)))return g();if(e&&!(e.startsWith("./")||e.startsWith(".."))){const h=e.startsWith("@")?e.split("/").slice(0,2).join("/"):e.split("/")[0];return fs.existsSync(path.join(n,"node_modules",h))?g(null,`commonjs ${e}`):g()}return g()}],mode:"development",devtool:"eval-cheap-module-source-map",plugins:[new webpack.HotModuleReplacementPlugin,new app_plugin_1.NxAppWebpackPlugin({target:"node22",compiler:"tsc",main:a,additionalEntryPoints:y,verbose:!0,sourceMap:"eval-cheap-module-source-map",mergeExternals:!0,externalDependencies:[],memoryLimit:p,tsConfig:s,assets:i,optimization:!1,progress:!1,outputHashing:"none",generatePackageJson:!1,watchDependencies:!0,typeCheckOptions:process.env.LENSMCP_TYPECHECK==="off"||_?!1:{async:!0},buildLibsFromSource:l}),new DevServerReloadPlugin(c,r.httpsReload===!0,devserverControlSock(r.serviceName)),..._?[new tsgo_check_plugin_1.TsgoCheckPlugin({appName:r.appName,tsConfig:s,workspaceRoot:n,...process.env.LENSMCP_TSGO_BIN?{binPath:process.env.LENSMCP_TSGO_BIN}:{}})]:process.env.LENSMCP_TYPECHECK==="off"?[]:[new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:r.appName,tsConfig:s,workspaceRoot:n})]],snapshot:{managedPaths:[/^(.+?[\\/])?node_modules[\\/]/]},watch:!0,watchOptions:{ignored:["**/*.env.template","**/config.template.json","**/*.md","**/dist/**","**/migrations/**"]},cache:{type:"filesystem"}};if(v){const e=require(path.resolve(t,v));return(e.default||e)(k)}return k}d(createDevWebpackConfig,"createDevWebpackConfig"),u(createDevWebpackConfig,"createDevWebpackConfig");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}catch{}return e.registry.register(
|
|
1
|
+
"use strict";var m=Object.defineProperty;var d=(t,e)=>m(t,"name",{value:e,configurable:!0});var v=Object.defineProperty,y=d((t,e)=>v(t,"name",{value:e,configurable:!0}),"y");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.controlSocketPath=controlSocketPath,exports.versionAtLoad=versionAtLoad,exports.readClusterVersion=readClusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),registration_guards_1=require("./registration-guards"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log");function controlSocketPath(){const t=process.env.LENSMCP_HOME||os.homedir();return path.join(t,".lensmcp","control.sock")}d(controlSocketPath,"controlSocketPath"),y(controlSocketPath,"controlSocketPath"),exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(t){const e=t();return()=>e}d(versionAtLoad,"versionAtLoad"),y(versionAtLoad,"versionAtLoad");function readClusterVersion(t){let e=t;for(let n=0;n<6;n++){const u=path.join(e,"package.json");if(fs.existsSync(u))try{const g=JSON.parse(fs.readFileSync(u,"utf8"));if(g.name==="@lensmcp/cluster"&&typeof g.version=="string")return g.version}catch{}const s=path.dirname(e);if(s===e)break;e=s}}d(readClusterVersion,"readClusterVersion"),y(readClusterVersion,"readClusterVersion");function readJsonBody(t){return new Promise((e,n)=>{let u="";t.on("data",s=>{u+=s,u.length>1e6&&n(new Error("body too large"))}),t.on("end",()=>{try{e(u?JSON.parse(u):{})}catch(s){n(s)}}),t.on("error",n)})}d(readJsonBody,"readJsonBody"),y(readJsonBody,"readJsonBody");function ensureOwnerOnlyDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448})}catch{}try{const e=fs.lstatSync(t);return e.isDirectory()?((e.mode&63)!==0&&fs.chmodSync(t,448),!0):!1}catch{return!1}}d(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),y(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(t){const{rt:e}=t,n=controlSocketPath(),u=path.dirname(n);ensureOwnerOnlyDir(u)||console.warn(`[gateway] control home ${u} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(n)}catch{}const s=y((i,c,l)=>{i.writeHead(c,{"content-type":"application/json"}),i.end(JSON.stringify(l))},"send"),g=http.createServer((i,c)=>{(async()=>{try{const l=(i.url??"").split("?")[0];if(i.method==="GET"&&l==="/list")return s(c,200,{workspaces:e.registry.keys().map(r=>{const o=e.registry.get(r);return{key:r,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0}})});if(i.method==="GET"&&l==="/status"){const r=(0,exports.clusterVersion)();return s(c,200,{daemon:{pid:process.pid,wsKey:e.wsKey,...r?{version:r}:{}},workspaces:e.registry.keys().map(o=>{const a=e.registry.get(o);return{key:o,root:a?.root,routes:a?.routes.length??0,services:a?.services.length??0,isDaemon:o===e.wsKey}}),services:e.registry.allServices().map(o=>({project:o.project,wsKey:o.wsKey,host:o.decl.host,state:o.state}))})}if(i.method==="POST"&&l==="/register"){const r=await readJsonBody(i);if(!r.wsKey||!r.root)return s(c,400,{error:"wsKey and root are required"});const o=r.projects??{},a=(0,discovery_1.projectSourceRoots)(r.root,o,r.projectDependencies),{routes:p,services:f}=(0,discovery_1.discoverRoutes)(r.root,o,void 0,r.wsKey,a),w={wsKey:r.wsKey,root:r.root,routes:p,services:f},k=(0,registration_guards_1.refuseRegistration)(e.registry,w);if(k)return console.warn(`[gateway] refused to register '${r.wsKey}' from ${r.root}: ${k.error}`),s(c,409,k);try{const h=(0,service_keys_1.adoptServiceKeys)(e,r.root,f,r.wsKey);h.added.length>0&&console.log(`[gateway] adopted east-west keys for ${r.wsKey}: ${h.added.join(", ")}`)}catch(h){console.error(`[gateway] could not adopt east-west keys for ${r.wsKey}: ${h.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(r.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${r.wsKey}') is served by the daemon at ${e.root} (pid ${process.pid}); its gateway-level log is ${path.join(e.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
|
|
2
|
+
`)}catch{}return e.registry.register(w),await t.onRegister?.(w),e.rebuildRoutes(),s(c,200,{ok:!0,wsKey:r.wsKey,routes:p.length,services:f.length,workspaces:e.registry.keys()})}if(i.method==="POST"&&l==="/unregister"){const r=await readJsonBody(i);if(!r.wsKey)return s(c,400,{error:"wsKey is required"});const o=(0,registration_guards_1.refuseUnregistration)(e.registry,{wsKey:r.wsKey,...r.root?{root:r.root}:{}},e.wsKey);if(o)return console.warn(`[gateway] refused to unregister '${r.wsKey}'${r.root?` for ${r.root}`:""}: ${o.error}`),s(c,409,o);const a=e.registry.unregister(r.wsKey);if(a){for(const p of a.services){const f=(0,service_keys_1.serviceKeyId)(p.wsKey??r.wsKey,p.project),w=e.serviceKeys[f];w&&(delete e.serviceKeys[f],e.keyToProject.delete(w),e.keyToWorkspace?.delete(w))}for(const p of a.services)t.killService(p,"workspace unregistered");t.onUnregister?.(a)}return e.rebuildRoutes(),s(c,200,{ok:!0,removed:!!a,workspaces:e.registry.keys()})}s(c,404,{error:"not found"})}catch(l){s(c,500,{error:l.message})}})()});return g.listen(n,()=>{try{fs.chmodSync(n,384)}catch(i){console.warn(`[gateway] could not restrict ${n} to 0600:`,i.message)}}),{socketPath:n,close:y(()=>{g.close();try{fs.unlinkSync(n)}catch{}},"close")}}d(startControlServer,"startControlServer"),y(startControlServer,"startControlServer");
|
|
@@ -16,6 +16,31 @@ import type { Observability } from './observability';
|
|
|
16
16
|
* actually resolve, instead of the whole monorepo — see that function for WHY. Omitted ⇒ the whole root
|
|
17
17
|
* (the original workspace-wide behavior, still used when a child's project root is unknown). Root-level
|
|
18
18
|
* loose files are always counted, so a `package.json`/`tsconfig.base.json` add reaches every scope. */
|
|
19
|
+
/**
|
|
20
|
+
* Where the gateway sends "grow the pool" for a service: its OWN devserver's control socket.
|
|
21
|
+
*
|
|
22
|
+
* It used to post to `localhost:<cluster.port>`. That port is the same in every checkout of a repo,
|
|
23
|
+
* so with two of them up it reached whichever held it — and under a gateway the devserver no
|
|
24
|
+
* longer takes pool commands on the port at all (issues/foodguard/35). The socket sits in this
|
|
25
|
+
* service's own pool dir, so it cannot be anyone else's. The URL remains for a service that
|
|
26
|
+
* declares an `adminUrl`, and for a devserver that has no control socket.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scaleTarget(svc: Pick<ServiceCtl, 'pool' | 'decl'>, exists?: (p: string) => boolean): {
|
|
29
|
+
socketPath: string;
|
|
30
|
+
} | {
|
|
31
|
+
url: string;
|
|
32
|
+
} | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Is this nx runner command line one of OURS — a runner started from `root`?
|
|
35
|
+
*
|
|
36
|
+
* It used to be `cmd.includes(root)`. A checkout at `<root>-gate1`, or a worktree nested at
|
|
37
|
+
* `<root>/.claude/worktrees/x`, CONTAINS the main root as text — so every time the main workspace
|
|
38
|
+
* respawned a service it SIGKILLed the same-named runner of the other checkout
|
|
39
|
+
* (issues/foodguard/35). The runner's own nx entry is the identity: `<root>/node_modules/nx/…`.
|
|
40
|
+
* A runner with no nx path in its argv (the yarn-shim fallback) cannot be told apart by path and is
|
|
41
|
+
* bounded by the target match alone, as before.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isOwnRunnerCommand(cmd: string, root: string): boolean;
|
|
19
44
|
export interface SourceSetSnapshot {
|
|
20
45
|
signature: string;
|
|
21
46
|
paths: Set<string>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var ge=Object.defineProperty;var
|
|
2
|
-
`)){const u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const
|
|
3
|
-
`)){const p=(0,pod_output_1.classifyPodLine)(N),{clean:j,broken:k,healthy:M}=p,z=j;if(k){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const T=Date.now();(j!==e.lastErrorEmitSig||T-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=T,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j,fault:p.fault}))}M&&(e.buildFailed=!1);const I=(e.faults??=new pod_output_1.PodFaults).observe(p);if(I){e.lastErrorEmitSig=void 0;const T=I.open.length===0;h(e,"info",`${T?"service recovered":`${I.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:T?"service-recovered":"service-fault-closed",project:e.project,line:j,closed:I.closed,open:I.open})}!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||(W(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),D(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.())},250).unref?.())}},"sniff"),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;R(w);const _=`[${e.project}] ${w}`;process.stdout.write(_),A?.(_)}),m.stderr?.on("data",w=>{if(t.stopped())return;R(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 N=(0,exit_reason_1.describeExit)(w,_);if(console.log(`[gateway] ${r} exited (${w??"no code"}${_?`, ${_}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${N?` \u2014 ${N}`:""}`),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 p=Date.now();if(e.restarts=(e.restarts??[]).filter(k=>p-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(p);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}),x(e))},j)}),m},"launchServiceProcess"),x=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&D(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),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"),V=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),G=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,P(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return 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(C(e.pool)>0){const r=await G(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&&W(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&x(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),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"),W=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 u=X(e,c,"standby"),$=(async()=>{const L=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],m=d+y;let R;for(;!t.stopped()&&Date.now()<m&&!(u.exitCode!==null||u.signalCode!==null);){C(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,L))?.podGeneration===c){R=A;break}if(R)break;await sleep(E)}if(!R||t.stopped()){const A=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}: ${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(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(()=>{J(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(R)} in ${Date.now()-d}ms; retiring g${r}.`),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}),W(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}),W(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let Q;const B=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 u=sourceScopeDirs(t.root,d,s,o,r);return u.length>0?u: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(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),$=Q?diffSourceSets(Q,c,d,u):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 L;try{L=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(p=>p.isDirectory()).map(p=>p.name)}catch{return}const m=ie(),R=new Set(t.services.map(p=>p.project));for(const p of t.routes)p.lens&&R.add(p.lens.project);const A=new Map;for(const p of R){const j=ae(p,L,m,s);if(!j)continue;const k=j.join("|"),M=A.get(k)??{dirs:j,projects:[]};M.projects.push(p),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 N=i((p,j,k)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of p)t.sourceSetChangedFor.set(M,j),t.sourceSetChangesFor.set(M,k)},"stampScoped");for(const[p,{dirs:j,projects:k}]of A){const M=await sourceSetSnapshot(t.root,j,s,r),z=B.get(p);B.set(p,M);const I=new Map;for(const K of k){const F=oe.get(K);if(F===void 0||F===p)continue;const q=I.get(F)??[];q.push(K),I.set(F,q)}for(const K of k)oe.set(K,p);for(const[K,F]of I){const q=B.get(K),U=q?diffSourceSets(q,M,w,_):$;U&&(N(F,q?w:d,U),g("info",`source file set changed \u2014 ${F.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:F,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!z)continue;const T=diffSourceSets(z,M,w,_);T&&(N(k,w,T),g("info",`source file set changed in ${k.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:k,added:T.added,removed:T.removed,truncated:T.truncated}))}for(const p of[...B.keys()])A.has(p)||B.delete(p)})().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;W(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),he=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){W(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=C(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&b(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await V(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:C,spawnService:x,ensureUp:re,scaleUp:se,killSpawned:W,markUp:b,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 w=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=w((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isOwnRunnerCommand=isOwnRunnerCommand,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}w(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}w(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}}w(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,n=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const a=path.join(t.pool.dir,"parent.sock");return n(a)?{socketPath:a}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}w(scaleTarget,"scaleTarget"),i(scaleTarget,"scaleTarget");function isOwnRunnerCommand(t,n){return t.includes("/node_modules/nx/")?t.includes(`${n.replace(/\/+$/,"")}/node_modules/nx/`):!0}w(isOwnRunnerCommand,"isOwnRunnerCommand"),i(isOwnRunnerCommand,"isOwnRunnerCommand");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}w(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const f=l?.ignoreTests??!0;let v=0,y=0,_=!1;const g=new Set,I=await gitIgnoredPaths(t),h=i(x=>path.relative(t,x).split(path.sep).join("/"),"rel"),C=i(async(x,X=!0)=>{if(v>=SCAN_FILE_CAP){_=!0;return}let D;try{D=await fs.promises.readdir(x,{withFileTypes:!0})}catch{return}for(const V of D){if(v>=SCAN_FILE_CAP){_=!0;return}const P=V.name,G=path.join(x,P);if(V.isDirectory()){if(!X||P.startsWith(".")||SRC_EXCLUDE.has(P)||a?.has(P)||P.endsWith("-devserver")||I.has(`${h(G)}/`))continue;await C(G)}else if(V.isFile()){const Y=P.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(P.slice(Y)))continue;const b=h(G);if(I.has(b)||f&&isTestSourcePath(b)||g.has(b))continue;g.add(b),v+=1,y^=fnv1a(b)}}},"walk");if(n&&n.length>0){await C(t,!1);for(const x of n)await C(x)}else await C(t);return{signature:`${v}:${(y>>>0).toString(36)}`,paths:g,truncated:_}}w(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}w(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let _=!0;for(const g of t.paths)if(!n.paths.has(g)){_=!1;break}if(_)return null}const f=[],v=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?v.push(_):y=!0);return{changedAt:a,added:f,removed:v,truncated:y,...l!==void 0?{windowMs:l}:{}}}w(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}w(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const v=i(g=>{const I=path.relative(t,g).split(path.sep).filter(Boolean);return I.length===0||I[0].startsWith("..")?"":I[0]},"bucketOf")(n);if(!v)return[];const y=new Set(a.filter(Boolean)),_=new Set([v]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||_.add(g);return[..._].sort().map(g=>path.join(t,g))}w(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}w(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)}w(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,v=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return l.every(y=>v.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}w(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"}w(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"}w(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}w(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,v=a.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:g}=n,I=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,r,s,o,c)=>g(r,s,o,{wsKey:e.wsKey,...c},void 0,I(e)),"emitSvc"),C=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),x=i((e,r)=>{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 o=e.proc?.pid;let c=0;for(const d of s.split(`
|
|
2
|
+
`)){const u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const m=Number(u[1]),L=Number(u[2]),k=u[3];if(k.includes(`run ${r}`)&&/\bnx(\.js)?\b/.test(k)&&isOwnRunnerCommand(k,e.root)&&!(m===process.pid||m===o)&&!(o!==void 0&&L===o)){try{process.kill(-L,"SIGKILL")}catch{try{process.kill(m,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${r}: 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,r,s)=>{const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),u=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[m,L]=fs.existsSync(d)?[u,[d,"run",o,`--childCount=${c}`]]:["yarn",["nx","run",o,`--childCount=${c}`]],k=l(m,L,{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(r),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const R=i(S=>{const E=S.toString();for(const N of E.split(`
|
|
3
|
+
`)){const p=(0,pod_output_1.classifyPodLine)(N),{clean:j,broken:$,healthy:T}=p,z=j;if($){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(j!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=M,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j,fault:p.fault}))}T&&(e.buildFailed=!1);const O=(e.faults??=new pod_output_1.PodFaults).observe(p);if(O){e.lastErrorEmitSig=void 0;const M=O.open.length===0;h(e,"info",`${M?"service recovered":`${O.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:M?"service-recovered":"service-fault-closed",project:e.project,line:j,closed:O.closed,open:O.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: 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||(W(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),x(e,o),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 k.stdout?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stdout.write(E),A?.(E)}),k.stderr?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stderr.write(E),A?.(E)}),k.on("exit",(S,E)=>{if(e.proc!==k||(e.proc=void 0,e.state="down",t.stopped()))return;const N=(0,exit_reason_1.describeExit)(S,E);if(console.log(`[gateway] ${o} exited (${S??"no code"}${E?`, ${E}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${N?` \u2014 ${N}`:""}`),h(e,S===0?"info":"warning",`service down: ${e.project} (exit ${S})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:S,...E?{signal:E}:{}}),S===0||e.buildFailed)return;const p=Date.now();if(e.restarts=(e.restarts??[]).filter($=>p-$<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 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)}),k},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&x(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (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"),V=i(async e=>{const r=e.pool.socks[0];if(!r)return;const s=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),G=i(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const s=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),o=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,r);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&o.push({sock:c,pid:d.pid})}return o},"staleAdoptedSocks"),Y=i((e,r)=>{const s=r.map(o=>o.sock);for(const{sock:o,pid:c}of r){const d=chooseStalePodKillTarget(c,P(o),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(o=>!s.includes(o)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,r)=>{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, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:r==="adopted"}),!0},"markUp"),re=i(async e=>{if(C(e.pool)>0){const o=await G(e);if(o.length>0&&(console.error(`[gateway] ${e.project}: ${o.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:o.length}),e.proc&&W(e,"stale adoption (foreign east-west key)"),Y(e,o)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const r=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const o=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${o?`, cold start ${o}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${o??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:o}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const r=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,o=scaleTarget(e);if(!o||s===0||s>=r||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();let c;"socketPath"in o?c=http.request({socketPath:o.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},d=>d.resume()):c=(o.url.startsWith("https")?require("node:https"):http).request(`${o.url}/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"),W=i((e,r,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const o=e.proc;try{o.stdout?.removeAllListeners("data"),o.stderr?.removeAllListeners("data"),o.removeAllListeners("exit")}catch{}v(o,"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 r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const s=path.join(e.pool.dir,r);try{const o=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(o)&&o>0)try{process.kill(o,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),te=i((e,r)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const o=e.generation??0,c=o+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${o}\u2192g${c} reason=${r} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:r,generation:c,fromGeneration:o});const u=X(e,c,"standby"),m=(async()=>{const L=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],k=d+y;let R;for(;!t.stopped()&&Date.now()<k&&!(u.exitCode!==null||u.signalCode!==null);){C(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,L))?.podGeneration===c){R=A;break}if(R)break;await sleep(_)}if(!R||t.stopped()){const A=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 v(u,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${A} \u2014 old generation g${o} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r,generation:c,error:A,durationMs:Date.now()-d}),setTimeout(()=>J(e),1200).unref?.(),!1}if(e.proc!==s)return v(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 v(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(R)} in ${Date.now()-d}ms; retiring g${o}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:r,generation:c,fromGeneration:o,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=m,m},"rollPod"),ne=i(e=>{const r=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>r-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}),W(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(r),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}),W(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 B=new Map,oe=new Map;let Z=!1,ee=0,H=0;const ie=i(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(o=>o.project));for(const o of t.routes)o.lens&&r.add(o.lens.project);const s=new Set;for(const o of r){const c=e[o];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,r,s,o)=>{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,r,o);return u.length>0?u: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 r=(0,scope_1.readSourceSetOptions)(t.root),s=r.exclude,o={ignoreTests:r.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,o),d=Date.now(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),m=Q?diffSourceSets(Q,c,d,u):null;if(m&&(t.sourceSetChangedAt=d,t.sourceSetChange=m,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:m.added,removed:m.removed,truncated:m.truncated})),Q=c,!t.projectRoots){H=e;return}let L;try{L=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(p=>p.isDirectory()).map(p=>p.name)}catch{return}const k=ie(),R=new Set(t.services.map(p=>p.project));for(const p of t.routes)p.lens&&R.add(p.lens.project);const A=new Map;for(const p of R){const j=ae(p,L,k,s);if(!j)continue;const $=j.join("|"),T=A.get($)??{dirs:j,projects:[]};T.projects.push(p),A.set($,T)}const S=Date.now(),E=sourceChangeWindowMs(S,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const N=i((p,j,$)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const T of p)t.sourceSetChangedFor.set(T,j),t.sourceSetChangesFor.set(T,$)},"stampScoped");for(const[p,{dirs:j,projects:$}]of A){const T=await sourceSetSnapshot(t.root,j,s,o),z=B.get(p);B.set(p,T);const O=new Map;for(const K of $){const F=oe.get(K);if(F===void 0||F===p)continue;const q=O.get(F)??[];q.push(K),O.set(F,q)}for(const K of $)oe.set(K,p);for(const[K,F]of O){const q=B.get(K),U=q?diffSourceSets(q,T,S,E):m;U&&(N(F,q?S:d,U),g("info",`source file set changed \u2014 ${F.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:F,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!z)continue;const M=diffSourceSets(z,T,S,E);M&&(N($,S,M),g("info",`source file set changed in ${$.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:$,added:M.added,removed:M.removed,truncated:M.truncated}))}for(const p of[...B.keys()])A.has(p)||B.delete(p)})().catch(()=>{}).finally(()=>{Z=!1})},"maybeScanSourceSet"),de=i((e,r)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,o=podWatcherVerdict(e.watcher,r,s),c=i(d=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return o==="heard"?(e.staleVerifiedAt=r,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:o}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):o==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):o==="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,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){te(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const s=!!e.decl.eager;W(e,`${r} \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(r=>console.error("[gateway] sweep error:",r))},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 r=e.decl.idleKillSec;if(r&&e.inflight===0&&Date.now()-e.lastUsed>r*1e3){W(e,`idle ${r}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,o=C(e.pool)>0;if(o||s){if(e.wedgedSince=void 0,o&&b(e,"observed"),o&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await V(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:C,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:W,markUp:b,startSweeper:ue,rollPod:te}}w(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}w(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RouteRegistry, WorkspaceFragment } from './route-registry';
|
|
2
|
+
/** A root as the filesystem names it — so `/tmp/x` and `/private/tmp/x` are one checkout. */
|
|
3
|
+
export declare function canonicalRoot(root: string): string;
|
|
4
|
+
export declare const sameRoot: (a: string, b: string) => boolean;
|
|
5
|
+
export interface HostConflict {
|
|
6
|
+
/** The host as declared, or `(default for <apex>)` for a catch-all. */
|
|
7
|
+
host: string;
|
|
8
|
+
prefix?: string;
|
|
9
|
+
heldBy: string;
|
|
10
|
+
heldByRoot: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Refusal {
|
|
13
|
+
error: 'key-in-use' | 'host-conflict' | 'not-your-registration' | 'daemon-workspace';
|
|
14
|
+
/** One sentence a person can act on. The CLI prints it as is. */
|
|
15
|
+
message: string;
|
|
16
|
+
heldByRoot?: string;
|
|
17
|
+
conflicts?: HostConflict[];
|
|
18
|
+
}
|
|
19
|
+
/** Claims of `fragment` that ANOTHER workspace already holds. Re-registering yourself is not a conflict. */
|
|
20
|
+
export declare function hostConflicts(registry: RouteRegistry, fragment: WorkspaceFragment): HostConflict[];
|
|
21
|
+
/** Why this registration must not go in — or `undefined` when it may. */
|
|
22
|
+
export declare function refuseRegistration(registry: RouteRegistry, fragment: WorkspaceFragment): Refusal | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Why this unregistration must not happen — or `undefined` when it may.
|
|
25
|
+
*
|
|
26
|
+
* `root` is what makes it safe: a caller that says where it is may only remove what was registered
|
|
27
|
+
* from there. A caller that does not say (a CLI from before this guard) is taken at its word, as before.
|
|
28
|
+
*/
|
|
29
|
+
export declare function refuseUnregistration(registry: RouteRegistry, body: {
|
|
30
|
+
wsKey: string;
|
|
31
|
+
root?: string;
|
|
32
|
+
}, daemonWsKey?: string): Refusal | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var l=Object.defineProperty;var n=(e,t)=>l(e,"name",{value:t,configurable:!0});var u=Object.defineProperty,r=n((e,t)=>u(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.sameRoot=void 0,exports.canonicalRoot=canonicalRoot,exports.hostConflicts=hostConflicts,exports.refuseRegistration=refuseRegistration,exports.refuseUnregistration=refuseUnregistration;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function canonicalRoot(e){try{return fs.realpathSync(e)}catch{return path.resolve(e)}}n(canonicalRoot,"canonicalRoot"),r(canonicalRoot,"canonicalRoot");const sameRoot=r((e,t)=>canonicalRoot(e)===canonicalRoot(t),"sameRoot");exports.sameRoot=sameRoot;function claim(e){return e.host?`${e.host.toLowerCase()} ${e.prefix??""}`:e.apex?`* ${e.apex.toLowerCase()}`:void 0}n(claim,"claim"),r(claim,"claim");function hostConflicts(e,t){const s=[],o=new Set;for(const i of e.keys()){if(i===t.wsKey)continue;const a=e.get(i);if(!a)continue;const f=new Set(a.routes.map(claim));for(const c of t.routes){const h=claim(c);!h||!f.has(h)||o.has(h)||(o.add(h),s.push({host:c.host??`(default for ${c.apex})`,...c.prefix?{prefix:c.prefix}:{},heldBy:i,heldByRoot:a.root}))}}return s}n(hostConflicts,"hostConflicts"),r(hostConflicts,"hostConflicts");const describeConflict=r(e=>`${e.host}${e.prefix??""} (served by '${e.heldBy}')`,"describeConflict");function refuseRegistration(e,t){const s=e.get(t.wsKey);if(s&&!(0,exports.sameRoot)(s.root,t.root))return{error:"key-in-use",heldByRoot:s.root,message:`'${t.wsKey}' is already served from ${s.root}. ${t.root} is another checkout that derives the same key, and two checkouts cannot share a key or hosts behind one gateway: registering this one would replace the other's routes. Give this checkout its own \`key\` in ${path.join(t.root,".lensmcp","config.json")} and its own hosts, or run \`lensmcp gateway stop\` in ${s.root} first.`};const o=hostConflicts(e,t);if(o.length>0){const i=o.slice(0,6).map(describeConflict).join(", "),a=o.length>6?`, and ${o.length-6} more`:"";return{error:"host-conflict",conflicts:o,message:`'${t.wsKey}' declares ${o.length===1?"a host":`${o.length} hosts`} another workspace already serves: ${i}${a}. The workspace registered first would win every request to ${o.length===1?"it":"them"}, so this registration is refused. Two workspaces behind one gateway need different hosts.`}}}n(refuseRegistration,"refuseRegistration"),r(refuseRegistration,"refuseRegistration");function refuseUnregistration(e,t,s){const o=e.get(t.wsKey);if(o&&t.root&&!(0,exports.sameRoot)(o.root,t.root))return{error:"not-your-registration",heldByRoot:o.root,message:`'${t.wsKey}' is served from ${o.root}, not from ${t.root} \u2014 nothing of this checkout is registered, so nothing was removed.`};if(o&&s!==void 0&&t.wsKey===s)return{error:"daemon-workspace",heldByRoot:o.root,message:`'${t.wsKey}' is the workspace that owns the gateway \u2014 unregistering it would leave :443 serving without it. Stop the gateway instead: \`lensmcp gateway stop\` in ${o.root}.`}}n(refuseUnregistration,"refuseUnregistration"),r(refuseUnregistration,"refuseUnregistration");
|
package/main.devserver.d.ts
CHANGED
|
@@ -29,6 +29,24 @@ export interface GatewayRoute {
|
|
|
29
29
|
export declare function isOwnerOnlyTransport(req: {
|
|
30
30
|
socket?: unknown;
|
|
31
31
|
}): boolean;
|
|
32
|
+
/** A devserver the gateway started carries its pid; one run by hand (`nx run <svc>:serve-hmr`) does not. */
|
|
33
|
+
export declare function gatewayManaged(env?: NodeJS.ProcessEnv): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* May this request DRIVE THE POOL — `/webpack/reload`, `/webpack/watching`, `/webpack/scale`?
|
|
36
|
+
*
|
|
37
|
+
* Under a gateway, only the control socket may. The TCP port is not an identity: it comes from a
|
|
38
|
+
* tracked config file, so every checkout of a repo has the same number. A second checkout's FIRST
|
|
39
|
+
* build has no control socket yet, so its plugin fell back to `localhost:<port>` — and that port
|
|
40
|
+
* belonged to the first checkout, whose pods it hot-swapped (issues/foodguard/35). Nothing checked
|
|
41
|
+
* who was asking. The gateway and the service's own build plugin both reach the control socket,
|
|
42
|
+
* which lives in the workspace's owner-only socket dir; nobody else has a reason to.
|
|
43
|
+
*
|
|
44
|
+
* A devserver run by hand has no gateway and no second checkout behind one door; its port stays
|
|
45
|
+
* its admin surface, as before.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mayDriveThePool(req: {
|
|
48
|
+
socket?: unknown;
|
|
49
|
+
}, managed?: boolean): boolean;
|
|
32
50
|
/**
|
|
33
51
|
* Opt-out that restores the pre-`06` PREDICTABLE inspector ports (`base+slot`, `base+20+i`).
|
|
34
52
|
*
|
package/main.devserver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var Oe=Object.defineProperty;var
|
|
1
|
+
"use strict";var Oe=Object.defineProperty;var h=(o,p)=>Oe(o,"name",{value:p,configurable:!0});var Ce=Object.defineProperty,s=h((o,p)=>Ce(o,"name",{value:p,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEBUG_FIXED_PORTS_ENV=void 0,exports.isOwnerOnlyTransport=isOwnerOnlyTransport,exports.gatewayManaged=gatewayManaged,exports.mayDriveThePool=mayDriveThePool,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 o=process.env.GATEWAY_CONFIG;if(!o)return{};try{return JSON.parse(o)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const o=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof o=="function"?o:typeof o.default=="function"?o.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(o){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,o)}function applyGatewayMiddleware(o){if(gatewayMiddleware)try{gatewayMiddleware(o,GATEWAY_CONFIG)}catch(p){console.error("[gateway] Middleware error:",p)}}h(applyGatewayMiddleware,"applyGatewayMiddleware"),s(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(o,p){if(o.startsWith("*.")){const g=o.slice(1);return p.endsWith(g)||p===o.slice(2)}return p===o}h(hostMatches,"hostMatches"),s(hostMatches,"hostMatches");function matchGatewayRoute(o){if(GATEWAY_ROUTES.length===0)return;const p=(o.headers.host||"").split(":")[0],g=o.url||"/";for(const _ of GATEWAY_ROUTES)if(!(_.host&&!hostMatches(_.host,p))&&!(_.prefix&&!g.startsWith(_.prefix)))return _}h(matchGatewayRoute,"matchGatewayRoute"),s(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_DIR=LENSMCP_WS_KEY?path.join(os.tmpdir(),LENSMCP_WS_KEY,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),POD_GENERATION=(()=>{const o=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(o)&&o>1?Math.floor(o):1})();function childSockPath(o){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${o}-g${POD_GENERATION}.sock`:`child-${o}.sock`)}h(childSockPath,"childSockPath"),s(childSockPath,"childSockPath");function sockOwnerPath(o){return`${o}.owner`}h(sockOwnerPath,"sockOwnerPath"),s(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(o){return`${o}.retiring`}h(sockRetiringPath,"sockRetiringPath"),s(sockRetiringPath,"sockRetiringPath");function isOwnerOnlyTransport(o){try{return typeof o.socket?.server?.address?.()=="string"}catch{return!1}}h(isOwnerOnlyTransport,"isOwnerOnlyTransport"),s(isOwnerOnlyTransport,"isOwnerOnlyTransport");function gatewayManaged(o=process.env){return Number(o.LENSMCP_GATEWAY_PID??0)>0}h(gatewayManaged,"gatewayManaged"),s(gatewayManaged,"gatewayManaged");function mayDriveThePool(o,p=gatewayManaged()){return!p||isOwnerOnlyTransport(o)}h(mayDriveThePool,"mayDriveThePool"),s(mayDriveThePool,"mayDriveThePool");function refusePoolCommand(o,p){console.warn(`[devserver] refused ${o.method} ${o.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),p.writeHead(404,{"content-type":"application/json"}),p.end(JSON.stringify({error:"not found"}))}h(refusePoolCommand,"refusePoolCommand"),s(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(o=process.env){const p=(o[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return p==="1"||p==="true"||p==="yes"||p==="on"}h(usesFixedInspectorPorts,"usesFixedInspectorPorts"),s(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(o,p,g=process.env){if(!usesFixedInspectorPorts(g))return 0;const _=Math.trunc(o)+Math.trunc(p);return Number.isInteger(_)&&_>1024&&_<=65535?_:0}h(inspectorPortFor,"inspectorPortFor"),s(inspectorPortFor,"inspectorPortFor");const INSPECTOR_TOKEN_RE=/((?:wss?:\/\/[^\s/]+\/|ws=[^\s/]+\/))([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;function redactInspectorUrls(o){return o.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}h(redactInspectorUrls,"redactInspectorUrls"),s(redactInspectorUrls,"redactInspectorUrls");function inspectorPortOf(o){const p=/^wss?:\/\/[^/]*?:(\d+)\//.exec(o??""),g=p?Number(p[1]):NaN;return Number.isInteger(g)&&g>0?g:null}h(inspectorPortOf,"inspectorPortOf"),s(inspectorPortOf,"inspectorPortOf");function ensureOwnerOnlyDir(o){try{fs.mkdirSync(o,{recursive:!0,mode:448})}catch{}try{const p=fs.lstatSync(o);p.isDirectory()&&(p.mode&63)!==0&&fs.chmodSync(o,448)}catch{}}h(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),s(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(o,p=process.pid){ensureOwnerOnlyDir(path.dirname(o));try{const g=sockOwnerPath(o);try{fs.unlinkSync(g)}catch{}fs.writeFileSync(g,String(p),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(sockRetiringPath(o))}catch{}}h(claimSock,"claimSock"),s(claimSock,"claimSock");function cleanupSock(o,p=process.pid){if(o){try{const g=fs.readFileSync(sockOwnerPath(o),"utf8").trim();if(g&&g!==String(p))return}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(sockOwnerPath(o))}catch{}try{fs.unlinkSync(sockRetiringPath(o))}catch{}}}h(cleanupSock,"cleanupSock"),s(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const o=process.env.LENSMCP_SERVICE_KEY;if(!o)return;const{createHash:p}=require("node:crypto");return p("sha256").update(o).digest("hex").slice(0,8)}h(serviceKeyFingerprint,"serviceKeyFingerprint"),s(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"){(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=
|
|
14
|
-
`+String(b),"stack")}}),W=
|
|
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),
|
|
17
|
-
`);
|
|
12
|
+
})()`),eval(`require(${JSON.stringify(resolved)})`)}h(importFresh,"importFresh"),s(importFresh,"importFresh");function debounce(o,p){let g=null;return(..._)=>{g&&clearTimeout(g),g=setTimeout(()=>o(..._),p)}}if(h(debounce,"debounce"),s(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const o=s(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"),p=process.env.CHILD_DEBUG_PORT;if(p!==void 0&&p!==""){const g=Number(p);Number.isInteger(g)&&(g===0||g>1024)&&o(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,_=!1,B,j=!1;const N=(0,hot_swap_registries_1.registryPruningEnabled)(),Z=s((r,i)=>{const f=(0,hot_swap_registries_1.describePrune)(r,i);f&&console.log(f)},"logPrune");let $={app:null,handler:s((r,i)=>{i.statusCode=503,i.end("starting")},"handler"),inflight:0};const ee=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),Y=s(r=>{const i=globalThis.__lensmcpActiveWork;if(typeof i!="function")return{count:0};try{const f=i(r);return{count:Number(f?.count??0),...f?.oldestStartedAt?{oldestStartedAt:f.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),te=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),C=new Set,P=(0,swap_recycle_1.swapRecyclePolicy)();let oe,le=0;const ge=(()=>{const r=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(r)&&r>=0?r:3500})();let K=!1,V=!1,u;const m=s(()=>{if(u===void 0)try{v8.setFlagsFromString("--expose-gc");const r=vm.runInNewContext("gc");u=typeof r=="function"?r:null}catch{u=null}try{u?.()}catch{}return process.memoryUsage().heapUsed},"liveHeap"),w=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,i=`${path.sep}node_modules${path.sep}`,f=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:s(y=>f.has(y)||y.startsWith(r)&&!y.slice(r.length).includes(i)&&!y.slice(r.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let O=!1;function W(){if(!(!w||O))try{const r=w();r.errors>0&&console.log(`[child] read the stack of ${r.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(r){console.error("[child] could not release library error frames:",r),O=!0}}h(W,"z"),s(W,"releaseLibraryErrorFrames");const D=new Map;let b=Date.now();const T=s(()=>P.settleMs<=0||!_&&!j&&D.size===0&&Date.now()-b>=P.settleMs,"settled");let A;function R(r){if(!(!P.enabled||K||V)){if(P.settleMs<=0){F(r);return}A&&clearTimeout(A),A=setTimeout(()=>{A=void 0,T()?F(r):R(r)},P.settleMs),A.unref?.()}}h(R,"N"),s(R,"recycleCheckWhenSettled");const ne=s(()=>{if(P.stuckCloseMs<=0)return 0;const r=Date.now();let i=0;for(const f of D.values())r-f>=P.stuckCloseMs&&(i+=1);return i},"stuckCloses");function F(r){if(!P.enabled||K||V||!process.send)return;const i=v8.getHeapStatistics().heap_size_limit,f=s(E=>({swaps:le,usedBytes:E,limitBytes:i,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(m()));y&&(K=!0,console.log(`[child] asking for a fresh pod ${r} \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}))}h(F,"K"),s(F,"considerRecycle");async function J(r,i){if(r.closed||(r.closed=!0,C.delete(r),i&&console.error(`[child] ${i}`),!r.app))return;D.set(r,Date.now());const f=P.enabled&&P.stuckCloseMs>0?setTimeout(()=>{D.has(r)&&(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 r.app.close()}catch(y){console.error("[child] error closing a previous app:",y)}finally{D.delete(r),b=Date.now(),f&&clearTimeout(f),r.app=null,r.handler=(y,E)=>{E.statusCode=503,E.end("closed")}}if(r.superseded){try{Z((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(y){console.error("[child] could not release the previous app's class registrations:",y)}r.superseded=void 0}R("after closing the previous app")}h(J,"Q"),s(J,"closeGeneration");async function de(r){for(;C.size>=te;){const E=C.values().next().value;if(!E)break;await J(E,`closing a still-draining generation early \u2014 ${C.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.`)}C.add(r);const i=Date.now(),f=s(()=>({inflight:r.inflight,work:Y(r.workGen).count}),"busy");for(;ee>0&&Date.now()-i<ee;){if(r.closed)return;const E=f();if(E.inflight===0&&E.work===0)break;await new Promise(x=>{setTimeout(x,250).unref?.()})}if(r.closed)return;const y=f();y.inflight>0||y.work>0?console.error(`[child] swap drain expired after ${Date.now()-i}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()-i>250&&console.log(`[child] previous app drained in ${Date.now()-i}ms \u2014 closing it`),await J(r)}h(de,"he"),s(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 r;if(N)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=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 i;if(r)try{i=(0,hot_swap_registries_1.supersededSince)(r),Z((0,hot_swap_registries_1.pruneRegistries)(i,"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 E;try{E=await global.createChildApp()}catch(S){throw _=!1,b=Date.now(),S}const x=$;return i?.size&&(x.app?x.superseded=i:Z((0,hot_swap_registries_1.pruneRegistries)(i,"serve"),"at the swap")),g=E,$={app:E,handler:E.handler,inflight:0,...y===void 0?{}:{workGen:y}},console.log("[child] Swapped to fresh app (previous draining in background)"),W(),x.app&&(le+=1,de(x).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()):E}h(M,"V"),s(M,"swapNow");const re=debounce(async()=>{try{console.log("[child] /webpack/reload"),await M()}catch(r){console.error("[child] reload failed:",r)}},250),G=http.createServer((r,i)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){i.writeHead(200,{"content-type":"application/json"}),i.end(JSON.stringify({ok:!0})),re();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const y=r.headers["x-internal-token"],E=process.env.LENSMCP_SERVICE_KEY;if(E&&y!==E){i.writeHead(404,{"content-type":"text/plain"}),i.end("not found");return}const x=Y();i.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),i.end(JSON.stringify({ok:!0,pid:process.pid,swapping:_,inflight:$.inflight,oldestInflightAt:$.oldestAt,activeWork:x.count,oldestWorkAt:x.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()),i.on("close",()=>{f.inflight-=1,f.inflight===0&&(f.oldestAt=void 0)}),f.handler(r,i)});G.keepAliveTimeout=0;const I=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(I),process.exit(0)});try{const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const i=(0,schema_first_instance_1.prewarmZodFor)(r);i?.created&&console.log(`[child] zod${i.version?` ${i.version}`:""}: built ${i.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 i=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(r);i?.patched&&console.log(`[child] ioredis${i.versions.length?` ${i.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=m()),console.log(`[child] Using bundle: ${r}`),claimSock(I),G.listen(I,()=>{console.log(`[child] Listening on ${I}`),process.send&&process.send({type:"ready",socketPath:I})})}catch(r){console.error("[child] Startup error:",r),process.send&&process.send({type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const i=Number(r.port);o(Number.isInteger(i)&&i>1024?i:0);return}if(r.type==="watcher"){const i=r.testimony;i&&typeof i=="object"&&(B=i);return}if(r.type==="recycle-declined"){K=!1;return}if(r.type==="retire"){U();return}if(r.type==="reload"){if(V)return;try{await M(),process.send&&process.send({type:"reloaded",socketPath:I})}catch(i){console.error("[child] reload error:",i),process.send&&process.send({type:"reload-error",error:String(i?.stack||i)})}}}});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(i=>{setTimeout(i,ge).unref?.()}),cleanupSock(I),G.close();const r=Date.now();for(;Date.now()-r<ee&&(G.closeIdleConnections?.(),!($.inflight===0&&Y().count===0));)await new Promise(i=>{setTimeout(i,250).unref?.()});setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(i){console.error("[child] close error while retiring:",i)}process.exit(0)}h(U,"ee"),s(U,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const X=s(async r=>{console.log(`[child ${r}] shutting down\u2026`);const i=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([i,f]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{inspector?.close?.(),X(r)})})})()}else{let o=h(function(u){C.push(u),C.length>te&&C.splice(0,C.length-te),ee?.write(u.replace(Y,""));for(const m of P)try{const w=m.plain?u.replace(Y,""):u;m.res.write(m.redact?redactInspectorUrls(w):w)}catch{P.delete(m)}},"P");s(o,"emitLogLine");const p=["\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=s(u=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(B,SERVICE_NAME||"service",u)),"execPathFor");let N=(()=>{const u=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(u)&&u>1024?u:0})();const Z=20;N&&(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 (${N}+slot, workers ${N}+${Z}+i). Any account on this host can connect to 127.0.0.1:${N} 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 u=fs.createWriteStream($,{flags:"a",mode:384});try{fs.chmodSync($,384)}catch{}return u}catch(u){return console.warn(`[parent] cannot open log file ${$}:`,u.message),null}})():null,Y=/\x1b\[[0-9;]*m/g,te=2e3,C=[],P=new Set;for(const u of["log","info","warn","error"]){const m=console[u].bind(console);console[u]=(...w)=>{m(...w),o(w.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
+
`)}}const oe=s(u=>p[(u-1)%p.length],"colorFor"),le=s(u=>{if(_===1)return"";const m=`[child#${u}] `;return`${oe(u)}${m}${g}`},"tagFor"),ge=s(u=>u.startsWith("{")&&u.endsWith("}"),"isJsonLog"),K=s((u,m,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:s(b=>`
|
|
14
|
+
`+String(b),"stack")}}),W=s((b,T)=>{const A=b==="stdout"?process.stdout:process.stderr;try{w?.(T)}catch{}const R=ge(T)?O(T):`${T}
|
|
15
|
+
`;A.write(`${m}${R}`),o(`${m}${R}`)},"write"),D=s((b,T)=>{if(!b)return;const A=readline.createInterface({input:b});A.on("line",R=>W(T,R)),A.on("close",()=>{})},"attach");D(u.stdout,"stdout"),D(u.stderr,"stderr")},"wireProcLogging"),V=s(u=>K(u.proc,le(u.id),m=>u.tail.push(m)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const u=httpProxy.createProxyServer({});let m=Number(process.env.PORT)||0;m||(m=9090);const w=[],O=new Set;function W(){let e=0;for(;O.has(e);)e++;return O.add(e),e}h(W,"z"),s(W,"allocDebugSlot");let D=1,b=0,T=!1,A=_,R=[],ne=0;const F=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),J=s(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),de=s(e=>e.booting&&!J(e)&&Date.now()-e.spawnedAt<F,"stillBooting");function M(){return w.filter(e=>e.healthy&&typeof e.socketPath=="string")}h(M,"V"),s(M,"healthyChildren");let re;function G(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){re=t;for(const d of w)try{d.proc.send?.({type:"watcher",testimony:t})}catch{}}}h(G,"U"),s(G,"recordWatcherTestimony");function I(e,t=256*1024){return new Promise(d=>{let a="",c=!1;e.on("data",n=>{c||(a+=n,a.length>t&&(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))})}h(I,"I"),s(I,"readJsonBody");function U(){const e=M();if(e.length===0)return null;const t=b%e.length;return b=(b+1)%e.length,e[t]}h(U,"ee"),s(U,"pickChild");async function X(){const e=D++,t=childSockPath(e);cleanupSock(t);const d=["--enable-source-maps"];__filename.endsWith(".ts")&&d.unshift("--require","@swc-node/register");const a=N?W():-1,c=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...a>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(N,a))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:d,execPath:j("pod")}),n={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(n.socketPath=String(l.socketPath),n.healthy=!0,n.booting=!1,console.log(`[parent] Child#${n.id} ready on socket ${n.socketPath}`),re)try{c.send?.({type:"watcher",testimony:re})}catch{}if(ne!==n.bootReloadSeq)try{c.send?.({type:"reload"})}catch{}}else l.type==="recycle-request"?L(n,String(l.reason||"it has outgrown its bound")):l.type==="inspector-url"?(n.inspectorUrl=String(l.url),console.log(`[parent] Child#${n.id} Debugger listening on ${l.url}`)):l.type==="reloaded"?(n.socketPath=String(l.socketPath),n.healthy=!0,console.log(`[parent] Child#${n.id} hot-swapped on socket ${n.socketPath}`)):l.type==="reload-error"?(n.lastError=String(l.error||"unknown reload error"),console.error(`[parent] Child#${n.id} reload error: ${n.lastError}`),n.healthy=!1):l.type==="boot-error"&&(n.lastError=String(l.error||"unknown boot error"),console.error(`[parent] Child#${n.id} boot error: ${n.lastError}`),n.healthy=!1,n.booting=!1)}),c.on("exit",(k,l)=>{n.booting=!1,n.debugSlot>=0&&(O.delete(n.debugSlot),n.debugSlot=-1);const v=l==="SIGINT"||l==="SIGTERM"||k===0;if(v?console.log(`[parent] Child#${n.id} exited cleanly (code=${k}, signal=${l??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${n.id} crashed (code=${k}, signal=${l??"none"}, pid=${n.proc.pid}, ${n.healthy?"was serving":"was not ready"})`,k,l,n.tail.snapshot())),n.lastError||(n.lastError=`Child exited abnormally (code=${k}, signal=${l??"none"})`)),n.healthy=!1,cleanupSock(n.socketPath,n.proc.pid),n.socketPath=void 0,!v&&!T){const q=w.indexOf(n);q>=0&&w.splice(q,1);const ae=Date.now();if(R=R.filter(Q=>ae-Q<3e4),R.length>=5){console.error(`[parent] crash-loop detected (${R.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}R.push(ae),setTimeout(()=>{const Q=w.filter($e=>$e.proc.killed===!1).length;!T&&Q<A&&(console.log(`[parent] respawning a crashed pod (${Q}/${A} live)`),X().catch(()=>{}))},1500).unref?.()}}),w.push(n),V(n),n}h(X,"te"),s(X,"spawnChild");async function r(e){A=Math.max(A,e);const t=w.filter(a=>a.proc.killed===!1),d=e-t.length;for(let a=0;a<d;a++)await X()}h(r,"r"),s(r,"ensurePoolSize");async function i(){const e=w.filter(t=>(!t.healthy||typeof t.socketPath!="string")&&!de(t));await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const d=w.indexOf(t);d>=0&&w.splice(d,1),await X()}))}h(i,"c"),s(i,"respawnUnhealthy");const f=new Set;let y=!1,E=0;const x=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,t){const d=s(c=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${t}) \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()-E;if(a<x)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 ${t}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const c=await X(),n=Date.now()+F;for(;!c.healthy&&!c.lastError&&!J(c)&&Date.now()<n;)await new Promise(v=>{setTimeout(v,100).unref?.()});if(T)return;if(!c.healthy){const v=w.indexOf(c);v>=0&&w.splice(v,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{}}E=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}}h(L,"D"),s(L,"recyclePod");const H=[];function ye(e){const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),d=path.join(t,`${e}.js`);if(!fs.existsSync(d))return console.error(`[parent] Worker "${e}" bundle not found: ${d}`),null;const 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(N||9229,Z+Math.max(0,WORKER_NAMES.indexOf(e)));a.push(N?`--inspect=127.0.0.1:${c}`:`--inspect-port=127.0.0.1:${c}`);const n=(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:n,debugPort:c};return H.push(k),K(n,`\x1B[35m[worker:${e}] \x1B[0m`,l=>{const v=/Debugger listening on (wss?:\/\/\S+)/.exec(l);v&&(k.inspectorUrl=v[1])}),n.on("exit",(l,v)=>{v==="SIGINT"||v==="SIGTERM"||l===0||console.error(`[parent] Worker "${e}" crashed (code=${l}, signal=${v??"none"})`);const q=H.indexOf(k);q>=0&&H.splice(q,1)}),console.log(`[parent] Worker "${e}" started (pid=${n.pid})`),k}h(ye,"we"),s(ye,"spawnWorkerProcess");const Ee=(()=>{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=s(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function pe(e,t){if(se.delete(e),!Pe(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${t} \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{}}}h(pe,"fe"),s(pe,"killDrainingWorker");function ke(e){try{e.proc.kill("SIGTERM")}catch{}if(Pe(e))return;const t=[...se].filter(a=>a.name===e.name);for(;t.length>=Se;)pe(t.shift(),`was still running when a later reload replaced its successor (cap ${Se})`);se.add(e);const d=setTimeout(()=>pe(e,`was still running ${Ee}ms after SIGTERM`),Ee);d.unref?.(),e.proc.once("exit",()=>{clearTimeout(d),se.delete(e)})}h(ke,"ke"),s(ke,"retireWorker");function be(){for(const e of[...H])ke(e);H.length=0;for(const e of WORKER_NAMES)ye(e)}h(be,"Ae"),s(be,"restartWorkers");const Re=debounce(async()=>{ne+=1;const e=M();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)t.proc.send?.({type:"reload"})}await i(),WORKER_NAMES.length>0&&be()},200),ie=s(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!mayDriveThePool(e))return refusePoolCommand(e,t);t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),I(e).then(c=>G(c?.watcher)),Re();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!mayDriveThePool(e))return refusePoolCommand(e,t);const c=await I(e);G(c),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:re!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!mayDriveThePool(e))return refusePoolCommand(e,t);let c="";e.on("data",n=>{c+=n}),e.on("end",async()=>{let n=0;try{n=Number(JSON.parse(c||"{}").pods)||0}catch{}n=Math.max(1,Math.min(n,16)),await r(n),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:n}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const c=new URL(e.url,"http://localhost").searchParams,n=Math.min(Math.max(Number(c.get("tail")??"100")||0,0),te),k=c.get("follow")!=="0",l=c.get("plain")==="1",v=!isOwnerOnlyTransport(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const q=n===0?"":C.slice(-n).join(""),ae=l?q.replace(Y,""):q;if(t.write(v?redactInspectorUrls(ae):ae),!k){t.end();return}const Q={res:t,plain:l,redact:v};P.add(Q),e.on("close",()=>P.delete(Q));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!isOwnerOnlyTransport(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const c=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(c)&&c>1024&&(N=c),N||(N=9229);const n=w.filter(l=>l.proc.exitCode===null&&l.proc.signalCode===null);for(const l of n){l.debugSlot<0&&(l.debugSlot=W());try{l.proc.send?.({type:"debug-open",port:inspectorPortFor(N,l.debugSlot)})}catch{}}for(const l of H)try{l.proc.kill("SIGUSR1")}catch{}const k=Date.now()+2e3;for(;Date.now()<k&&n.some(l=>!l.inspectorUrl);)await new Promise(l=>setTimeout(l,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:N,fixedPorts:usesFixedInspectorPorts(),pods:n.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),u.web(e,t,{target:d.target,autoRewrite:typeof d.target=="string",changeOrigin:!0},c=>{console.error(`[parent] proxy error to ${d.target}:`,c),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${d.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const a=U();if(!a){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const c=w.map(n=>`#${n.id}: ${n.lastError??"no error recorded"}`).join(`
|
|
17
|
+
`);t.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"),
|
|
20
|
+
${c}`);return}u.web(e,t,{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),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.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"),t=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",t),we=e.caCertPath(),z=require("node:https").createServer({key:ce,cert:ce},ie)}else z=http.createServer(ie);const ve=s((e,t,d)=>{const a=matchGatewayRoute(e);if(a?.prependPrefix&&!e.url?.startsWith(a.prependPrefix)&&(e.url=a.prependPrefix+(e.url??"/")),a?.target){u.ws(e,t,d,{target:a.target,changeOrigin:!0},()=>t.destroy());return}const c=U();if(!c){t.destroy();return}u.ws(e,t,d,{target:{socketPath:c.socketPath}},()=>t.destroy())},"upgradeHandler");z.on("upgrade",ve);const Te=2e3;let Ne=!1;z.on("error",e=>{if(e.code==="EADDRINUSE"){Ne||(Ne=!0,console.warn(`[parent] public port ${m} 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(m)},Te).unref?.();return}console.error("[parent] public server error:",e)}),await r(_);for(const e of WORKER_NAMES)ye(e);z.listen(m,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${m}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),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:${m}/webpack/reload to trigger rolling swap/respawn`)});const he=Number(process.env.INTERNAL_PORT||0);if(he>0){const e=s((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}u.web(d,a,{target:{socketPath:c.socketPath}},n=>{c.healthy=!1,c.lastError=String(n?.stack||n),a.statusCode=502,a.end(`Upstream error from Child#${c.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(d,a,c)=>{const n=U();if(!n){a.destroy();return}u.ws(d,a,c,{target:{socketPath:n.socketPath}},()=>a.destroy())}),t.on("error",d=>{console.warn(`[parent] internal port ${he} unavailable (${d.code}).`)}),t.listen(he,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${he}`)}),ue.push(t)}const Ie=(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of Ie){if(!Number.isInteger(e)||e===Number(m))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:ce,cert:ce},ie):http.createServer(ie);t.on("upgrade",ve),t.on("error",d=>{console.warn(`[parent] extra port ${e} unavailable (${d.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),ue.push(t)}const _e=path.join(SOCK_DIR,"parent.sock");claimSock(_e);const me=http.createServer(ie);me.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),me.listen(_e),ue.push(me);const Ae=s(async e=>{T=!0,console.log(`[${e}] [parent] shutting down\u2026`),cleanupSock(_e);for(const t of ue)try{t.close()}catch{}for(const t of H)try{t.proc.kill("SIGTERM")}catch{}for(const t of[...se])pe(t,"was still draining when its devserver shut down");for(const t of f)try{t.proc.kill("SIGTERM")}catch{}for(const t of w){try{t.proc.kill("SIGTERM")}catch{}t.socketPath&&cleanupSock(t.socketPath,t.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{Ae(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(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${fe}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),Ae("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.13",
|
|
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.13",
|
|
61
|
+
"@lensmcp/node-instrumentation": "1.23.13",
|
|
62
|
+
"@lensmcp/nx-plugin": "1.23.13",
|
|
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/tsgo-check-plugin.d.ts
CHANGED
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
* 2-worker pair (~80MB+) per service, ~1GB across a 13-service cluster, alive even when nobody
|
|
6
6
|
* saves. tsgo (@typescript/native-preview, the native TS compiler) full-checks one of these
|
|
7
7
|
* services cold in ~1-3s, so instead of holding a program we re-check COLD after each successful
|
|
8
|
-
* rebuild (debounced
|
|
8
|
+
* rebuild (debounced): idle cost is exactly zero.
|
|
9
|
+
*
|
|
10
|
+
* ONE AT A TIME (issues/foodguard/34). "Zero resident" is not "small": a cold check of a real service
|
|
11
|
+
* peaks at 1.4-3.4GB. Every service's plugin used to start its own the moment its build finished, so
|
|
12
|
+
* one edit to a shared file started one per service at the same instant; and a newer build was meant
|
|
13
|
+
* to REPLACE the running check with SIGTERM, which this checker ignores — it runs to the end and
|
|
14
|
+
* exits 0 — so each further build ADDED a process. Fifteen at once on a 32GB machine turned a 2s
|
|
15
|
+
* check into two minutes, then fourteen. So now:
|
|
16
|
+
* - a check waits its turn in a MACHINE-WIDE line (`typecheck-queue.ts`), one slot by default;
|
|
17
|
+
* - a service has at most one check running and one asked for: a build that lands while a check
|
|
18
|
+
* runs is remembered, and one more check follows — a running check is never killed to be replaced;
|
|
19
|
+
* - stopping one for real is SIGKILL, and that happens only on shutdown or on a timeout, which is
|
|
20
|
+
* reported as the checker failing, never as a verdict;
|
|
21
|
+
* - one checker is held to a few cores (`GOMAXPROCS`).
|
|
9
22
|
*
|
|
10
23
|
* The output CONTRACT is the point: `--pretty false` prints classic `file.ts(1,2): error TS1234:`
|
|
11
24
|
* lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
|
|
@@ -35,6 +48,7 @@
|
|
|
35
48
|
* reports itself onto the lens bus instead of only warning into a log nobody reads.
|
|
36
49
|
*/
|
|
37
50
|
import { spawn } from 'node:child_process';
|
|
51
|
+
import { type TypecheckQueue } from './typecheck-queue';
|
|
38
52
|
export interface TsgoCheckPluginOptions {
|
|
39
53
|
appName: string;
|
|
40
54
|
/** Absolute path to the app tsconfig the check covers. */
|
|
@@ -53,7 +67,15 @@ export interface TsgoCheckPluginOptions {
|
|
|
53
67
|
eventFile?: string;
|
|
54
68
|
/** Set false to skip publishing to the lens bus (console output is unchanged). */
|
|
55
69
|
publishEvents?: boolean;
|
|
70
|
+
/** The line a check waits in before it starts. Defaults to the machine-wide one. Injectable for tests. */
|
|
71
|
+
queue?: TypecheckQueue;
|
|
72
|
+
/** A check still running after this long is killed and reported as a tool failure. Default 120s. */
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
/** Cores one checker may use (`GOMAXPROCS`). Default {@link checkerMaxProcs}. */
|
|
75
|
+
maxProcs?: number;
|
|
56
76
|
}
|
|
77
|
+
/** `LENSMCP_TYPECHECK_GOMAXPROCS`, else {@link DEFAULT_MAX_PROCS}. An explicit `GOMAXPROCS` in the env wins over both. */
|
|
78
|
+
export declare function checkerMaxProcs(env?: NodeJS.ProcessEnv): number;
|
|
57
79
|
/** The npm package that ships the tsgo compiler — a real `dependency` of `@lensmcp/cluster`. */
|
|
58
80
|
export declare const TSGO_PACKAGE = "@typescript/native-preview";
|
|
59
81
|
/** Seams for {@link resolveTsgoBin}, so every branch of the precedence is testable without an install. */
|
|
@@ -133,19 +155,42 @@ export declare function writeShimTsconfig(appName: string, tsConfig: string, wor
|
|
|
133
155
|
export declare class TsgoCheckPlugin {
|
|
134
156
|
private readonly opts;
|
|
135
157
|
private readonly spawnImpl;
|
|
158
|
+
private readonly queue;
|
|
136
159
|
private timer;
|
|
160
|
+
/** idle → waiting (in line) → running → idle. */
|
|
161
|
+
private phase;
|
|
162
|
+
/** A build finished while a check was running: one more check follows it. */
|
|
163
|
+
private dirty;
|
|
164
|
+
private ticket;
|
|
137
165
|
private inflight;
|
|
166
|
+
private askedAt;
|
|
138
167
|
private shimPath;
|
|
139
168
|
private disabled;
|
|
169
|
+
private disposed;
|
|
140
170
|
constructor(opts: TsgoCheckPluginOptions);
|
|
141
171
|
apply(compiler: any): void;
|
|
142
|
-
/** Debounced: rapid successive rebuilds collapse into one
|
|
172
|
+
/** Debounced: rapid successive rebuilds collapse into one request for a check. */
|
|
143
173
|
schedule(): void;
|
|
174
|
+
/**
|
|
175
|
+
* A build asks for a check. At most one runs and one is asked for:
|
|
176
|
+
* - already in line → nothing to add, the check reads the files when its turn comes;
|
|
177
|
+
* - running → remembered, and ONE more check follows this one. The running check is left alone:
|
|
178
|
+
* it cannot be stopped politely, and alone it is done in seconds.
|
|
179
|
+
*/
|
|
180
|
+
private request;
|
|
181
|
+
/** Asked through a method on purpose: `run()` may have moved the phase inside the call above. */
|
|
182
|
+
private isIdle;
|
|
144
183
|
/**
|
|
145
184
|
* Publish one completed check onto the lens bus so `typecheck://*` reflects
|
|
146
185
|
* it. Strictly passive + fully swallowed: the console output above is the
|
|
147
186
|
* contract webpack/the gateway rely on, this is pure observation on top.
|
|
148
187
|
*/
|
|
149
188
|
private publish;
|
|
189
|
+
/** This service's turn in the line. */
|
|
150
190
|
private run;
|
|
191
|
+
/** The check is over: give the slot back, and check once more if a build landed meanwhile. */
|
|
192
|
+
private finish;
|
|
193
|
+
private leave;
|
|
194
|
+
/** The dev server is going away: leave the line, and stop the checker for real (it ignores SIGTERM). */
|
|
195
|
+
dispose(): void;
|
|
151
196
|
}
|
package/tsgo-check-plugin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`&&(
|
|
3
|
-
`)?
|
|
4
|
-
`);const
|
|
5
|
-
`)[0]?.slice(0,300)??""}
|
|
1
|
+
"use strict";var y=Object.defineProperty;var h=(e,t)=>y(e,"name",{value:t,configurable:!0});var S=Object.defineProperty,a=h((e,t)=>S(e,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.resolveTsgoPackageDir=resolveTsgoPackageDir,exports.resolveTsgoBin=resolveTsgoBin,exports.reportTsgoUnavailable=reportTsgoUnavailable,exports.effectivePaths=effectivePaths,exports.writeShimTsconfig=writeShimTsconfig;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus"),typecheck_queue_1=require("./typecheck-queue"),DEFAULT_TIMEOUT_MS=12e4,DEFAULT_MAX_PROCS=4;function checkerMaxProcs(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_GOMAXPROCS);return Number.isInteger(t)&&t>=1?t:DEFAULT_MAX_PROCS}h(checkerMaxProcs,"checkerMaxProcs"),a(checkerMaxProcs,"checkerMaxProcs");const livePlugins=new Set;let exitHooked=!1;function stopWithTheProcess(e){livePlugins.add(e),!exitHooked&&(exitHooked=!0,process.once("exit",()=>{for(const t of livePlugins)t.dispose()}))}h(stopWithTheProcess,"stopWithTheProcess"),a(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}h(shimNames,"shimNames"),a(shimNames,"shimNames");function isExecutableFile(e,t){try{if(!fs.statSync(e).isFile())return!1}catch{return!1}if(t==="win32")return!0;try{return fs.accessSync(e,fs.constants.X_OK),!0}catch{return!1}}h(isExecutableFile,"isExecutableFile"),a(isExecutableFile,"isExecutableFile");function resolveTsgoPackageDir(){try{return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`))}catch{}try{let e=path.dirname(require.resolve(exports.TSGO_PACKAGE));for(let t=0;t<6;t++){const o=path.join(e,"package.json");if(fs.existsSync(o))try{const{name:s}=JSON.parse(fs.readFileSync(o,"utf8"));if(s===exports.TSGO_PACKAGE)return e}catch{}const r=path.dirname(e);if(r===e)break;e=r}}catch{}return null}h(resolveTsgoPackageDir,"resolveTsgoPackageDir"),a(resolveTsgoPackageDir,"resolveTsgoPackageDir");function packageDeclaredBin(e){try{const{bin:t}=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8")),o=typeof t=="string"?t:t?.tsgo;return o?path.join(e,o):null}catch{return null}}h(packageDeclaredBin,"packageDeclaredBin"),a(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const o=path.resolve(e,"..","..",".bin"),r=shimNames(t).map(i=>path.join(o,i)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...r]:r}h(ownBinCandidates,"ownBinCandidates"),a(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,o={}){const r=o.platform??process.platform,s=o.isExecutable??(n=>isExecutableFile(n,r));if(t)return s(t)?t:null;for(const n of shimNames(r)){const l=path.join(e,"node_modules",".bin",n);if(s(l))return l}const i=(o.ownPackageDir??resolveTsgoPackageDir)();return i?ownBinCandidates(i,r).find(s)??null:null}h(resolveTsgoBin,"resolveTsgoBin"),a(resolveTsgoBin,"resolveTsgoBin"),exports.TSGO_MISSING_CAUSE=`tsgo binary not found \u2014 @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;function reportTsgoUnavailable(e){try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:e.workspaceRoot,project:e.appName,tool:"tsgo",tsConfig:e.tsConfig,output:"",durationMs:0,toolError:e.detail,...e.eventFile?{eventFile:e.eventFile}:{}})}catch{}}h(reportTsgoUnavailable,"reportTsgoUnavailable"),a(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",o=!1,r=!1,s=!1;for(let i=0;i<e.length;i++){const n=e[i],l=e[i+1];if(r){n===`
|
|
2
|
+
`&&(r=!1,t+=n);continue}if(s){n==="*"&&l==="/"&&(s=!1,i++);continue}if(o){t+=n,n==="\\"?(t+=l??"",i++):n==='"'&&(o=!1);continue}if(n==='"'){o=!0,t+=n;continue}if(n==="/"&&l==="/"){r=!0,i++;continue}if(n==="/"&&l==="*"){s=!0,i++;continue}t+=n}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}h(parseJsonc,"parseJsonc"),a(parseJsonc,"parseJsonc");function resolveExtends(e,t){if(!e.startsWith(".")&&!path.isAbsolute(e))return null;const o=path.isAbsolute(e)?e:path.resolve(t,e);for(const r of[o,`${o}.json`,path.join(o,"tsconfig.json")])try{if(fs.statSync(r).isFile())return r}catch{}return null}h(resolveExtends,"resolveExtends"),a(resolveExtends,"resolveExtends");function effectivePaths(e,t=new Set){if(t.has(e))return null;t.add(e);let o;try{o=fs.readFileSync(e,"utf8")}catch{return null}const r=parseJsonc(o);if(!r)return null;const s=path.dirname(e),i=r.compilerOptions??{},n=Array.isArray(r.extends)?r.extends.filter(p=>typeof p=="string"):typeof r.extends=="string"?[r.extends]:[],l=a(()=>{for(const p of[...n].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=i.paths,c=typeof i.baseUrl=="string"?path.resolve(s,i.baseUrl):void 0;if(!u){const p=l();return p?{paths:p.paths,anchorDir:c??p.anchorDir}:null}return{paths:u,anchorDir:c??l()?.anchorDir??s}}h(effectivePaths,"effectivePaths"),a(effectivePaths,"effectivePaths");function writeShimTsconfig(e,t,o){const r=path.join(o,"tmp","lensmcp-tsgo");fs.mkdirSync(r,{recursive:!0});const s=path.join(r,`${e}.tsconfig.json`),i=effectivePaths(t),n=i?Object.fromEntries(Object.entries(i.paths).map(([l,u])=>[l,(Array.isArray(u)?u:[]).map(c=>path.isAbsolute(c)?c:path.resolve(i.anchorDir,c))])):void 0;return fs.writeFileSync(s,JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...n&&Object.keys(n).length?{paths:n}:{}}},null,2)),s}h(writeShimTsconfig,"writeShimTsconfig"),a(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{h(this,"TsgoCheckPlugin")}static{a(this,"TsgoCheckPlugin")}constructor(t){this.timer=null,this.phase="idle",this.dirty=!1,this.ticket=null,this.inflight=null,this.askedAt=0,this.shimPath=null,this.disabled=!1,this.disposed=!1,this.opts=t,this.spawnImpl=t.spawnImpl??node_child_process_1.spawn,this.queue=t.queue??(0,typecheck_queue_1.machineTypecheckQueue)()}apply(t){t.hooks.done.tap("TsgoCheckPlugin",o=>{o.hasErrors()||this.schedule()}),t.hooks.shutdown?.tap?.("TsgoCheckPlugin",()=>this.dispose()),stopWithTheProcess(this)}schedule(){this.disabled||this.disposed||(this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.request(),this.opts.debounceMs??500),this.timer.unref?.())}request(){if(this.disabled||this.disposed)return;if(this.phase==="running"){this.dirty=!0;return}if(this.phase==="waiting")return;this.phase="waiting",this.askedAt=Date.now();const t=this.queue(this.opts.appName,()=>this.run());this.isIdle()?t.release():this.ticket=t}isIdle(){return this.phase==="idle"}publish(t){if(this.opts.publishEvents!==!1)try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,output:t.output,durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{},...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}run(){if(this.disposed)return this.leave();let t;try{t=resolveTsgoBin(this.opts.workspaceRoot,this.opts.binPath),t&&!this.shimPath&&(this.shimPath=writeShimTsconfig(this.opts.appName,this.opts.tsConfig,this.opts.workspaceRoot))}catch(c){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${c.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${c.message}`}),this.leave()}if(!t){this.disabled=!0;const c=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${c}.`),this.publish({output:"",durationMs:0,toolError:c}),this.leave()}this.phase="running",this.dirty=!1;const o=Date.now(),r=o-this.askedAt,s=this.spawnImpl(t,["--noEmit","--pretty","false","-p",this.shimPath],{cwd:this.opts.workspaceRoot,stdio:["ignore","pipe","pipe"],env:{...process.env,GOMAXPROCS:process.env.GOMAXPROCS??String(this.opts.maxProcs??checkerMaxProcs())}});this.inflight=s;let i="",n=!1;const l=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{n=!0,s.kill("SIGKILL")},l);u.unref?.(),s.stdout?.on("data",c=>{i+=c.toString()}),s.stderr?.on("data",c=>{i+=c.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.publish({output:"",durationMs:Date.now()-o,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(c,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-o,f=(d/1e3).toFixed(1),m=r>=1e3?`${f}s, waited ${(r/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(n){const k=`the check was killed after ${Math.round(l/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${k} \u2014 no verdict.`),this.publish({output:"",durationMs:d,toolError:k}),this.finish(s)}if(p)return this.finish(s);if(c===0)return console.log(`[tsgo] ${this.opts.appName}: types clean (${m})`),this.publish({output:"",durationMs:d}),this.finish(s);i.trim()&&process.stdout.write(i.endsWith(`
|
|
3
|
+
`)?i:`${i}
|
|
4
|
+
`);const g=(i.match(/error TS\d+/g)??[]).length;console.log(`[tsgo] ${this.opts.appName}: ${g} type error${g===1?"":"s"} (${m})`),this.publish({output:i,durationMs:d,...g===0?{toolError:`tsgo exited ${c} with no diagnostics: ${firstLine(i)}`}:{}}),this.finish(s)})}finish(t){this.inflight===t&&(this.inflight=null),this.leave(),this.dirty&&!this.disposed&&(this.dirty=!1,this.request())}leave(){this.phase="idle",this.ticket?.release(),this.ticket=null}dispose(){this.disposed||(this.disposed=!0,livePlugins.delete(this),this.timer&&clearTimeout(this.timer),this.inflight?.kill("SIGKILL"),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
|
|
5
|
+
`)[0]?.slice(0,300)??""}h(firstLine,"firstLine"),a(firstLine,"firstLine");
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface QueueTicket {
|
|
2
|
+
/** Give the slot back, or leave the line. Idempotent. */
|
|
3
|
+
release(): void;
|
|
4
|
+
}
|
|
5
|
+
/** Join the line; `onTurn` is called once, when this caller may run. */
|
|
6
|
+
export type TypecheckQueue = (label: string, onTurn: () => void) => QueueTicket;
|
|
7
|
+
export interface MachineQueueOptions {
|
|
8
|
+
dir?: string;
|
|
9
|
+
slots?: number;
|
|
10
|
+
/** How often a waiter looks again. */
|
|
11
|
+
pollMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* How long a new ticket waits before its first look. A ticket created in the same clock tick as
|
|
14
|
+
* ours may not be listed yet; after this pause it is, so two newcomers can never both see
|
|
15
|
+
* themselves first.
|
|
16
|
+
*/
|
|
17
|
+
settleMs?: number;
|
|
18
|
+
/** A ticket older than this is treated as abandoned whatever its pid says (a reused pid looks alive). */
|
|
19
|
+
maxAgeMs?: number;
|
|
20
|
+
pid?: number;
|
|
21
|
+
isAlive?: (pid: number) => boolean;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
/** `~/.lensmcp/typecheck-queue` — machine-wide on purpose; `LENSMCP_HOME` moves it (tests, sandboxes). */
|
|
25
|
+
export declare function typecheckQueueDir(env?: NodeJS.ProcessEnv): string;
|
|
26
|
+
/** `LENSMCP_TYPECHECK_CONCURRENCY`, default ONE. Anything unreadable is one, never unlimited. */
|
|
27
|
+
export declare function typecheckSlots(env?: NodeJS.ProcessEnv): number;
|
|
28
|
+
/**
|
|
29
|
+
* The line, oldest first. A ticket whose owner is dead — or that has sat past `maxAgeMs` — is
|
|
30
|
+
* deleted on the way, so a crashed service never holds a slot.
|
|
31
|
+
*/
|
|
32
|
+
export declare function liveTickets(dir: string, options?: Pick<MachineQueueOptions, 'isAlive' | 'now' | 'maxAgeMs'>): string[];
|
|
33
|
+
/** The real queue. Every option has a production default; they are parameters for the spec. */
|
|
34
|
+
export declare function machineTypecheckQueue(options?: MachineQueueOptions): TypecheckQueue;
|
|
35
|
+
/** No queue: run at once. For specs of the plugin's own logic, and for nothing else. */
|
|
36
|
+
export declare const immediateQueue: TypecheckQueue;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var T=Object.defineProperty;var c=(e,t)=>T(e,"name",{value:t,configurable:!0});var E=Object.defineProperty,r=c((e,t)=>E(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.immediateQueue=void 0,exports.typecheckQueueDir=typecheckQueueDir,exports.typecheckSlots=typecheckSlots,exports.liveTickets=liveTickets,exports.machineTypecheckQueue=machineTypecheckQueue;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),TICKET_SUFFIX=".ticket",DEFAULT_POLL_MS=250,DEFAULT_SETTLE_MS=120,DEFAULT_MAX_AGE_MS=15*6e4,MAX_SLOTS=8;function typecheckQueueDir(e=process.env){return path.join(e.LENSMCP_HOME||os.homedir(),".lensmcp","typecheck-queue")}c(typecheckQueueDir,"typecheckQueueDir"),r(typecheckQueueDir,"typecheckQueueDir");function typecheckSlots(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_CONCURRENCY);return Number.isInteger(t)&&t>=1?Math.min(t,MAX_SLOTS):1}c(typecheckSlots,"typecheckSlots"),r(typecheckSlots,"typecheckSlots");function pidAlive(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}c(pidAlive,"pidAlive"),r(pidAlive,"pidAlive");const ticketPid=r(e=>{const t=Number(e.split("-")[0]);return Number.isInteger(t)&&t>0?t:void 0},"ticketPid");function liveTickets(e,t={}){const h=t.isAlive??pidAlive,S=(t.now??Date.now)(),m=t.maxAgeMs??DEFAULT_MAX_AGE_MS;let s;try{s=fs.readdirSync(e).filter(i=>i.endsWith(TICKET_SUFFIX))}catch{return[]}const u=[];for(const i of s){const n=path.join(e,i);let o;try{o=fs.statSync(n,{bigint:!0})}catch{continue}const a=ticketPid(i);if(a===void 0||!h(a)||S-Number(o.mtimeMs)>m){try{fs.unlinkSync(n)}catch{}continue}u.push({name:i,at:o.mtimeNs})}return u.sort((i,n)=>i.at<n.at?-1:i.at>n.at?1:i.name<n.name?-1:i.name>n.name?1:0),u.map(i=>i.name)}c(liveTickets,"liveTickets"),r(liveTickets,"liveTickets");let ticketSeq=0;function machineTypecheckQueue(e={}){const t=e.dir??typecheckQueueDir(),h=Math.max(1,e.slots??typecheckSlots()),S=e.pollMs??DEFAULT_POLL_MS,m=e.settleMs??DEFAULT_SETTLE_MS,s=e.pid??process.pid,u=r(()=>liveTickets(t,e),"scan");return(i,n)=>{const o=`${s}-${(ticketSeq++).toString(36)}-${Math.random().toString(36).slice(2,8)}${TICKET_SUFFIX}`,a=path.join(t,o);let l=!1,y=!1,p;const f=r(()=>{l||y||(y=!0,n())},"turn"),_=r(()=>{try{return fs.mkdirSync(t,{recursive:!0}),fs.writeFileSync(a,JSON.stringify({pid:s,label:i,at:(e.now??Date.now)()})),!0}catch{return!1}},"write"),k=r(d=>{p=setTimeout(M,d),p.unref?.()},"later");function M(){if(l||y)return;const d=u().indexOf(o);if(d===-1)return _()?k(m):f();if(d<h)return f();k(S)}return c(M,"k"),r(M,"look"),_()?(k(m),{release(){if(!l){l=!0,p&&clearTimeout(p);try{fs.unlinkSync(a)}catch{}}}}):(f(),{release(){l=!0}})}}c(machineTypecheckQueue,"machineTypecheckQueue"),r(machineTypecheckQueue,"machineTypecheckQueue");const immediateQueue=r((e,t)=>(t(),{release(){}}),"immediateQueue");exports.immediateQueue=immediateQueue;
|