@lensmcp/cluster 1.25.39 → 1.25.41

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/config-files.d.ts CHANGED
@@ -31,4 +31,24 @@ export declare class ConfigFilesPlugin {
31
31
  constructor(options: ConfigFilesPluginOptions);
32
32
  apply(compiler: ConfigFilesCompiler): void;
33
33
  }
34
+ /** How often {@link watchConfigFiles} reads the files' stats. */
35
+ export declare const CONFIG_POLL_MS = 1000;
36
+ export interface WatchConfigFilesOptions {
37
+ /** The files to watch — read again after every change, so a chain that grows or shrinks is followed. */
38
+ files(): Iterable<string>;
39
+ /** Some of them changed or went, with the ones that did. */
40
+ changed(paths: string[]): void;
41
+ /** Default {@link CONFIG_POLL_MS}. */
42
+ intervalMs?: number;
43
+ }
44
+ export interface ConfigFilesWatcher {
45
+ close(): void;
46
+ }
47
+ /**
48
+ * Watches configuration files for a process that has no compiler to watch them with (issues/closed/foodguard-115): a
49
+ * service built by the build host type-checks in its own process, and its checker read the tsconfig chain when it
50
+ * started. The files are polled by their stats, not watched with `fs.watch`: an editor saves by renaming a new file
51
+ * over the old one, and a watch on the old file sees nothing after that. A handful of files a second costs nothing.
52
+ */
53
+ export declare function watchConfigFiles(options: WatchConfigFilesOptions): ConfigFilesWatcher;
34
54
  export {};
package/config-files.js CHANGED
@@ -1 +1 @@
1
- "use strict";var a=Object.defineProperty;var s=(e,o)=>a(e,"name",{value:o,configurable:!0});var l=Object.defineProperty,t=s((e,o)=>l(e,"name",{value:o,configurable:!0}),"t");Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConfigFilesPlugin=void 0,exports.configFilesOf=configFilesOf;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),workspace_paths_1=require("./workspace-paths");function configFilesOf(e){const o=((0,workspace_paths_1.readWorkspacePaths)(e.tsConfig,e.workspaceRoot)?.configFiles??[path.resolve(e.workspaceRoot,e.tsConfig)]).filter(i=>!i.split(path.sep).includes("node_modules"));return e.webpackConfigPath&&o.push(path.resolve(e.appRoot,e.webpackConfigPath)),[...new Set(o)]}s(configFilesOf,"configFilesOf"),t(configFilesOf,"configFilesOf");class ConfigFilesPlugin{static{s(this,"ConfigFilesPlugin")}static{t(this,"ConfigFilesPlugin")}constructor(o){this.options=o}apply(o){o.hooks.afterCompile.tap("LensmcpConfigFiles",i=>{for(const n of this.options.files())i.fileDependencies.add(n)}),o.hooks.watchRun.tap("LensmcpConfigFiles",i=>{if(!i.modifiedFiles&&!i.removedFiles)return;const n=new Set(this.options.files()),r=[...i.modifiedFiles??[],...i.removedFiles??[]].filter(p=>n.has(p));r.length>0&&this.options.changed([...new Set(r)])})}}exports.ConfigFilesPlugin=ConfigFilesPlugin;
1
+ "use strict";var F=Object.defineProperty;var c=(e,i)=>F(e,"name",{value:i,configurable:!0});var d=Object.defineProperty,l=c((e,i)=>d(e,"name",{value:i,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.CONFIG_POLL_MS=exports.ConfigFilesPlugin=void 0,exports.configFilesOf=configFilesOf,exports.watchConfigFiles=watchConfigFiles;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),workspace_paths_1=require("./workspace-paths");function configFilesOf(e){const i=((0,workspace_paths_1.readWorkspacePaths)(e.tsConfig,e.workspaceRoot)?.configFiles??[path.resolve(e.workspaceRoot,e.tsConfig)]).filter(s=>!s.split(path.sep).includes("node_modules"));return e.webpackConfigPath&&i.push(path.resolve(e.appRoot,e.webpackConfigPath)),[...new Set(i)]}c(configFilesOf,"configFilesOf"),l(configFilesOf,"configFilesOf");class ConfigFilesPlugin{static{c(this,"ConfigFilesPlugin")}static{l(this,"ConfigFilesPlugin")}constructor(i){this.options=i}apply(i){i.hooks.afterCompile.tap("LensmcpConfigFiles",s=>{for(const n of this.options.files())s.fileDependencies.add(n)}),i.hooks.watchRun.tap("LensmcpConfigFiles",s=>{if(!s.modifiedFiles&&!s.removedFiles)return;const n=new Set(this.options.files()),t=[...s.modifiedFiles??[],...s.removedFiles??[]].filter(f=>n.has(f));t.length>0&&this.options.changed([...new Set(t)])})}}exports.ConfigFilesPlugin=ConfigFilesPlugin,exports.CONFIG_POLL_MS=1e3;const CONFIG_SETTLE_MS=100;function watchConfigFiles(e){const i=e.intervalMs??exports.CONFIG_POLL_MS,s=new Map,n=new Set;let t,f=!1;const g=l(()=>{if(t=void 0,f||n.size===0)return;const r=[...n].sort();n.clear(),h(),e.changed(r)},"flush"),h=l(()=>{let r;try{r=new Set(e.files())}catch{return}for(const[o,a]of s)r.has(o)||(fs.unwatchFile(o,a),s.delete(o));for(const o of r){if(s.has(o))continue;const a=l((p,u)=>{p.mtimeMs===u.mtimeMs&&p.size===u.size&&p.ino===u.ino||(n.add(o),t||(t=setTimeout(g,CONFIG_SETTLE_MS),t.unref?.()))},"listener");fs.watchFile(o,{persistent:!1,interval:i},a),s.set(o,a)}},"sync");return h(),{close(){f=!0,t&&clearTimeout(t);for(const[r,o]of s)fs.unwatchFile(r,o);s.clear()}}}c(watchConfigFiles,"watchConfigFiles"),l(watchConfigFiles,"watchConfigFiles");
@@ -8,7 +8,7 @@
8
8
  * `/webpack/watching` answers 404 and nothing is said.
9
9
  *
10
10
  * Driven by {@link DevServerReloadPlugin} on a service's own compiler, and by the serve executor itself when the
11
- * service is compiled in a shared compilation (shared-build/attach.ts, issues/open/pod-memory-05).
11
+ * service is compiled in a shared compilation (shared-build/attach.ts, issues/closed/pod-memory-05).
12
12
  */
13
13
  export declare class DevserverNotifier {
14
14
  private base;
@@ -1,2 +1,2 @@
1
1
  "use strict";var q=Object.defineProperty;var h=(c,t)=>q(c,"name",{value:t,configurable:!0});var x=Object.defineProperty,l=h((c,t)=>x(c,"name",{value:t,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.instanceSummary=instanceSummary,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"),guest_graph_1=require("./guest-graph"),guest_replay_1=require("./guest-replay"),registration_guards_1=require("./registration-guards"),scope_1=require("./scope"),awake_1=require("./awake"),warm_start_1=require("./warm-start"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),workspace_middleware_1=require("./workspace-middleware");function instanceSummary(c){const t=c?.instance;return t?{instance:t.name,baseKey:t.baseKey,apex:t.apex,baseApex:t.baseApex,...t.frozen?{frozen:!0}:{}}:{}}h(instanceSummary,"instanceSummary"),l(instanceSummary,"instanceSummary");function controlSocketPath(){const c=process.env.LENSMCP_HOME||os.homedir();return path.join(c,".lensmcp","control.sock")}h(controlSocketPath,"controlSocketPath"),l(controlSocketPath,"controlSocketPath");const CONTROL_BODY_BYTES=1e6;class BodyTooLarge extends Error{static{h(this,"BodyTooLarge")}static{l(this,"BodyTooLarge")}constructor(t){super(`body too large (over ${t} bytes)`),this.limit=t}}exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(c){const t=c();return()=>t}h(versionAtLoad,"versionAtLoad"),l(versionAtLoad,"versionAtLoad");function readClusterVersion(c){let t=c;for(let f=0;f<6;f++){const k=path.join(t,"package.json");if(fs.existsSync(k))try{const v=JSON.parse(fs.readFileSync(k,"utf8"));if(v.name==="@lensmcp/cluster"&&typeof v.version=="string")return v.version}catch{}const i=path.dirname(t);if(i===t)break;t=i}}h(readClusterVersion,"readClusterVersion"),l(readClusterVersion,"readClusterVersion");function readJsonBody(c,t=CONTROL_BODY_BYTES){return new Promise((f,k)=>{const i=[];let v=0,_=!1;c.on("data",S=>{if(!_){if(v+=S.length,v>t){_=!0,i.length=0,k(new BodyTooLarge(t));return}i.push(S)}}),c.on("end",()=>{if(_)return;const S=Buffer.concat(i).toString("utf8");try{f({value:S?JSON.parse(S):{},bytes:v})}catch(T){k(T)}}),c.on("error",k)})}h(readJsonBody,"readJsonBody"),l(readJsonBody,"readJsonBody");function graphShare(c,t){return t-Buffer.byteLength(JSON.stringify({...c,projectGraph:null}))}h(graphShare,"graphShare"),l(graphShare,"graphShare");function graphVerdict(c){return c?c.refused===void 0?{forwarded:!0,accepted:!0}:{forwarded:!0,accepted:!1,reason:c.refused}:{forwarded:!1,accepted:!1}}h(graphVerdict,"graphVerdict"),l(graphVerdict,"graphVerdict");function ensureOwnerOnlyDir(c){try{fs.mkdirSync(c,{recursive:!0,mode:448})}catch{}try{const t=fs.lstatSync(c);return t.isDirectory()?((t.mode&63)!==0&&fs.chmodSync(c,448),!0):!1}catch{return!1}}h(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),l(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(c){const{rt:t}=c,f=controlSocketPath(),k=path.dirname(f);ensureOwnerOnlyDir(k)||console.warn(`[gateway] control home ${k} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(f)}catch{}const i=l((e,s,u)=>{e.writeHead(s,{"content-type":"application/json"}),e.end(JSON.stringify(u))},"send"),v=l(async(e,s,u=!1)=>{if(!e.wsKey||!e.root)return{status:400,json:{error:"wsKey and root are required"}};const r=e.projects??{},o=(0,discovery_1.projectSourceRoots)(e.root,r,e.projectDependencies),a=(0,scope_1.readLensScope)(e.root),d=a.key===e.wsKey,y=d&&a.sockNs!==e.wsKey?a.sockNs:void 0,{routes:p,services:w,instance:m}=(0,discovery_1.discoverRoutes)(e.root,r,void 0,e.wsKey,o,y,d?a.instance:void 0),b=(0,workspace_middleware_1.findGatewayMiddleware)(e.root,r),$={wsKey:e.wsKey,...y?{sockNs:y}:{},root:e.root,routes:p,services:w,...m?{instance:m}:{},...b?{middleware:b}:{}},K=(0,registration_guards_1.refuseRegistration)(t.registry,$);if(K)return console.warn(`[gateway] refused to register '${e.wsKey}' from ${e.root}: ${K.error}`),{status:409,json:K};if(u)return m?{status:200,json:{wsKey:e.wsKey,root:e.root,...instanceSummary($),env:m.env}}:{status:400,json:{error:`'${e.wsKey}' has no resolvable instance configuration`}};try{const n=(0,service_keys_1.adoptServiceKeys)(t,e.root,w,e.wsKey);n.added.length>0&&console.log(`[gateway] adopted east-west keys for ${e.wsKey}: ${n.added.join(", ")}`),n.regenerated.length>0&&console.warn(`[gateway] ${e.root} carried another workspace's east-west keys (a copied key file) \u2014 new keys for ${e.wsKey}: ${n.regenerated.join(", ")}`)}catch(n){console.error(`[gateway] could not adopt east-west keys for ${e.wsKey}: ${n.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(e.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${e.wsKey}') is served by the daemon at ${t.root} (pid ${process.pid}); its gateway-level log is ${path.join(t.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
2
- `)}catch{}let j;if(t.graphStores){const n={root:e.root,daemonRoot:t.root,otherRoots:t.registry.keys().filter(g=>g!==e.wsKey).flatMap(g=>t.registry.get(g)?.root??[])};j=e.projectGraph===void 0?void 0:(0,guest_graph_1.admitForwardedGraph)(e.projectGraph,{...n,bytes:graphShare(e,s)}),j?.refused!==void 0&&console.warn(`[gateway] refused the project graph '${e.wsKey}' forwarded from ${e.root}: ${j.refused} \u2014 its services start through \`nx run\` until the gateway computes it`),(0,registration_guards_1.sameRoot)(e.root,t.root)||t.graphStores.set((0,guest_graph_1.createGuestGraphStore)({wsKey:e.wsKey,root:e.root,...j?{admission:j}:{},context:n}))}return t.registry.register($),await c.onRegister?.($),t.rebuildRoutes(),t.root&&!(0,registration_guards_1.sameRoot)(e.root,t.root)&&(0,guest_replay_1.recordGuest)({wsKey:e.wsKey,root:e.root,projects:r,...e.projectDependencies?{projectDependencies:e.projectDependencies}:{},daemonRoot:t.root,registeredAt:new Date().toISOString()}),{status:200,json:{ok:!0,wsKey:e.wsKey,routes:p.length,services:w.length,workspaces:t.registry.keys(),...t.graphStores?{projectGraph:graphVerdict(j)}:{}}}},"register"),_=l((e,s)=>{const u=t.registry.get(e);if(!u)return{error:`no workspace '${e}' is registered`,workspaces:t.registry.keys()};const r=u.services.find(o=>o.project===s);return r?{svc:r}:{error:`'${e}' has no service '${s}'`,services:u.services.map(o=>o.project)}},"findService"),S=l(e=>{let s=(e.stoppingUntil??0)>Date.now();if(e.stoppingPid)try{process.kill(e.stoppingPid,0),s=!0}catch{e.stoppingPid=void 0}return{service:e.project,state:e.state,stopped:e.manualStopped===!0&&!s,ready:!e.manualStopped&&e.state==="up"&&e.pool.socks.length>0,busy:s||e.actionStarting===!0||!!e.rolling||!!(e.restart&&!e.restart.done),failure:e.actionFailure??(!e.manualStopped&&e.restart?.done?.ok===!1?e.restart.done.error:void 0)}},"serviceView"),T=l((e,s)=>(e.manualStopped=!1,e.actionFailure=void 0,e.restart=void 0,e.actionStarting=!0,s(e).then(u=>(!u&&!e.manualStopped&&(e.actionFailure="Service did not become ready"),u),u=>(e.actionFailure=String(u).slice(0,500),!1)).finally(()=>{e.actionStarting=!1})),"startService"),O=l((e,s)=>({project:e.project,wsKey:e.wsKey,host:e.decl.host,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{},...(0,awake_1.idleFields)(e,(0,warm_start_1.serviceIdleSec)(t,e),s)}),"statusRow");let P=Promise.resolve();const A=c.startTurn??(e=>{const s=P.then(e);return P=s.catch(()=>{}),s}),E=l(e=>({wsKey:e.wsKey,service:e.project,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{}}),"restartView"),G=http.createServer((e,s)=>{(async()=>{try{const u=(e.url??"").split("?")[0];if(e.method==="GET"&&u==="/list")return i(s,200,{workspaces:t.registry.keys().map(r=>{const o=t.registry.get(r);return{key:r,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0,...instanceSummary(o)}})});if(e.method==="GET"&&u==="/status"){const r=(0,exports.clusterVersion)();return i(s,200,{daemon:{pid:process.pid,wsKey:t.wsKey,...r?{version:r}:{}},workspaces:t.registry.keys().map(o=>{const a=t.registry.get(o);return{key:o,root:a?.root,routes:a?.routes.length??0,services:a?.services.length??0,isDaemon:o===t.wsKey,...instanceSummary(a)}}),services:t.registry.allServices().map(o=>O(o,Date.now()))})}if(e.method==="GET"&&u==="/instance-env"){const r=new URL(e.url??"/","http://control").searchParams.get("wsKey")??"",o=t.registry.get(r);return o?o.instance?i(s,200,{wsKey:r,root:o.root,...instanceSummary(o),env:o.instance.env}):i(s,404,{error:`'${r}' is not an instance`}):i(s,404,{error:`no workspace '${r}' is registered`})}if(e.method==="POST"&&u==="/instance-env"){const{value:r,bytes:o}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),a=await v(r,o,!0);return i(s,a.status,a.json)}if(u==="/service"&&(e.method==="GET"||e.method==="POST")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{};if(typeof r.wsKey!="string"||typeof r.service!="string")return i(s,400,{error:"wsKey and service are required"});const o=_(r.wsKey,r.service);if("error"in o)return i(s,404,o);const a=o.svc,d=l(()=>S(a),"view");if(e.method==="GET")return i(s,200,d());const y=r.action;if(!["start","stop","restart"].includes(String(y)))return i(s,400,{error:"action must be start, stop or restart"});if(d().busy&&!(y==="stop"&&a.actionStarting&&!a.rolling))return i(s,409,{error:"A service lifecycle operation is already in progress"});if(y==="stop")return!a.proc&&a.pool.socks.length&&!a.manualStopped?i(s,409,{error:"This gateway does not own the adopted service process; stop it with its owner"}):(a.manualStopped=!0,a.stoppingPid=a.proc?.pid,a.stoppingUntil=Date.now()+(a.proc?1600:0),c.killService(a,"workspace action stop"),a.state="down",i(s,202,d()));if(y==="restart"){if(!c.restartService)return i(s,501,{error:"This gateway cannot restart services"});const w=c.restartService(a,{hard:r.hard===!0,reason:"workspace action restart"});return"refused"in w?i(s,409,{error:w.refused}):i(s,202,d())}if(!c.ensureService)return i(s,501,{error:"This gateway cannot start services"});const p=a.state!=="up"&&!a.proc;return T(a,c.ensureService),i(s,202,{...d(),started:p})}if(e.method==="POST"&&u==="/awake"){const r=(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"";if(!o)return i(s,400,{error:"wsKey is required"});const a=typeof r.forSec=="number"?r.forSec:NaN;if(!Number.isFinite(a)||a<0)return i(s,400,{error:"forSec is required: the seconds to hold them awake, 0 to release the hold"});if(a>awake_1.MAX_AWAKE_SEC)return i(s,400,{error:`forSec ${a} is over the ${awake_1.MAX_AWAKE_SEC} s (4 h) a hold may last \u2014 ask again before it ends to keep them longer`,maxSec:awake_1.MAX_AWAKE_SEC});const d=r.services;if(d!==void 0&&(!Array.isArray(d)||d.length===0||!d.every(n=>typeof n=="string"&&n.length>0)))return i(s,400,{error:"services, when given, names one service or more"});const y=t.registry.get(o);if(!y)return i(s,404,{error:`no workspace '${o}' is registered`,workspaces:t.registry.keys()});const p=[];for(const n of d??[]){const g=_(o,n);if("error"in g)return i(s,404,g);p.includes(g.svc)||p.push(g.svc)}const w=p.length>0;w||p.push(...y.services);const m=Date.now();if(a===0){for(const n of p)(n.awakeUntil??0)<=m||(n.awakeUntil=m,c.emitService?.(n,"info",`awake hold released: ${n.project}`,{kind:"service-awake",project:n.project,until:null,released:!0}));return i(s,202,{ok:!0,wsKey:o,forSec:0,released:!0,services:p.map(n=>O(n,m))})}const b=m+Math.round(a*1e3),$=[],K=l((n,g)=>({at:m,outcome:n,...g?{reason:g}:{}}),"asked");for(const n of p)n.awakeUntil=Math.max(n.awakeUntil??0,b),n.state==="up"?n.wake=K("up"):S(n).busy?n.wake=K("skipped","a stop, start or restart of it is under way \u2014 `lensmcp instance status` shows how it ends"):n.manualStopped&&!w?n.wake=K("skipped","stopped by hand \u2014 name it to start it"):c.ensureService?(n.manualStopped=!1,n.wake=K("queued"),$.push({svc:n,wake:n.wake})):n.wake=K("failed","this gateway cannot start services"),c.emitService?.(n,"info",`awake until ${new Date(n.awakeUntil).toISOString()}: ${n.project}`,{kind:"service-awake",project:n.project,until:n.awakeUntil,forSec:a,wake:n.wake.outcome});const j=c.ensureService;return $.length>0&&j&&A(()=>(0,awake_1.wakeServices)($,{start:l(n=>T(n,j),"start"),available:c.available??(()=>(0,warm_start_1.availableMemory)()),minFreeBytes:c.minFreeBytes??(0,warm_start_1.warmMinFreeBytes)(),current:l(n=>!t.stopped?.()&&!!t.registry.get(n.wsKey)?.services.includes(n),"current"),busy:l(n=>S(n).busy,"busy"),emit:l((n,g,D,R)=>c.emitService?.(n,g,D,R),"emit"),log:l(n=>console.log(n),"log")})).catch(n=>console.error(`[gateway] wake of ${o} failed:`,n)),i(s,202,{ok:!0,wsKey:o,forSec:a,until:b,services:p.map(n=>O(n,m))})}if(u==="/restart"&&(e.method==="POST"||e.method==="GET")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"",a=typeof r.service=="string"?r.service:"";if(!o||!a)return i(s,400,{error:"wsKey and service are required"});const d=_(o,a);if("error"in d)return i(s,404,d);const y=d.svc;if(e.method==="GET")return i(s,200,E(y));if(!c.restartService)return i(s,501,{error:"this gateway cannot restart a service"});const p=typeof r.reason=="string"&&r.reason.trim()?r.reason.trim().slice(0,200):"asked for",w=c.restartService(y,{hard:r.hard===!0,reason:p});return"refused"in w?i(s,409,{error:w.refused,...E(y)}):i(s,202,{ok:!0,already:w.already,...E(y)})}if(e.method==="GET"&&u==="/identities"||e.method==="POST"&&u==="/act"){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"";if(!o)return i(s,400,{error:"wsKey is required"});if(!t.registry.get(o)&&o!==t.wsKey)return i(s,404,{error:`no workspace '${o}' is registered`,workspaces:t.registry.keys()});if(!c.act)return i(s,501,{error:"acting as a signed-in user is turned off on this gateway (LENSMCP_GW_ACT=0)"});if(e.method==="GET")return i(s,200,{wsKey:o,identities:c.act.list(o)});const a=l(w=>typeof r[w]=="string"&&r[w].trim()?r[w].trim().slice(0,256):void 0,"pick"),d=a("user"),y=a("tenant"),p=c.act.mint(o,{...d?{user:d}:{},...y?{tenant:y}:{}});return"error"in p?i(s,404,{wsKey:o,...p}):i(s,200,{wsKey:o,...p})}if(e.method==="POST"&&u==="/register"){const{value:r,bytes:o}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),a=await v(r,o);return i(s,a.status,a.json)}if(e.method==="POST"&&u==="/unregister"){const r=(await readJsonBody(e)).value;if(!r.wsKey)return i(s,400,{error:"wsKey is required"});const o=(0,registration_guards_1.refuseUnregistration)(t.registry,{wsKey:r.wsKey,...r.root?{root:r.root}:{}},t.wsKey);if(o)return console.warn(`[gateway] refused to unregister '${r.wsKey}'${r.root?` for ${r.root}`:""}: ${o.error}`),i(s,409,o);const a=t.registry.unregister(r.wsKey);if(a){for(const d of a.services){const y=(0,service_keys_1.serviceKeyId)(d.wsKey??r.wsKey,d.project),p=t.serviceKeys[y];p&&(delete t.serviceKeys[y],t.keyToProject.delete(p),t.keyToWorkspace?.delete(p))}for(const d of a.services)c.killService(d,"workspace unregistered");t.graphStores?.delete(a.root),(0,guest_replay_1.forgetGuest)(r.wsKey),c.onUnregister?.(a)}return t.rebuildRoutes(),i(s,200,{ok:!0,removed:!!a,workspaces:t.registry.keys()})}i(s,404,{error:"not found"})}catch(u){if(u instanceof BodyTooLarge)return e.resume(),i(s,413,{error:u.message});i(s,500,{error:u.message})}})()});return G.listen(f,()=>{try{fs.chmodSync(f,384)}catch(e){console.warn(`[gateway] could not restrict ${f} to 0600:`,e.message)}}),{socketPath:f,async replay(){const{replay:e,gone:s}=(0,guest_replay_1.guestsToReplay)(t.root,(0,guest_replay_1.readGuests)()),u={replayed:[],refused:[],gone:s.map(r=>r.wsKey)};for(const r of s)(0,guest_replay_1.forgetGuest)(r.wsKey),console.log(`[gateway] not re-hosting '${r.wsKey}': ${r.root} is no longer a workspace \u2014 forgotten`);for(const r of e)if(!t.registry.has(r.wsKey))try{const o=await v({wsKey:r.wsKey,root:r.root,projects:r.projects,...r.projectDependencies?{projectDependencies:r.projectDependencies}:{}},0);o.status===200?(u.replayed.push(r.wsKey),console.log(`[gateway] re-hosted '${r.wsKey}' (${r.root}) \u2014 registered before this daemon restarted`)):(u.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${JSON.stringify(o.json)} \u2014 \`lensmcp gateway start\` there`))}catch(o){u.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${o.message}`)}return u},close:l(()=>{G.close();try{fs.unlinkSync(f)}catch{}},"close")}}h(startControlServer,"startControlServer"),l(startControlServer,"startControlServer");
2
+ `)}catch{}let j;if(t.graphStores){const n={root:e.root,daemonRoot:t.root,otherRoots:t.registry.keys().filter(g=>g!==e.wsKey).flatMap(g=>t.registry.get(g)?.root??[])};j=e.projectGraph===void 0?void 0:(0,guest_graph_1.admitForwardedGraph)(e.projectGraph,{...n,bytes:graphShare(e,s)}),j?.refused!==void 0&&console.warn(`[gateway] refused the project graph '${e.wsKey}' forwarded from ${e.root}: ${j.refused} \u2014 its services start through \`nx run\` until the gateway computes it`),(0,registration_guards_1.sameRoot)(e.root,t.root)||t.graphStores.set((0,guest_graph_1.createGuestGraphStore)({wsKey:e.wsKey,root:e.root,...j?{admission:j}:{},context:n}))}return t.registry.register($),await c.onRegister?.($),t.rebuildRoutes(),t.root&&!(0,registration_guards_1.sameRoot)(e.root,t.root)&&(0,guest_replay_1.recordGuest)({wsKey:e.wsKey,root:e.root,projects:r,...e.projectDependencies?{projectDependencies:e.projectDependencies}:{},daemonRoot:t.root,registeredAt:new Date().toISOString()}),{status:200,json:{ok:!0,wsKey:e.wsKey,routes:p.length,services:w.length,workspaces:t.registry.keys(),...t.graphStores?{projectGraph:graphVerdict(j)}:{}}}},"register"),_=l((e,s)=>{const u=t.registry.get(e);if(!u)return{error:`no workspace '${e}' is registered`,workspaces:t.registry.keys()};const r=u.services.find(o=>o.project===s);return r?{svc:r}:{error:`'${e}' has no service '${s}'`,services:u.services.map(o=>o.project)}},"findService"),S=l(e=>{let s=(e.stoppingUntil??0)>Date.now();if(e.stoppingPid)try{process.kill(e.stoppingPid,0),s=!0}catch{e.stoppingPid=void 0}return{service:e.project,state:e.state,stopped:e.manualStopped===!0&&!s,ready:!e.manualStopped&&e.state==="up"&&e.pool.socks.length>0,busy:s||e.actionStarting===!0||!!e.rolling||!!(e.restart&&!e.restart.done),failure:e.actionFailure??(!e.manualStopped&&e.restart?.done?.ok===!1?e.restart.done.error:void 0)}},"serviceView"),T=l((e,s)=>(e.manualStopped=!1,e.actionFailure=void 0,e.restart=void 0,e.actionStarting=!0,s(e).then(u=>(!u&&!e.manualStopped&&(e.actionFailure="Service did not become ready"),u),u=>(e.actionFailure=String(u).slice(0,500),!1)).finally(()=>{e.actionStarting=!1})),"startService"),O=l((e,s)=>({project:e.project,wsKey:e.wsKey,host:e.decl.host,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{},...(0,awake_1.idleFields)(e,(0,warm_start_1.serviceIdleSec)(t,e),s)}),"statusRow");let P=Promise.resolve();const A=c.startTurn??(e=>{const s=P.then(e);return P=s.catch(()=>{}),s}),E=l(e=>({wsKey:e.wsKey,service:e.project,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{}}),"restartView"),G=http.createServer((e,s)=>{(async()=>{try{const u=(e.url??"").split("?")[0];if(e.method==="GET"&&u==="/list")return i(s,200,{workspaces:t.registry.keys().map(r=>{const o=t.registry.get(r);return{key:r,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0,...instanceSummary(o)}})});if(e.method==="GET"&&u==="/status"){const r=(0,exports.clusterVersion)();return i(s,200,{daemon:{pid:process.pid,wsKey:t.wsKey,...r?{version:r}:{}},workspaces:t.registry.keys().map(o=>{const a=t.registry.get(o);return{key:o,root:a?.root,routes:a?.routes.length??0,services:a?.services.length??0,isDaemon:o===t.wsKey,...instanceSummary(a)}}),services:t.registry.allServices().map(o=>O(o,Date.now()))})}if(e.method==="GET"&&u==="/instance-env"){const r=new URL(e.url??"/","http://control").searchParams.get("wsKey")??"",o=t.registry.get(r);return o?o.instance?i(s,200,{wsKey:r,root:o.root,...instanceSummary(o),env:o.instance.env}):i(s,404,{error:`'${r}' is not an instance`}):i(s,404,{error:`no workspace '${r}' is registered`})}if(e.method==="POST"&&u==="/instance-env"){const{value:r,bytes:o}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),a=await v(r,o,!0);return i(s,a.status,a.json)}if(u==="/service"&&(e.method==="GET"||e.method==="POST")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{};if(typeof r.wsKey!="string"||typeof r.service!="string")return i(s,400,{error:"wsKey and service are required"});const o=_(r.wsKey,r.service);if("error"in o)return i(s,404,o);const a=o.svc,d=l(()=>S(a),"view");if(e.method==="GET")return i(s,200,d());const y=r.action;if(!["start","stop","restart"].includes(String(y)))return i(s,400,{error:"action must be start, stop or restart"});if(d().busy&&!(y==="stop"&&a.actionStarting&&!a.rolling))return i(s,409,{error:"A service lifecycle operation is already in progress"});if(y==="stop")return!a.proc&&a.pool.socks.length&&!a.manualStopped?i(s,409,{error:"This gateway does not own the adopted service process; stop it with its owner"}):(a.manualStopped=!0,a.stoppingPid=a.proc?.pid,a.stoppingUntil=Date.now()+(a.proc?1600:0),c.killService(a,"workspace action stop"),a.state="down",i(s,202,d()));if(y==="restart"){if(!c.restartService)return i(s,501,{error:"This gateway cannot restart services"});const w=c.restartService(a,{hard:r.hard===!0,reason:"workspace action restart"});return"refused"in w?i(s,409,{error:w.refused}):i(s,202,{...d(),restart:w.restart})}if(!c.ensureService)return i(s,501,{error:"This gateway cannot start services"});const p=a.state!=="up"&&!a.proc;return T(a,c.ensureService),i(s,202,{...d(),started:p})}if(e.method==="POST"&&u==="/awake"){const r=(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"";if(!o)return i(s,400,{error:"wsKey is required"});const a=typeof r.forSec=="number"?r.forSec:NaN;if(!Number.isFinite(a)||a<0)return i(s,400,{error:"forSec is required: the seconds to hold them awake, 0 to release the hold"});if(a>awake_1.MAX_AWAKE_SEC)return i(s,400,{error:`forSec ${a} is over the ${awake_1.MAX_AWAKE_SEC} s (4 h) a hold may last \u2014 ask again before it ends to keep them longer`,maxSec:awake_1.MAX_AWAKE_SEC});const d=r.services;if(d!==void 0&&(!Array.isArray(d)||d.length===0||!d.every(n=>typeof n=="string"&&n.length>0)))return i(s,400,{error:"services, when given, names one service or more"});const y=t.registry.get(o);if(!y)return i(s,404,{error:`no workspace '${o}' is registered`,workspaces:t.registry.keys()});const p=[];for(const n of d??[]){const g=_(o,n);if("error"in g)return i(s,404,g);p.includes(g.svc)||p.push(g.svc)}const w=p.length>0;w||p.push(...y.services);const m=Date.now();if(a===0){for(const n of p)(n.awakeUntil??0)<=m||(n.awakeUntil=m,c.emitService?.(n,"info",`awake hold released: ${n.project}`,{kind:"service-awake",project:n.project,until:null,released:!0}));return i(s,202,{ok:!0,wsKey:o,forSec:0,released:!0,services:p.map(n=>O(n,m))})}const b=m+Math.round(a*1e3),$=[],K=l((n,g)=>({at:m,outcome:n,...g?{reason:g}:{}}),"asked");for(const n of p)n.awakeUntil=Math.max(n.awakeUntil??0,b),n.state==="up"?n.wake=K("up"):S(n).busy?n.wake=K("skipped","a stop, start or restart of it is under way \u2014 `lensmcp instance status` shows how it ends"):n.manualStopped&&!w?n.wake=K("skipped","stopped by hand \u2014 name it to start it"):c.ensureService?(n.manualStopped=!1,n.wake=K("queued"),$.push({svc:n,wake:n.wake})):n.wake=K("failed","this gateway cannot start services"),c.emitService?.(n,"info",`awake until ${new Date(n.awakeUntil).toISOString()}: ${n.project}`,{kind:"service-awake",project:n.project,until:n.awakeUntil,forSec:a,wake:n.wake.outcome});const j=c.ensureService;return $.length>0&&j&&A(()=>(0,awake_1.wakeServices)($,{start:l(n=>T(n,j),"start"),available:c.available??(()=>(0,warm_start_1.availableMemory)()),minFreeBytes:c.minFreeBytes??(0,warm_start_1.warmMinFreeBytes)(),current:l(n=>!t.stopped?.()&&!!t.registry.get(n.wsKey)?.services.includes(n),"current"),busy:l(n=>S(n).busy,"busy"),emit:l((n,g,D,R)=>c.emitService?.(n,g,D,R),"emit"),log:l(n=>console.log(n),"log")})).catch(n=>console.error(`[gateway] wake of ${o} failed:`,n)),i(s,202,{ok:!0,wsKey:o,forSec:a,until:b,services:p.map(n=>O(n,m))})}if(u==="/restart"&&(e.method==="POST"||e.method==="GET")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"",a=typeof r.service=="string"?r.service:"";if(!o||!a)return i(s,400,{error:"wsKey and service are required"});const d=_(o,a);if("error"in d)return i(s,404,d);const y=d.svc;if(e.method==="GET")return i(s,200,E(y));if(!c.restartService)return i(s,501,{error:"this gateway cannot restart a service"});const p=typeof r.reason=="string"&&r.reason.trim()?r.reason.trim().slice(0,200):"asked for",w=c.restartService(y,{hard:r.hard===!0,reason:p});return"refused"in w?i(s,409,{error:w.refused,...E(y)}):i(s,202,{ok:!0,already:w.already,...E(y)})}if(e.method==="GET"&&u==="/identities"||e.method==="POST"&&u==="/act"){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},o=typeof r.wsKey=="string"?r.wsKey:"";if(!o)return i(s,400,{error:"wsKey is required"});if(!t.registry.get(o)&&o!==t.wsKey)return i(s,404,{error:`no workspace '${o}' is registered`,workspaces:t.registry.keys()});if(!c.act)return i(s,501,{error:"acting as a signed-in user is turned off on this gateway (LENSMCP_GW_ACT=0)"});if(e.method==="GET")return i(s,200,{wsKey:o,identities:c.act.list(o)});const a=l(w=>typeof r[w]=="string"&&r[w].trim()?r[w].trim().slice(0,256):void 0,"pick"),d=a("user"),y=a("tenant"),p=c.act.mint(o,{...d?{user:d}:{},...y?{tenant:y}:{}});return"error"in p?i(s,404,{wsKey:o,...p}):i(s,200,{wsKey:o,...p})}if(e.method==="POST"&&u==="/register"){const{value:r,bytes:o}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),a=await v(r,o);return i(s,a.status,a.json)}if(e.method==="POST"&&u==="/unregister"){const r=(await readJsonBody(e)).value;if(!r.wsKey)return i(s,400,{error:"wsKey is required"});const o=(0,registration_guards_1.refuseUnregistration)(t.registry,{wsKey:r.wsKey,...r.root?{root:r.root}:{}},t.wsKey);if(o)return console.warn(`[gateway] refused to unregister '${r.wsKey}'${r.root?` for ${r.root}`:""}: ${o.error}`),i(s,409,o);const a=t.registry.unregister(r.wsKey);if(a){for(const d of a.services){const y=(0,service_keys_1.serviceKeyId)(d.wsKey??r.wsKey,d.project),p=t.serviceKeys[y];p&&(delete t.serviceKeys[y],t.keyToProject.delete(p),t.keyToWorkspace?.delete(p))}for(const d of a.services)c.killService(d,"workspace unregistered");t.graphStores?.delete(a.root),(0,guest_replay_1.forgetGuest)(r.wsKey),c.onUnregister?.(a)}return t.rebuildRoutes(),i(s,200,{ok:!0,removed:!!a,workspaces:t.registry.keys()})}i(s,404,{error:"not found"})}catch(u){if(u instanceof BodyTooLarge)return e.resume(),i(s,413,{error:u.message});i(s,500,{error:u.message})}})()});return G.listen(f,()=>{try{fs.chmodSync(f,384)}catch(e){console.warn(`[gateway] could not restrict ${f} to 0600:`,e.message)}}),{socketPath:f,async replay(){const{replay:e,gone:s}=(0,guest_replay_1.guestsToReplay)(t.root,(0,guest_replay_1.readGuests)()),u={replayed:[],refused:[],gone:s.map(r=>r.wsKey)};for(const r of s)(0,guest_replay_1.forgetGuest)(r.wsKey),console.log(`[gateway] not re-hosting '${r.wsKey}': ${r.root} is no longer a workspace \u2014 forgotten`);for(const r of e)if(!t.registry.has(r.wsKey))try{const o=await v({wsKey:r.wsKey,root:r.root,projects:r.projects,...r.projectDependencies?{projectDependencies:r.projectDependencies}:{}},0);o.status===200?(u.replayed.push(r.wsKey),console.log(`[gateway] re-hosted '${r.wsKey}' (${r.root}) \u2014 registered before this daemon restarted`)):(u.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${JSON.stringify(o.json)} \u2014 \`lensmcp gateway start\` there`))}catch(o){u.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${o.message}`)}return u},close:l(()=>{G.close();try{fs.unlinkSync(f)}catch{}},"close")}}h(startControlServer,"startControlServer"),l(startControlServer,"startControlServer");
@@ -1 +1 @@
1
- "use strict";var A=Object.defineProperty;var h=(e,s)=>A(e,"name",{value:s,configurable:!0});var I=Object.defineProperty,p=h((e,s)=>I(e,"name",{value:s,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.RETIRING_SUFFIX=void 0,exports.stopRoutingTo=stopRoutingTo,exports.pickSock=pickSock,exports.lensPortFromUpstream=lensPortFromUpstream,exports.rewriteInstanceDecls=rewriteInstanceDecls,exports.discoverRoutes=discoverRoutes,exports.projectSourceRoots=projectSourceRoots;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")),manifest_1=require("../manifest"),pod_sock_1=require("../../../pod-sock"),instance_1=require("./instance"),scope_1=require("./scope");exports.RETIRING_SUFFIX=".retiring";function sockIdentity(e){try{const s=fs.statSync(e);return`${s.dev}:${s.ino}:${s.ctimeMs}`}catch{return}}h(sockIdentity,"sockIdentity"),p(sockIdentity,"sockIdentity");function stopRoutingTo(e,s=()=>!0){let a;try{a=fs.readdirSync(e.dir)}catch{a=[]}const r=a.filter(t=>(0,pod_sock_1.isPodSockName)(t)&&s(t)).map(t=>path.join(e.dir,t));if(r.length===0)return r;const l=e.retired??=new Map;for(const t of r){const d=sockIdentity(t);d!==void 0&&l.set(t,d)}const n=new Set(r);return e.socks=e.socks.filter(t=>!n.has(t)),e.idx=-1,r}h(stopRoutingTo,"stopRoutingTo"),p(stopRoutingTo,"stopRoutingTo");function stillRetired(e,s){const a=e.retired?.get(s);return a===void 0?!1:sockIdentity(s)===a?!0:(e.retired?.delete(s),!1)}h(stillRetired,"stillRetired"),p(stillRetired,"stillRetired");function pickSock(e,s=1500){const a=Date.now();if(a-e.scannedAt>s){try{const r=fs.readdirSync(e.dir),l=r.filter(t=>(0,pod_sock_1.isPodSockName)(t)&&!stillRetired(e,path.join(e.dir,t)));if(e.retired)for(const t of[...e.retired.keys()])r.includes(path.basename(t))||e.retired.delete(t);const n=l.filter(t=>!r.includes(t+exports.RETIRING_SUFFIX));e.socks=(n.length>0?n:l).map(t=>path.join(e.dir,t))}catch{e.socks=[]}e.scannedAt=a}if(e.socks.length!==0)return e.idx=(e.idx+1)%e.socks.length,e.socks[e.idx]}h(pickSock,"pickSock"),p(pickSock,"pickSock");function lensPortFromUpstream(e){if(!e)return;const s=/:(\d+)/.exec(e);return s?Number(s[1]):void 0}h(lensPortFromUpstream,"lensPortFromUpstream"),p(lensPortFromUpstream,"lensPortFromUpstream");function rewriteInstanceDecls(e,s){const a=e.flatMap(([,n])=>[...n.host?[n.host]:[],...typeof n.internalHost=="string"?[n.internalHost]:[]]),r=s.apex??(0,instance_1.commonApex)(a);if(!r)return console.warn(`[gateway] instance '${s.name}': the declared hosts share no apex \u2014 set \`instance.apex\` in .lensmcp/config.json; no host is served for it.`),{decls:e.map(([n,t])=>[n,{...t,host:void 0,internalHost:!1,default:!1}])};const l=[];for(const[n,t]of e){const d={...t};if(t.host){const c=(0,instance_1.rewriteHost)(t.host,r,s.name);c||console.warn(`[gateway] instance '${s.name}': ${n}'s host ${t.host} is outside ${r} \u2014 not served for the instance.`),d.host=c}if(typeof t.internalHost=="string"){const c=(0,instance_1.rewriteHost)(t.internalHost,r,s.name);c||console.warn(`[gateway] instance '${s.name}': ${n}'s internal host ${t.internalHost} is outside ${r} \u2014 not served for the instance.`),d.internalHost=c??!1}l.push([n,d])}return{decls:l,baseApex:r,apex:`${s.name}.${r}`}}h(rewriteInstanceDecls,"rewriteInstanceDecls"),p(rewriteInstanceDecls,"rewriteInstanceDecls");function discoverRoutes(e,s,a=os.tmpdir(),r="",l={},n=r,t){const d=n||r,c=p(i=>d?path.join(a,d,`${i}-devserver`):path.join(a,`${i}-devserver`),"sockDirFor"),u=[],g=[],_=[],H=[],S={};let y,w=[];const F=new Map;for(const[i,o]of Object.entries(s))try{const f=JSON.parse(fs.readFileSync(path.join(e,o.root,"project.json"),"utf8")),m=f.cluster??f.davnx;m&&(w.push([i,m]),F.set(i,o.root))}catch{continue}let v={};t&&({decls:w,...v}=rewriteInstanceDecls(w,t));for(const[i,o]of w){const f={root:F.get(i)};let m,j,k,R;if(!o.service&&o.sources!==void 0&&console.warn(`[gateway] ${i}: cluster.sources is read from pod (service) decls only \u2014 ignored.`),o.service)o.lens&&console.warn(`[gateway] ${i}: cluster.lens is ignored for pod (service) decls \u2014 backends are instrumented by serve-hmr.`),m={dir:c(o.service),socks:[],idx:-1,scannedAt:0},j={project:i,decl:o,root:e,wsKey:r,...d!==r?{sockNs:d}:{},pool:m,state:"down",lastUsed:Date.now(),inflight:0,lastScaleAt:0},g.push(j);else if(o.lens){const x=o.port??lensPortFromUpstream(o.upstream)??5173;k=`http://localhost:${x}`,R={project:i,...r?{workspaceKey:r}:{},workspaceRoot:e,projectRoot:path.join(e,f.root),port:x,...l[i]?.length?{sourceRoots:l[i]}:{}},_.push(R)}else if(k=o.upstream??(o.port?`http://localhost:${o.port}`:void 0),!k){console.warn(`[gateway] ${i}: no service/upstream/port \u2014 skipped.`);continue}const b={...r?{wsKey:r}:{},project:i,prependPrefix:o.prependPrefix,target:k,pool:m,svc:j,prefix:o.path,auth:o.auth,lens:R};if(o.host&&(u.push({...b,host:o.host}),H.push(o.host),S[i]={host:o.host},m&&o.internalHost!==!1)){const x=typeof o.internalHost=="string"?o.internalHost:`internal.${o.host}`;u.push({...b,host:x,internal:!0}),S[i]={host:o.host,internalHost:x}}o.default&&(y={...b,prefix:void 0})}y&&(y.apex=v.apex??(0,scope_1.baseDomainOf)(H),u.push(y));let $;if(t&&v.apex&&v.baseApex){const i={name:t.name,apex:v.apex,baseApex:v.baseApex},o=(0,instance_1.instanceEnv)(e,(0,instance_1.instanceBuiltinEnv)(i,S));$={...i,baseKey:t.baseKey,env:o,...t.frozen?{frozen:!0}:{}};for(const f of g)f.instanceEnv=o,t.frozen&&(f.frozen=!0);for(const f of _)f.env=o,t.frozen&&(f.frozen=!0)}return(0,manifest_1.sortBySpecificity)(u),{routes:u,services:g,...$?{instance:$}:{}}}h(discoverRoutes,"discoverRoutes"),p(discoverRoutes,"discoverRoutes");function projectSourceRoots(e,s,a={}){const r={};for(const l of Object.keys(s)){const n=new Set,t=p(c=>{if(!n.has(c)){n.add(c);for(const u of a[c]??[])t(u)}},"visit");t(l);const d=[...n].map(c=>s[c]?.root).filter(c=>typeof c=="string").map(c=>path.resolve(e,c)).filter(c=>{const u=path.relative(e,c);return u===""||u!==".."&&!u.startsWith(`..${path.sep}`)&&!path.isAbsolute(u)});r[l]=[...new Set(d)]}return r}h(projectSourceRoots,"projectSourceRoots"),p(projectSourceRoots,"projectSourceRoots");
1
+ "use strict";var A=Object.defineProperty;var h=(e,o)=>A(e,"name",{value:o,configurable:!0});var I=Object.defineProperty,l=h((e,o)=>I(e,"name",{value:o,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.RETIRING_SUFFIX=void 0,exports.stopRoutingTo=stopRoutingTo,exports.pickSock=pickSock,exports.lensPortFromUpstream=lensPortFromUpstream,exports.rewriteInstanceDecls=rewriteInstanceDecls,exports.discoverRoutes=discoverRoutes,exports.projectSourceRoots=projectSourceRoots;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")),manifest_1=require("../manifest"),pod_sock_1=require("../../../pod-sock"),instance_1=require("./instance"),scope_1=require("./scope");exports.RETIRING_SUFFIX=".retiring";function sockIdentity(e){try{const o=fs.statSync(e);return`${o.dev}:${o.ino}:${o.ctimeMs}`}catch{return}}h(sockIdentity,"sockIdentity"),l(sockIdentity,"sockIdentity");function stopRoutingTo(e,o=()=>!0){let a;try{a=fs.readdirSync(e.dir)}catch{a=[]}const r=a.filter(t=>(0,pod_sock_1.isPodSockName)(t)&&o(t)).map(t=>path.join(e.dir,t));if(r.length===0)return r;const d=e.retired??=new Map;for(const t of r){const u=sockIdentity(t);u!==void 0&&d.set(t,u)}const n=new Set(r);return e.socks=e.socks.filter(t=>!n.has(t)),e.idx=-1,r}h(stopRoutingTo,"stopRoutingTo"),l(stopRoutingTo,"stopRoutingTo");function stillRetired(e,o){const a=e.retired?.get(o);return a===void 0?!1:sockIdentity(o)===a?!0:(e.retired?.delete(o),!1)}h(stillRetired,"stillRetired"),l(stillRetired,"stillRetired");function pickSock(e,o=1500){const a=Date.now();if(a-e.scannedAt>o){try{const r=fs.readdirSync(e.dir),d=r.filter(t=>(0,pod_sock_1.isPodSockName)(t)&&!stillRetired(e,path.join(e.dir,t)));if(e.retired)for(const t of[...e.retired.keys()])r.includes(path.basename(t))||e.retired.delete(t);const n=d.filter(t=>!r.includes(t+exports.RETIRING_SUFFIX));e.socks=(n.length>0?n:d).map(t=>path.join(e.dir,t))}catch{e.socks=[]}e.scannedAt=a}if(e.socks.length!==0)return e.idx=(e.idx+1)%e.socks.length,e.socks[e.idx]}h(pickSock,"pickSock"),l(pickSock,"pickSock");function lensPortFromUpstream(e){if(!e)return;const o=/:(\d+)/.exec(e);return o?Number(o[1]):void 0}h(lensPortFromUpstream,"lensPortFromUpstream"),l(lensPortFromUpstream,"lensPortFromUpstream");function rewriteInstanceDecls(e,o){const a=e.flatMap(([,n])=>[...n.host?[n.host]:[],...typeof n.internalHost=="string"?[n.internalHost]:[]]),r=o.apex??(0,instance_1.commonApex)(a);if(!r)return console.warn(`[gateway] instance '${o.name}': the declared hosts share no apex \u2014 set \`instance.apex\` in .lensmcp/config.json; no host is served for it.`),{decls:e.map(([n,t])=>[n,{...t,host:void 0,internalHost:!1,default:!1}])};const d=[];for(const[n,t]of e){const u={...t};if(t.host){const c=(0,instance_1.rewriteHost)(t.host,r,o.name);c||console.warn(`[gateway] instance '${o.name}': ${n}'s host ${t.host} is outside ${r} \u2014 not served for the instance.`),u.host=c}if(typeof t.internalHost=="string"){const c=(0,instance_1.rewriteHost)(t.internalHost,r,o.name);c||console.warn(`[gateway] instance '${o.name}': ${n}'s internal host ${t.internalHost} is outside ${r} \u2014 not served for the instance.`),u.internalHost=c??!1}d.push([n,u])}return{decls:d,baseApex:r,apex:`${o.name}.${r}`}}h(rewriteInstanceDecls,"rewriteInstanceDecls"),l(rewriteInstanceDecls,"rewriteInstanceDecls");function discoverRoutes(e,o,a=os.tmpdir(),r="",d={},n=r,t){const u=n||r,c=l(i=>u?path.join(a,u,`${i}-devserver`):path.join(a,`${i}-devserver`),"sockDirFor"),f=[],g=[],_=[],H=[],S={};let y,w=[];const F=new Map;for(const[i,s]of Object.entries(o))try{const m=JSON.parse(fs.readFileSync(path.join(e,s.root,"project.json"),"utf8")),p=m.cluster??m.davnx;p&&(w.push([i,p]),F.set(i,s.root))}catch{continue}let v={};t&&({decls:w,...v}=rewriteInstanceDecls(w,t));for(const[i,s]of w){const m={root:F.get(i)};let p,j,k,R;if(!s.service&&s.sources!==void 0&&console.warn(`[gateway] ${i}: cluster.sources is read from pod (service) decls only \u2014 ignored.`),s.service)s.lens&&console.warn(`[gateway] ${i}: cluster.lens is ignored for pod (service) decls \u2014 backends are instrumented by serve-hmr.`),p={dir:c(s.service),socks:[],idx:-1,scannedAt:0},j={project:i,decl:s,root:e,wsKey:r,...u!==r?{sockNs:u}:{},pool:p,state:"down",lastUsed:Date.now(),inflight:0,lastScaleAt:0},g.push(j);else if(s.lens){const x=s.port??lensPortFromUpstream(s.upstream)??5173;k=`http://localhost:${x}`,R={project:i,...r?{workspaceKey:r}:{},workspaceRoot:e,projectRoot:path.join(e,m.root),port:x,...d[i]?.length?{sourceRoots:d[i]}:{}},_.push(R)}else if(k=s.upstream??(s.port?`http://localhost:${s.port}`:void 0),!k){console.warn(`[gateway] ${i}: no service/upstream/port \u2014 skipped.`);continue}const b={...r?{wsKey:r}:{},project:i,prependPrefix:s.prependPrefix,target:k,pool:p,svc:j,prefix:s.path,auth:s.auth,lens:R};if(s.host&&(f.push({...b,host:s.host}),H.push(s.host),S[i]={host:s.host},p&&s.internalHost!==!1)){const x=typeof s.internalHost=="string"?s.internalHost:`internal.${s.host}`;f.push({...b,host:x,internal:!0}),S[i]={host:s.host,internalHost:x}}s.default&&(y={...b,prefix:void 0})}y&&(y.apex=v.apex??(0,scope_1.baseDomainOf)(H),f.push(y));let $;if(t&&v.apex&&v.baseApex){const i={name:t.name,apex:v.apex,baseApex:v.baseApex},s=(0,instance_1.instanceBuiltinEnv)(i,S),m=(0,instance_1.instanceEnv)(e,s);$={...i,baseKey:t.baseKey,env:m,...t.frozen?{frozen:!0}:{}};for(const p of g)p.instanceEnv=m,p.instanceBuiltins=s,t.frozen&&(p.frozen=!0);for(const p of _)p.env=m,t.frozen&&(p.frozen=!0)}return(0,manifest_1.sortBySpecificity)(f),{routes:f,services:g,...$?{instance:$}:{}}}h(discoverRoutes,"discoverRoutes"),l(discoverRoutes,"discoverRoutes");function projectSourceRoots(e,o,a={}){const r={};for(const d of Object.keys(o)){const n=new Set,t=l(c=>{if(!n.has(c)){n.add(c);for(const f of a[c]??[])t(f)}},"visit");t(d);const u=[...n].map(c=>o[c]?.root).filter(c=>typeof c=="string").map(c=>path.resolve(e,c)).filter(c=>{const f=path.relative(e,c);return f===""||f!==".."&&!f.startsWith(`..${path.sep}`)&&!path.isAbsolute(f)});r[d]=[...new Set(u)]}return r}h(projectSourceRoots,"projectSourceRoots"),l(projectSourceRoots,"projectSourceRoots");
@@ -97,8 +97,24 @@ export declare function parseInstanceEnv(text: string, scope: Readonly<Record<st
97
97
  env: Record<string, string>;
98
98
  skipped: number[];
99
99
  };
100
+ /** The instance's variables as registration reads them ({@link layerInstanceEnv}, lenient): never throws. */
101
+ export declare function instanceEnv(root: string, builtins: Readonly<Record<string, string>>): Record<string, string>;
100
102
  /**
101
- * The built-ins, then `instance.resources.env` (the lens's, from the declared resources), then `instance.env` (the
102
- * user's) over them — each file expanding over everything below it. A missing file is the common case.
103
+ * The same variables {@link instanceEnv} gives, read again for a restart of one service — or why they cannot be: a file
104
+ * that is there and unreadable, or a line that is not `KEY=value`. A missing file is still the common case.
103
105
  */
104
- export declare function instanceEnv(root: string, builtins: Readonly<Record<string, string>>): Record<string, string>;
106
+ export declare function readInstanceEnvStrict(root: string, builtins: Readonly<Record<string, string>>): {
107
+ ok: true;
108
+ env: Record<string, string>;
109
+ } | {
110
+ ok: false;
111
+ reason: string;
112
+ };
113
+ /**
114
+ * Twelve hex digits of a SHA-256 over every `KEY=value` line, sorted by key: the same variables give the same print,
115
+ * whatever order a file names them in. It answers «did the change take» without saying any value: the answer of a
116
+ * restart and its event carry it, and never the variables themselves.
117
+ */
118
+ export declare function instanceEnvFingerprint(env: Readonly<Record<string, string>>): string;
119
+ /** The NAMES of the variables that were added, removed or given another value between two snapshots — sorted, no value. */
120
+ export declare function changedInstanceEnvNames(before: Readonly<Record<string, string>>, after: Readonly<Record<string, string>>): string[];
@@ -1,2 +1,3 @@
1
- "use strict";var x=Object.defineProperty;var a=(t,e)=>x(t,"name",{value:e,configurable:!0});var h=Object.defineProperty,c=a((t,e)=>h(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.INSTANCE_NAME_PATTERN=void 0,exports.parseInstanceDecl=parseInstanceDecl,exports.instanceKey=instanceKey,exports.instanceSockNs=instanceSockNs,exports.commonApex=commonApex,exports.rewriteHost=rewriteHost,exports.envSuffix=envSuffix,exports.instanceBuiltinEnv=instanceBuiltinEnv,exports.instanceEnvFile=instanceEnvFile,exports.instanceResourcesEnvFile=instanceResourcesEnvFile,exports.parseInstanceEnv=parseInstanceEnv,exports.instanceEnv=instanceEnv;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.INSTANCE_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,18}[a-z0-9])?$/;const HOST_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;function parseInstanceDecl(t){if(!t||typeof t!="object")return;const e=t;if(typeof e.name!="string"||!exports.INSTANCE_NAME_PATTERN.test(e.name)||typeof e.baseKey!="string"||e.baseKey.length===0)return;const n=typeof e.apex=="string"&&HOST_PATTERN.test(e.apex.toLowerCase())?e.apex.toLowerCase():void 0;return{name:e.name,baseKey:e.baseKey,...n?{apex:n}:{},...typeof e.mainRoot=="string"&&e.mainRoot.length>0?{mainRoot:e.mainRoot}:{},...e.frozen===!0?{frozen:!0}:{}}}a(parseInstanceDecl,"parseInstanceDecl"),c(parseInstanceDecl,"parseInstanceDecl");function instanceKey(t,e){return`${t}-${e}`}a(instanceKey,"instanceKey"),c(instanceKey,"instanceKey");function instanceSockNs(t){let e=2166136261;for(let n=0;n<t.length;n++)e^=t.charCodeAt(n),e=Math.imul(e,16777619);return`i-${(e>>>0&16777215).toString(16).padStart(6,"0")}`}a(instanceSockNs,"instanceSockNs"),c(instanceSockNs,"instanceSockNs");function commonApex(t){const e=t.map(s=>s.toLowerCase().replace(/^\*\./,"")).filter(s=>s.length>0).map(s=>s.split(".").reverse());if(e.length===0)return;const n=[];for(let s=0;;s++){const i=e[0]?.[s];if(i===void 0||!e.every(r=>r[s]===i))break;n.push(i)}return n.length>=2?n.reverse().join("."):void 0}a(commonApex,"commonApex"),c(commonApex,"commonApex");function rewriteHost(t,e,n){const s=t.toLowerCase();if(s.startsWith("*.")){const i=rewriteHost(s.slice(2),e,n);return i===void 0?void 0:`*.${i}`}if(s===e)return`${n}.${e}`;if(s.endsWith(`.${e}`))return`${s.slice(0,-e.length)}${n}.${e}`}a(rewriteHost,"rewriteHost"),c(rewriteHost,"rewriteHost");function envSuffix(t){return t.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")}a(envSuffix,"envSuffix"),c(envSuffix,"envSuffix");function instanceBuiltinEnv(t,e){const n={LENSMCP_INSTANCE:t.name,LENSMCP_APEX:t.apex,LENSMCP_BASE_APEX:t.baseApex},s={};for(const[i,r]of Object.entries(e).sort(([o],[l])=>o.localeCompare(l))){const o=envSuffix(i);o&&(r.host&&(n[`LENSMCP_HOST_${o}`]=r.host),r.internalHost&&(n[`LENSMCP_INTERNAL_HOST_${o}`]=r.internalHost),(r.host||r.internalHost)&&(s[i]={...r.host?{host:r.host}:{},...r.internalHost?{internalHost:r.internalHost}:{}}))}return n.LENSMCP_HOSTS_JSON=JSON.stringify(s),n}a(instanceBuiltinEnv,"instanceBuiltinEnv"),c(instanceBuiltinEnv,"instanceBuiltinEnv");function instanceEnvFile(t){return path.join(t,".lensmcp","instance.env")}a(instanceEnvFile,"instanceEnvFile"),c(instanceEnvFile,"instanceEnvFile");function instanceResourcesEnvFile(t){return path.join(t,".lensmcp","instance.resources.env")}a(instanceResourcesEnvFile,"instanceResourcesEnvFile"),c(instanceResourcesEnvFile,"instanceResourcesEnvFile");function parseInstanceEnv(t,e){const n={},s=[],i=c(o=>n[o]??e[o]??"","lookup"),r=c(o=>o.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(l,u)=>i(u)),"expand");return t.split(/\r?\n/).forEach((o,l)=>{const u=o.trim();if(u===""||u.startsWith("#"))return;const v=/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(u);if(!v){s.push(l+1);return}const[,_,p]=v;let E;if(p.startsWith("'")){const f=p.indexOf("'",1);if(f<0){s.push(l+1);return}E=p.slice(1,f)}else if(p.startsWith('"')){const f=p.indexOf('"',1);if(f<0){s.push(l+1);return}E=r(p.slice(1,f).replace(/\\n/g,`
2
- `))}else E=r(p.replace(/\s+#.*$/,"").trim());n[_]=E}),{env:n,skipped:s}}a(parseInstanceEnv,"parseInstanceEnv"),c(parseInstanceEnv,"parseInstanceEnv");function instanceEnv(t,e){let n={...e};for(const s of[instanceResourcesEnvFile(t),instanceEnvFile(t)]){let i;try{i=fs.readFileSync(s,"utf8")}catch{continue}const{env:r,skipped:o}=parseInstanceEnv(i,n);o.length>0&&console.warn(`[gateway] ${s}: skipped malformed line(s) ${o.join(", ")}`),n={...n,...r}}return{...n,...e}}a(instanceEnv,"instanceEnv"),c(instanceEnv,"instanceEnv");
1
+ "use strict";var m=Object.defineProperty;var i=(n,e)=>m(n,"name",{value:e,configurable:!0});var h=Object.defineProperty,o=i((n,e)=>h(n,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.INSTANCE_NAME_PATTERN=void 0,exports.parseInstanceDecl=parseInstanceDecl,exports.instanceKey=instanceKey,exports.instanceSockNs=instanceSockNs,exports.commonApex=commonApex,exports.rewriteHost=rewriteHost,exports.envSuffix=envSuffix,exports.instanceBuiltinEnv=instanceBuiltinEnv,exports.instanceEnvFile=instanceEnvFile,exports.instanceResourcesEnvFile=instanceResourcesEnvFile,exports.parseInstanceEnv=parseInstanceEnv,exports.instanceEnv=instanceEnv,exports.readInstanceEnvStrict=readInstanceEnvStrict,exports.instanceEnvFingerprint=instanceEnvFingerprint,exports.changedInstanceEnvNames=changedInstanceEnvNames;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.INSTANCE_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,18}[a-z0-9])?$/;const HOST_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;function parseInstanceDecl(n){if(!n||typeof n!="object")return;const e=n;if(typeof e.name!="string"||!exports.INSTANCE_NAME_PATTERN.test(e.name)||typeof e.baseKey!="string"||e.baseKey.length===0)return;const t=typeof e.apex=="string"&&HOST_PATTERN.test(e.apex.toLowerCase())?e.apex.toLowerCase():void 0;return{name:e.name,baseKey:e.baseKey,...t?{apex:t}:{},...typeof e.mainRoot=="string"&&e.mainRoot.length>0?{mainRoot:e.mainRoot}:{},...e.frozen===!0?{frozen:!0}:{}}}i(parseInstanceDecl,"parseInstanceDecl"),o(parseInstanceDecl,"parseInstanceDecl");function instanceKey(n,e){return`${n}-${e}`}i(instanceKey,"instanceKey"),o(instanceKey,"instanceKey");function instanceSockNs(n){let e=2166136261;for(let t=0;t<n.length;t++)e^=n.charCodeAt(t),e=Math.imul(e,16777619);return`i-${(e>>>0&16777215).toString(16).padStart(6,"0")}`}i(instanceSockNs,"instanceSockNs"),o(instanceSockNs,"instanceSockNs");function commonApex(n){const e=n.map(r=>r.toLowerCase().replace(/^\*\./,"")).filter(r=>r.length>0).map(r=>r.split(".").reverse());if(e.length===0)return;const t=[];for(let r=0;;r++){const a=e[0]?.[r];if(a===void 0||!e.every(s=>s[r]===a))break;t.push(a)}return t.length>=2?t.reverse().join("."):void 0}i(commonApex,"commonApex"),o(commonApex,"commonApex");function rewriteHost(n,e,t){const r=n.toLowerCase();if(r.startsWith("*.")){const a=rewriteHost(r.slice(2),e,t);return a===void 0?void 0:`*.${a}`}if(r===e)return`${t}.${e}`;if(r.endsWith(`.${e}`))return`${r.slice(0,-e.length)}${t}.${e}`}i(rewriteHost,"rewriteHost"),o(rewriteHost,"rewriteHost");function envSuffix(n){return n.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")}i(envSuffix,"envSuffix"),o(envSuffix,"envSuffix");function instanceBuiltinEnv(n,e){const t={LENSMCP_INSTANCE:n.name,LENSMCP_APEX:n.apex,LENSMCP_BASE_APEX:n.baseApex},r={};for(const[a,s]of Object.entries(e).sort(([c],[p])=>c.localeCompare(p))){const c=envSuffix(a);c&&(s.host&&(t[`LENSMCP_HOST_${c}`]=s.host),s.internalHost&&(t[`LENSMCP_INTERNAL_HOST_${c}`]=s.internalHost),(s.host||s.internalHost)&&(r[a]={...s.host?{host:s.host}:{},...s.internalHost?{internalHost:s.internalHost}:{}}))}return t.LENSMCP_HOSTS_JSON=JSON.stringify(r),t}i(instanceBuiltinEnv,"instanceBuiltinEnv"),o(instanceBuiltinEnv,"instanceBuiltinEnv");function instanceEnvFile(n){return path.join(n,".lensmcp","instance.env")}i(instanceEnvFile,"instanceEnvFile"),o(instanceEnvFile,"instanceEnvFile");function instanceResourcesEnvFile(n){return path.join(n,".lensmcp","instance.resources.env")}i(instanceResourcesEnvFile,"instanceResourcesEnvFile"),o(instanceResourcesEnvFile,"instanceResourcesEnvFile");function parseInstanceEnv(n,e){const t={},r=[],a=o(c=>t[c]??e[c]??"","lookup"),s=o(c=>c.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(p,l)=>a(l)),"expand");return n.split(/\r?\n/).forEach((c,p)=>{const l=c.trim();if(l===""||l.startsWith("#"))return;const f=/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(l);if(!f){r.push(p+1);return}const[,d,u]=f;let E;if(u.startsWith("'")){const v=u.indexOf("'",1);if(v<0){r.push(p+1);return}E=u.slice(1,v)}else if(u.startsWith('"')){const v=u.indexOf('"',1);if(v<0){r.push(p+1);return}E=s(u.slice(1,v).replace(/\\n/g,`
2
+ `))}else E=s(u.replace(/\s+#.*$/,"").trim());t[d]=E}),{env:t,skipped:r}}i(parseInstanceEnv,"parseInstanceEnv"),o(parseInstanceEnv,"parseInstanceEnv");function layerInstanceEnv(n,e,t){let r={...e};for(const a of[instanceResourcesEnvFile(n),instanceEnvFile(n)]){let s;try{s=fs.readFileSync(a,"utf8")}catch(l){const f=l.code;if(!t||f==="ENOENT")continue;return{problem:`${a} could not be read (${f??l.message})`}}const{env:c,skipped:p}=parseInstanceEnv(s,r);if(p.length>0){if(t)return{problem:`${a}: line(s) ${p.join(", ")} are not KEY=value`};console.warn(`[gateway] ${a}: skipped malformed line(s) ${p.join(", ")}`)}r={...r,...c}}return{env:{...r,...e}}}i(layerInstanceEnv,"layerInstanceEnv"),o(layerInstanceEnv,"layerInstanceEnv");function instanceEnv(n,e){const t=layerInstanceEnv(n,e,!1);return"env"in t?t.env:{...e}}i(instanceEnv,"instanceEnv"),o(instanceEnv,"instanceEnv");function readInstanceEnvStrict(n,e){const t=layerInstanceEnv(n,e,!0);return"env"in t?{ok:!0,env:t.env}:{ok:!1,reason:t.problem}}i(readInstanceEnvStrict,"readInstanceEnvStrict"),o(readInstanceEnvStrict,"readInstanceEnvStrict");function instanceEnvFingerprint(n){const e=Object.keys(n).sort().map(t=>`${t}=${n[t]}`);return(0,node_crypto_1.createHash)("sha256").update(e.join(`
3
+ `)).digest("hex").slice(0,12)}i(instanceEnvFingerprint,"instanceEnvFingerprint"),o(instanceEnvFingerprint,"instanceEnvFingerprint");function changedInstanceEnvNames(n,e){return[...new Set([...Object.keys(n),...Object.keys(e)])].filter(t=>n[t]!==e[t]).sort()}i(changedInstanceEnvNames,"changedInstanceEnvNames"),o(changedInstanceEnvNames,"changedInstanceEnvNames");
@@ -1,3 +1,3 @@
1
- "use strict";var it=Object.defineProperty;var m=(t,c)=>it(t,"name",{value:c,configurable:!0});var at=Object.defineProperty,a=m((t,c)=>at(t,"name",{value:c,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,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.podChangeClock=podChangeClock,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")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),pod_sock_1=require("../../../pod-sock"),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"),own_log_1=require("./own-log"),inspector_secret_1=require("../../../inspector-secret"),pod_output_1=require("./pod-output"),serve_launch_1=require("./serve-launch"),shared_build_1=require("./shared-build"),awake_1=require("./awake"),warm_start_1=require("./warm-start"),protocol_1=require("../../../shared-build/protocol"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name"),orphan_reaper_1=require("./orphan-reaper");function eastWestEnv(t,c){const i={},p=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(p&&(i.LENSMCP_SERVICE_KEY=p),t.https){const w=require("../../../basic-ssl").caCertPath();fs.existsSync(w)&&(i.NODE_EXTRA_CA_CERTS=w)}return i}m(eastWestEnv,"eastWestEnv"),a(eastWestEnv,"eastWestEnv");const sleep=a(t=>new Promise(c=>setTimeout(c,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 c=2166136261;for(let i=0;i<t.length;i+=1)c^=t.charCodeAt(i),c=Math.imul(c,16777619);return c>>>0}m(fnv1a,"fnv1a"),a(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}m(gitIgnoredPaths,"gitIgnoredPaths"),a(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const i=path.join(t.pool.dir,"parent.sock");return c(i)?{socketPath:i}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}m(scaleTarget,"scaleTarget"),a(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}m(isLoopbackHostname,"isLoopbackHostname"),a(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,i){let p;if("socketPath"in t)p=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},w=>w.resume());else{const w=t.url.startsWith("https"),T=w?require("node:https"):http,_={method:"POST"};w&&isLoopbackHostname(new URL(t.url).hostname)&&(_.rejectUnauthorized=!1),i&&(_.lookup=i),p=T.request(`${t.url}/webpack/scale`,_,A=>A.resume())}return p.on("error",()=>{}),p.end(JSON.stringify({pods:c})),p}m(postScale,"postScale"),a(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}m(isOwnRunnerCommand,"isOwnRunnerCommand"),a(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,i){const p=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${p}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,i)}m(isRunnerOf,"isRunnerOf"),a(isRunnerOf,"isRunnerOf");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 c=t.split("/"),i=c[c.length-1]??"";if(TEST_FILE_RE.test(i))return!0;for(let p=0;p<c.length-1;p+=1)if(TEST_DIR_SEGMENTS.has(c[p]))return!0;return!1}m(isTestSourcePath,"isTestSourcePath"),a(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,i){const p=i?.ignoreTests??!0;let w;const T=new Map,_=`tree:${path.resolve(t)}`,A=a(y=>path.relative(t,y).split(path.sep).join("/"),"rel"),C=a(async(y,$)=>{const E=await(w??=gitIgnoredPaths(t)),b=[],F=new Set;let D=!1;const H=a(async(te,S)=>{if(b.length>=SCAN_FILE_CAP){D=!0;return}let O;try{O=await fs.promises.readdir(te,{withFileTypes:!0})}catch{return}S&&F.add(A(te));for(const z of O){if(b.length>=SCAN_FILE_CAP){D=!0;return}const W=z.name,re=path.join(te,W);if(z.isDirectory()){if(!S||W.startsWith(".")||SRC_EXCLUDE.has(W)||c?.has(W)||W.endsWith("-devserver")||E.has(`${A(re)}/`))continue;await H(re,!0)}else if(z.isFile()){const oe=W.lastIndexOf(".");if(oe<=0||!SRC_EXT.has(W.slice(oe)))continue;const J=A(re);if(E.has(J)||p&&isTestSourcePath(J))continue;b.push(J)}}},"walk");return await H(y,$),{paths:b,entered:F,truncated:D}},"walkDir"),L=a((y,$)=>{let E=T.get(y);return E||(E=$(),T.set(y,E)),E},"memo"),ie=a(async()=>{const y=T.get(_);if(!y)return;const $=await y;return $.truncated?void 0:$},"wideWalk"),B=new Set,le=a(()=>L(`files:${path.resolve(t)}`,async()=>{const y=await ie();return y?{paths:y.paths.filter($=>!$.includes("/")),entered:B,truncated:!1}:C(t,!1)}),"rootFiles"),de=a(y=>L(`tree:${path.resolve(y)}`,async()=>{const $=await ie(),E=A(y);if($?.entered.has(E)){const b=`${E}/`;return{paths:E===""?$.paths:$.paths.filter(F=>F.startsWith(b)),entered:B,truncated:!1}}return C(y,!0)}),"subtree"),X=a(y=>{const $=new Set;let E=0,b=y.some(D=>D.truncated);const F=a(D=>$.has(D)?!0:$.size>=SCAN_FILE_CAP?(b=!0,!1):($.add(D),E^=fnv1a(D),!0),"add");for(const D of y)if(!D.paths.every(F))break;return{signature:`${$.size}:${(E>>>0).toString(36)}`,paths:$,truncated:b}},"compose");return{async snapshot(y){if(!y||y.length===0)return X([await L(_,()=>C(t,!0))]);const $=[await le()];for(const E of y)$.push(await de(E));return X($)}}}m(sourceScanPass,"sourceScanPass"),a(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,i,p){return sourceScanPass(t,i,p).snapshot(c)}m(sourceSetSnapshot,"sourceSetSnapshot"),a(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,i,p){return(await sourceSetSnapshot(t,c,i,p)).signature}m(sourceSetSignature,"sourceSetSignature"),a(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,i,p){if(t.paths.size===c.paths.size){let A=!0;for(const C of t.paths)if(!c.paths.has(C)){A=!1;break}if(A)return null}const w=[],T=[];let _=t.truncated||c.truncated;for(const A of c.paths)t.paths.has(A)||(w.length+T.length<SOURCE_DIFF_PATH_CAP?w.push(A):_=!0);for(const A of t.paths)c.paths.has(A)||(w.length+T.length<SOURCE_DIFF_PATH_CAP?T.push(A):_=!0);return{changedAt:i,added:w,removed:T,truncated:_,...p!==void 0?{windowMs:p}:{}}}m(diffSourceSets,"diffSourceSets"),a(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,i,p){const w=c&&c>0?t-c:i;return Math.max(w,i)+p}m(sourceChangeWindowMs,"sourceChangeWindowMs"),a(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,i,p,w){const T=a(C=>{const L=path.relative(t,C).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(c);if(!T)return[];const _=new Set(i.filter(Boolean)),A=new Set([T]);for(const C of p)C.startsWith(".")||SRC_EXCLUDE.has(C)||w?.has(C)||C.endsWith("-devserver")||_.has(C)||A.add(C);return[...A].sort().map(C=>path.join(t,C))}m(sourceScopeDirs,"sourceScopeDirs"),a(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,i,p){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<p.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<p.cooldownMs?null:i>0&&i-t.spawnAt>p.graceMs?"stale-source-set":p.maxAgeMs>0&&c-t.spawnAt>p.maxAgeMs?"max-age":null}m(podRecycleReason,"podRecycleReason"),a(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,i,p,w){return!(p-c<w.cooldownMs||i<=0||i-t<=w.graceMs||p-i<w.settleMs)}m(lensFrontendStale,"lensFrontendStale"),a(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,i,p=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&p.length>0){const w=c-i,T=new Set(t.recentEvents.filter(_=>_.at>=w).map(_=>path.resolve(_.path)));return p.every(_=>T.has(path.resolve(_)))?"heard":"missed"}return t.lastFsSetEventAt>=c-i?"heard":"missed"}m(lensWatcherVerdict,"lensWatcherVerdict"),a(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,i){if(!t)return"unknown";if(t.degraded)return"degraded";const p=c-i;return t.lastWatchRunAt>=p||t.lastBuildAt>=p?"heard":"silent"}m(podWatcherVerdict,"podWatcherVerdict"),a(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,i,p){const w=t.hasListened?p.graceMs:Math.max(p.graceMs,p.neverListenedGraceMs);return i-t.spawnedAt<w||i-c<p.cooldownMs||t.probeFails<Math.max(2,p.failureThreshold)?null:"wedged-no-listener"}m(lensChildWedged,"lensChildWedged"),a(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}m(isPortConflictExit,"isPortConflictExit"),a(isPortConflictExit,"isPortConflictExit");function podChangeClock(t,c){const i=t.sourceSetChangedFor?.get(c);if(i!==void 0){const p=t.sourceSetChangesFor?.get(c);return{at:i,...p?{change:p}:{},scope:"project"}}return t.sourceSetScoped?.has(c)?{at:0,scope:"none"}:{at:t.sourceSetChangedAt??0,...t.sourceSetChange?{change:t.sourceSetChange}:{},scope:"workspace"}}m(podChangeClock,"podChangeClock"),a(podChangeClock,"podChangeClock");const changeEvidence=a(t=>{if(!t)return"no path evidence";const c=[...t.added.map(i=>`+${i}`),...t.removed.map(i=>`-${i}`)];return c.length?`${c.slice(0,3).join(", ")}${c.length>3?` (+${c.length-3} more)`:""}`:"no path evidence"},"changeEvidence");function createServiceLayer(t,c,i={}){const p=i.spawnProcess??node_child_process_1.spawn,w=i.probeAlive??pod_probe_1.probePodAlive,T=i.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),_=i.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),A=a((e,r="SIGTERM")=>{_.detach(e),T(e,r)},"killTree"),C=i.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),L=i.treeAlive??(e=>{if(!e.pid||!(e instanceof node_child_process_1.ChildProcess))return!1;try{return process.kill(-e.pid,0),!0}catch(r){return r.code==="EPERM"}}),ie=i.retirePollMs??1e3,B=i.rollReadyMs??types_1.POD_ROLL_READY_MS,le=i.rollPollMs??750,de=i.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,X=de>0?de:Number.POSITIVE_INFINITY,y=i.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,$=y>0?y:Number.POSITIVE_INFINITY;let E=0;const b=[],F=a(()=>E<$?(E+=1,Promise.resolve()):new Promise(e=>{b.push(()=>{E+=1,e()})}),"acquireColdSlot"),D=a(()=>{E-=1,b.shift()?.()},"releaseColdSlot"),{emit:H}=c,te=a(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),S=a((e,r,o,s,l)=>H(r,o,s,{wsKey:e.wsKey,...l},void 0,te(e)),"emitSvc"),O=a(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),z=a((e,r)=>{let o;try{o=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const s=e.proc?.pid;let l=0;for(const n of o.split(`
2
- `)){const d=n.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!d)continue;const u=Number(d[1]),g=Number(d[2]);if(isRunnerOf(d[3],r,e.root)&&!(u===process.pid||u===s)&&!(s!==void 0&&g===s)){try{process.kill(-g,"SIGKILL")}catch{try{process.kill(u,"SIGKILL")}catch{}}l+=1}}return l>0&&(console.log(`[gateway] ${r}: reaped ${l} stale runner tree(s) \u2014 a foreign generation of this service (holding nx's task lock, or its pool)`),S(e,"warning",`stale-runner reap: ${e.project} (${l} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:l})),l},"reapForeignRunners"),W=a((e,r)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),...e.instanceEnv?{...e.instanceEnv,LENSMCP_SOCKET_ONLY:"1"}:{},...e.frozen?{LENSMCP_FREEZE:"1",LENSMCP_TYPECHECK:"off"}:{},NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},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)}),"serviceEnv"),re=a((e,r,o,s)=>{const l=W(e,r),n=e.wsKey||path.basename(e.root),d=(0,serve_launch_1.planServeLaunch)({stores:t.graphStores,root:e.root,project:e.project,target:e.decl.serveTarget??"serve-hmr",childCount:s,baseEnv:l});if(d.kind==="runner"){const v=_.offeredTo(e),f=p((0,process_name_1.namedExecPath)((0,process_name_1.processName)(n,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...d.script.execArgv,d.script.path,...(0,serve_launch_1.serveRunnerArgs)(o,s,e.root)],{cwd:e.root,detached:!0,env:v?{...d.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:d.env,stdio:["ignore","pipe","pipe","ipc"]});return v&&_.attach(e,r,f),f.send?.(d.message,R=>{R&&console.error(`[gateway] ${o}: could not hand the task to its runner (${R.message})`)}),{proc:f,launcher:"runner"}}console.log(`[gateway] ${o}: starting through nx run \u2014 ${d.reason}`);const u=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),g=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(n,e.project,"nx")),[k,j]=fs.existsSync(u)?[g,[u,"run",o,`--childCount=${s}`]]:["yarn",["nx","run",o,`--childCount=${s}`]];return{proc:p(k,j,{cwd:e.root,detached:!0,env:l,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),oe=new WeakMap,J=a((e,r,o)=>{const s=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,l=Math.max(1,e.decl.minPods??1),{proc:n,launcher:d}=re(e,r,s,l);oe.set(n,d),o==="active"&&(e.launcher=d),o==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const u=a(j=>{const v=j.toString();for(const f of v.split(`
3
- `)){const R=(0,pod_output_1.classifyPodLine)(f),{clean:M,broken:G,healthy:ne}=R,h=M;if(G){e.lastErrors.push(M),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();(M!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=M,e.lastErrorEmitAt=x,S(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:M,fault:R.fault}))}ne&&(e.buildFailed=!1);const P=(e.faults??=new pod_output_1.PodFaults).observe(R);if(P){e.lastErrorEmitSig=void 0;const x=P.open.length===0;S(e,"info",`${x?"service recovered":`${P.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:x?"service-recovered":"service-fault-closed",project:e.project,line:M,closed:P.closed,open:P.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(h)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${s}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),S(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||(V(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),z(e,s),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.())},250).unref?.())}},"sniff"),g=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,k=a(j=>{const v=(0,own_log_1.linePrefixer)(e.project);return f=>{if(t.stopped())return;const R=(0,inspector_secret_1.redactInspectorUrls)(f.toString());u(R);const M=v(R);j.write(M),g?.(M)}},"relay");return n.stdout?.on("data",k(process.stdout)),n.stderr?.on("data",k(process.stderr)),n.on("exit",(j,v)=>{if(e.proc!==n||(e.proc=void 0,e.state="down",t.stopped()))return;const f=(0,exit_reason_1.describeExit)(j,v);if(console.log(`[gateway] ${s} exited (${j??"no code"}${v?`, ${v}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${f?` \u2014 ${f}`:""}`),S(e,j===0?"info":"warning",`service down: ${e.project} (exit ${j})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:j,...v?{signal:v}:{}}),j===0||e.buildFailed)return;const R=Date.now();if(e.restarts=(e.restarts??[]).filter(G=>R-G<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){S(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(R);const M=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})`),S(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),Y(e))},M)}),n},"launchServiceProcess"),Y=a(e=>{if(t.stopped()||e.manualStopped)return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,o=Math.max(1,e.decl.minPods??1);i.reapForeign!==!1&&z(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${o})\u2026`),S(e,"info",`service starting: ${e.project} (${o} pod${o>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:o}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=J(e,e.generation,"active")},"spawnService"),be=a(async e=>{const r=e.pool.socks[0];if(!r)return;const o=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);o&&(e.activeWork=o.activeWork,o.watcher&&(e.watcher=o.watcher))},"refreshActiveWork"),Le=a(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),Ie=a(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const o=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),s=[];for(const l of e.pool.socks){const n=await(0,pod_probe_1.probePodAlive)(l,void 0,r);n&&(!n.keyFingerprint||n.keyFingerprint!==o)&&s.push({sock:l,pid:n.pid})}return s},"staleAdoptedSocks"),Oe=a((e,r)=>{const o=r.map(s=>s.sock);for(const{sock:s,pid:l}of r){const n=chooseStalePodKillTarget(l,Le(s),process.pid);if(n!==void 0)try{process.kill(n,"SIGTERM")}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(s=>!o.includes(s)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),pe=a((e,r)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const o=e.pool.socks.length;return S(e,"info",`service up: ${e.project} (${o} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:o,adopted:r==="adopted"}),!0},"markUp"),we=a(async e=>{if(e.manualStopped)return!1;if(O(e.pool)>0){const s=await Ie(e);if(e.manualStopped)return!1;if(s.length>0&&(console.error(`[gateway] ${e.project}: ${s.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),S(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:s.length}),e.proc&&V(e,"stale adoption (foreign east-west key)"),Oe(e,s)),e.pool.socks.length>0)return pe(e,"adopted"),!0}if(t.stopped())return!1;let r=!1;const o=a(()=>e.state!=="starting"&&!e.proc,"notStarted");if(o())if(e.coldWait)await e.coldWait;else{E>=$&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${E} starting, ${b.length} ahead (LENSMCP_MAX_COLD_STARTS=${$}).`),S(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:E,ahead:b.length}));const s=F();if(e.coldWait=s,await s,e.coldWait=void 0,r=!0,t.stopped()||e.manualStopped)return D(),!1;o()&&Y(e)}try{return await Ne(e)}finally{r&&D()}},"ensureUp"),Ne=a(async e=>{const r=Date.now()+t.startupTimeoutMs,o=Date.now()+12e3;for(;!t.stopped()&&!e.manualStopped&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.manualStopped||e.buildFailed&&Date.now()>o)return!1;if(O(e.pool)>0){e.state="up",e.buildFailed=!1;const s=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${s?`, cold start ${s}ms`:""})`),S(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${s??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:s}),!0}if(e.state==="down")return!1}return!1},"waitForPods"),Ke=a(e=>{const r=e.decl.maxPods??e.decl.minPods??1,o=e.pool.socks.length,s=scaleTarget(e);!s||o===0||o>=r||e.inflight<=o||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(s,o+1,i.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${o} \u2192 ${o+1} pods (inflight ${e.inflight})`),S(e,"info",`autoscale: ${e.project} ${o} \u2192 ${o+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:o,to:o+1,inflight:e.inflight}))},"scaleUp"),ue=i.killGraceMs??types_1.TREE_KILL_GRACE_MS,N=new Map,Ce=a(e=>{const r=N.get(e);if(!r||(N.delete(e),clearTimeout(r.timer),!L(e)))return;const{svc:o,why:s}=r;console.log(`[gateway] ${o.project}: its tree (pid ${e.pid}) was still running ${Math.round(ue/1e3)}s after it was told to stop (${s}) \u2014 killing it (SIGKILL).`),S(o,"warning",`tree force-killed: ${o.project} (it ignored SIGTERM)`,`cluster-service:${o.project}`,{kind:"tree-force-killed",project:o.project,why:s,pid:e.pid,graceMs:ue}),T(e,"SIGKILL")},"settleDyingTree"),Se=a((e,r,o)=>{if(A(r,"SIGTERM"),ue<=0||N.has(r))return;const s=setTimeout(()=>Ce(r),ue);s.unref?.(),N.set(r,{svc:e,why:o,timer:s})},"stopTree"),Fe=a(async(e=3e3)=>{const r=Date.now()+e;for(;N.size>0;){for(const[o,s]of[...N])L(o)||(clearTimeout(s.timer),N.delete(o));if(N.size===0||Date.now()>=r)break;await sleep(100)}for(const o of[...N.keys()])Ce(o)},"finishDyingTrees"),V=a((e,r,o)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),o?.announce!==!1&&S(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const s=e.proc;try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}(0,discovery_1.stopRoutingTo)(e.pool),Se(e,s,r),e.proc=void 0,e.state="down",setTimeout(()=>{ge(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ge=a(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const o=path.join(e.pool.dir,r);try{const s=Number(fs.readFileSync(`${o}.owner`,"utf8").trim());if(Number.isFinite(s)&&s>0)try{process.kill(s,0);continue}catch{}}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let Q=0;const ve=[],We=a(()=>Q<X?(Q+=1,Promise.resolve()):new Promise(e=>{ve.push(()=>{Q+=1,e()})}),"acquireRollSlot"),Ge=a(()=>{Q-=1,ve.shift()?.()},"releaseRollSlot"),ye=a(async(e,r,o)=>{O(e.pool);for(const s of e.pool.socks){const l=await w(s,void 0,o);if(l&&r(l.podGeneration))return!0}return!1},"anyPodAnswers"),fe=new Map,qe=a((e,r,o)=>{const s=C>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,o);if(A(r,"SIGTERM"),!s)return;const l=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,n=a(ne=>h=>{if(t.stopped())return;const P=(0,inspector_secret_1.redactInspectorUrls)(h.toString()).replace(/^(?=.)/gm,`[${e.project} g${o} retiring] `);ne.write(P),l?.(P)},"say"),d=n(process.stdout),u=n(process.stderr);r.stdout?.on("data",d),r.stderr?.on("data",u);let g,k=!1;const j=a(()=>{k=!0,clearTimeout(g),r.stdout?.removeListener("data",d),r.stderr?.removeListener("data",u),r.removeListener("close",j)},"releaseLogs");r.once("close",j);let v=!1;const f=a(()=>{v||(v=!0,clearTimeout(R),clearInterval(G),t.stopped()?j():k||(g=setTimeout(j,1e3),g.unref?.()),fe.delete(r),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,o),ge(e),e.pool.scannedAt=0)},"end"),R=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${o} was still running ${C}ms after it was retired \u2014 killing it.`),T(r,"SIGKILL"),f()},C);R.unref?.();const M=a(()=>{if(!v){if(t.stopped()){f();return}L(r)||(console.log(`[gateway] pod handoff: ${e.project} g${o} finished its in-flight work and exited.`),f())}},"look"),G=setInterval(M,ie);G.unref?.(),r.once("exit",M),fe.set(r,()=>{f(),Se(e,r,"the gateway is stopping")})},"retireReplacedTree"),Ve=i.standbyGraceMs??3e4,I=new Map,Ue=a((e,r,o,s,l)=>{const n=l+B+Ve,d=setTimeout(()=>Me(r),Math.max(0,n-Date.now()));d.unref?.(),I.set(r,{svc:e,generation:o,reason:s,beganAt:l,deadlineAt:n,timer:d})},"holdStandby"),Be=a(e=>{const r=I.get(e);r&&(clearTimeout(r.timer),I.delete(e))},"releaseStandby"),Z=a((e,r)=>{const o=I.get(e);if(!o)return!1;clearTimeout(o.timer),I.delete(e);const{svc:s,generation:l}=o;try{e.stdout?.removeAllListeners("data"),e.stderr?.removeAllListeners("data")}catch{}return(0,discovery_1.stopRoutingTo)(s.pool,n=>(0,pod_sock_1.podSockGeneration)(n)===l),Se(s,e,`standby g${l}: ${r}`),setTimeout(()=>ge(s),1200).unref?.(),!0},"endStandby"),Te=a((e,r)=>{const o=r.oldServing?`old generation g${r.fromGen} kept serving`:`and the old generation g${r.fromGen} is NOT serving (${r.oldGone?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;console.error(`[gateway] pod handoff failed: ${e.project} g${r.generation}: ${r.why} \u2014 ${o}.`),e.rollFailure=`${r.why} \u2014 ${o}`,S(e,r.oldServing?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r.reason,generation:r.generation,error:r.why,durationMs:Date.now()-r.beganAt,oldServing:r.oldServing})},"reportStandbyFailed"),Me=a(e=>{const r=I.get(e);if(!r)return;const{svc:o,generation:s,reason:l,beganAt:n}=r,d=`the standby outlived its roll (${Math.round((Date.now()-n)/1e3)}s; a roll waits ${Math.round(B/1e3)}s for it)`;Z(e,d);const u=o.proc,g=t.serviceKeys[(0,service_keys_1.serviceKeyId)(o.wsKey,o.project)];(async()=>{const k=!u||u.exitCode!==null||u.signalCode!==null,j=k?!1:await Promise.race([ye(o,f=>f!==s,g).catch(()=>!1),sleep(2e3).then(()=>{})]),v=!t.stopped()&&!k&&(j??O(o.pool)>0);Te(o,{reason:l,generation:s,fromGen:o.generation??s-1,why:d,beganAt:n,oldServing:v,oldGone:k})})()},"outlivedRoll"),He=a(()=>{for(const[e,r]of[...I])Date.now()>=r.deadlineAt?Me(e):!r.svc.rolling&&r.svc.proc!==e&&(console.error(`[gateway] pod handoff: ${r.svc.project} g${r.generation} is a standby whose roll is over \u2014 stopping it.`),Z(e,"its roll is over"))},"sweepStandbys"),Ye=a(()=>{for(const e of[...fe.values()])e();for(const e of[...I.keys()])Z(e,"the gateway is stopping")},"stopRetiringTrees"),me=a((e,r)=>{if(e.rolling)return e.rolling;const o=e.proc;if(!o||t.stopped())return Promise.resolve(!1);if(e.launcher==="nx")return console.log(`[gateway] pod handoff skipped: ${e.project} runs through nx run, which starts one instance of a target at a time \u2014 no standby can overlap it (reason=${r}).`),Promise.resolve(!1);Q>=X&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${r} \u2014 ${Q} roll(s) in flight, ${ve.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${X}).`);const s=(async()=>{await We();try{return await Xe(e,o,r)}finally{Ge()}})().finally(()=>{e.rolling=void 0});return e.rolling=s,s},"rollPod"),Xe=a(async(e,r,o)=>{const s=e.proc;if(s!==r||t.stopped())return!1;const l=e.generation??0,n=l+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${l}\u2192g${n} reason=${o} \u2014 standby spawning, old generation keeps serving.`),S(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:n,fromGeneration:l});const u=J(e,n,"standby"),g=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];Ue(e,u,n,o,d);let k=!1;try{const j=d+B;let v;for(;!t.stopped()&&Date.now()<j&&I.has(u)&&!(u.exitCode!==null||u.signalCode!==null);){O(e.pool);for(const f of e.pool.socks)if((await w(f,void 0,g))?.podGeneration===n){v=f;break}if(v)break;await sleep(le)}if(!I.has(u))return!1;if(!v||t.stopped()){const f=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(B/1e3)}s`;Z(u,f);const R=!t.stopped()&&e.proc===s&&s.exitCode===null&&s.signalCode===null&&await ye(e,M=>M!==n,g);return Te(e,{reason:o,generation:n,fromGen:l,why:f,beganAt:d,oldServing:R,oldGone:e.proc!==s||s.exitCode!==null||s.signalCode!==null}),!1}if(e.proc!==s)return Z(u,"the service was replaced underneath the roll"),!1;Be(u),k=!0,e.proc=u,e.launcher=oe.get(u),e.generation=n,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(0,discovery_1.stopRoutingTo)(e.pool,f=>(0,pod_sock_1.podSockGeneration)(f)!==n&&path.join(e.pool.dir,f)!==v),qe(e,s,l),setTimeout(()=>{ge(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${n} answered on ${path.basename(v)} in ${Date.now()-d}ms; retiring g${l}.`),S(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:n,fromGeneration:l,durationMs:Date.now()-d}),!0}finally{k||Z(u,"its roll ended without it")}},"runRoll"),ze=a((e,r)=>{if(t.stopped())return{refused:"the gateway is stopping"};if(e.manualStopped=!1,e.actionFailure=void 0,e.restart&&!e.restart.done)return{restart:e.restart,already:!0};if(!e.proc&&e.state!=="down")return{refused:`${e.project} is served by pods this gateway adopted from an earlier one \u2014 it has no tree of its own to replace. \`lensmcp gateway restart\` starts every service under this gateway.`};const o=e.proc?r.hard||e.launcher==="nx"||e.state!=="up"?"hard":"rolling":"start",s={how:o,reason:r.reason,startedAt:Date.now(),...e.proc&&e.generation!==void 0?{fromGeneration:e.generation}:{}};return e.restart=s,e.lastUsed=s.startedAt,console.log(`[gateway] restart asked: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${o} (${r.reason}).`),S(e,"info",`restart asked: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"started",project:e.project,how:o,reason:r.reason,...s.fromGeneration!==void 0?{fromGeneration:s.fromGeneration}:{}}),a(async()=>{if(o==="start")return await we(e)?{ok:!0}:{ok:!1,error:$e(e)};if(o==="rolling")return e.rolling&&await e.rolling,e.proc?(e.rollFailure=void 0,await me(e,r.reason)?{ok:!0}:{ok:!1,error:e.rollFailure??"the new tree did not take over"}):await we(e)?{ok:!0}:{ok:!1,error:$e(e)};if(V(e,`restart asked (${r.reason})`,{announce:!1}),await sleep(1500),t.stopped())return{ok:!1,error:"the gateway is stopping"};let l=!1;e.proc||(await F(),l=!0,!e.proc&&e.state!=="starting"&&Y(e));try{const n=e.proc,d=e.generation,u=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],g=Date.now()+t.startupTimeoutMs;for(;!t.stopped()&&Date.now()<g;){if(!n||e.proc!==n||n.exitCode!==null||n.signalCode!==null)return{ok:!1,error:$e(e)};if(await ye(e,k=>k===d,u))return pe(e,"observed"),e.buildFailed=!1,{ok:!0};await sleep(le)}return{ok:!1,error:t.stopped()?"the gateway is stopping":`no pod of the new tree answered within ${Math.round(t.startupTimeoutMs/1e3)}s`}}finally{l&&D()}},"run")().catch(l=>({ok:!1,error:l.message})).then(l=>{s.done={ok:l.ok,at:Date.now(),...e.generation!==void 0?{generation:e.generation}:{},...l.error?{error:l.error}:{}},l.ok&&(e.lastUsed=s.done.at);const n=Math.round((s.done.at-s.startedAt)/100)/10;l.ok?console.log(`[gateway] restarted: ${e.project} \u2014 g${s.done.generation??"?"} serving after ${n}s (${o}).`):console.error(`[gateway] restart failed: ${e.project} \u2014 ${l.error} (${o}, after ${n}s).`),S(e,l.ok?"info":"error",l.ok?`restarted: ${e.project}`:`restart failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:l.ok?"done":"failed",project:e.project,how:o,reason:r.reason,...s.fromGeneration!==void 0?{fromGeneration:s.fromGeneration}:{},...s.done.generation!==void 0?{generation:s.done.generation}:{},durationMs:s.done.at-s.startedAt,...l.error?{error:l.error}:{}})}),{restart:s,already:!1}},"restartService"),$e=a(e=>e.buildFailed?`its build failed${e.lastErrors?.length?`: ${e.lastErrors[e.lastErrors.length-1]}`:""}`:e.proc&&(e.proc.exitCode!==null||e.proc.signalCode!==null)?`its tree exited (${e.proc.exitCode??e.proc.signalCode})`:!e.proc&&e.state==="down"?"its tree exited before a pod answered":`no pod answered within ${Math.round(t.startupTimeoutMs/1e3)}s`,"startFailure"),xe=a(e=>{const r=Date.now();e.restarts=(e.restarts??[]).filter(s=>r-s<types_1.SERVICE_RESTART_WINDOW_MS);const o=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||o>types_1.SERVICE_MAX_RESTARTS){const s=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;S(e,"error",`service crash-looping: ${e.project} \u2014 ${s}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:o-1}),V(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=o,e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${o}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),S(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:o,inWindow:e.restarts.length}),V(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recycleWedged"),Je={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 _e;const se=new Map,Ee=new Map;let ke=!1,je=0,he=0;const Qe=a(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(s=>s.project));for(const s of t.routes)s.lens&&r.add(s.lens.project);const o=new Set;for(const s of r){const l=e[s];if(!l)continue;const n=path.relative(t.root,l).split(path.sep).filter(Boolean)[0];n&&!n.startsWith("..")&&o.add(n)}return[...o]},"managedBuckets"),Ze=a((e,r,o,s)=>{const l=t.projectSourceRoots?.[e];if(l?.length)return l;const n=t.projectRoots?.[e];if(!n)return;const d=sourceScopeDirs(t.root,n,o,r,s);return d.length>0?d:void 0},"scopeFor"),et=a(()=>{if(ke||Date.now()-je<types_1.POD_STALE_SCAN_MS)return;ke=!0,je=Date.now();const e=je;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),o=r.exclude,s={ignoreTests:r.ignoreTests},l=sourceScanPass(t.root,o,s),n=await l.snapshot(),d=Date.now(),u=sourceChangeWindowMs(d,he,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),g=_e?diffSourceSets(_e,n,d,u):null;if(g&&(t.sourceSetChangedAt=d,t.sourceSetChange=g,H("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:g.added,removed:g.removed,truncated:g.truncated})),_e=n,!t.projectRoots){he=e;return}let k;try{k=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(h=>h.isDirectory()).map(h=>h.name)}catch{return}const j=Qe(),v=new Set(t.services.map(h=>h.project));for(const h of t.routes)h.lens&&v.add(h.lens.project);const f=new Map;for(const h of v){const P=Ze(h,k,j,o);if(!P)continue;const x=P.join("|"),q=f.get(x)??{dirs:P,projects:[]};q.projects.push(h),f.set(x,q)}const R=Date.now(),M=sourceChangeWindowMs(R,he,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);he=e;const G=a((h,P,x)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const q of h)t.sourceSetChangedFor.set(q,P),t.sourceSetChangesFor.set(q,x)},"stampScoped"),ne=new Set([...f.values()].flatMap(h=>h.projects));for(const h of[...t.sourceSetScoped??[]])ne.has(h)||t.sourceSetScoped.delete(h);for(const[h,{dirs:P,projects:x}]of f){const q=await l.snapshot(P),Re=se.get(h);if(Re||x.some(K=>Ee.has(K))){t.sourceSetScoped??=new Set;for(const K of x)t.sourceSetScoped.add(K)}se.set(h,q);const Ae=new Map;for(const K of x){const U=Ee.get(K);if(U===void 0||U===h)continue;const ee=Ae.get(U)??[];ee.push(K),Ae.set(U,ee)}for(const K of x)Ee.set(K,h);for(const[K,U]of Ae){const ee=se.get(K),ce=ee?diffSourceSets(ee,q,R,M):g;ce&&(G(U,ee?R:d,ce),H("info",`source file set changed \u2014 ${U.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:U,added:ce.added,removed:ce.removed,truncated:ce.truncated}))}if(!Re)continue;const ae=diffSourceSets(Re,q,R,M);ae&&(G(x,R,ae),H("info",`source file set changed in ${x.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:x,added:ae.added,removed:ae.removed,truncated:ae.truncated}))}for(const h of[...se.keys()])f.has(h)||se.delete(h)})().catch(()=>{}).finally(()=>{ke=!1})},"maybeScanSourceSet"),tt=a((e,r)=>{const o=podChangeClock(t,e.project),s=o.change,l=`${o.scope==="project"?"in its scope":"in the workspace (its scope is not known)"}: ${changeEvidence(s)}`,n=s?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,d=podWatcherVerdict(e.watcher,r,n),u=a(k=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${k} \u2014 no recycle.`)),!1),"keep");if(d==="heard")return e.staleVerifiedAt=r,S(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:d}),u(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`);if(d==="degraded")return console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0;const g=a(k=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${k}; the change ${l} \u2014 recycling.`)),!0),"recycling");return d==="silent"?types_1.POD_WATCHER_SILENT==="keep"?u("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)"):g("watcher alive but quiet across the change"):types_1.POD_WATCHER_UNVERIFIED==="keep"?u("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):g("watcher unverifiable (no testimony yet \u2014 a pod that never rebuilt, or an older devserver bundle)")},"podStaleRecycleAllowed"),rt=a((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){me(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),S(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const o=!!e.decl.eager;V(e,`${r} \u2014 recycling stale pod`,{announce:!1}),o&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recyclePod"),ot=a(()=>{const e=setInterval(()=>{ct().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),st=i.listProcesses??(()=>{try{return(0,orphan_reaper_1.parsePsRows)((0,node_child_process_1.execFileSync)("ps",orphan_reaper_1.PS_ROW_ARGS,{encoding:"utf8",timeout:5e3}))}catch{return[]}}),Pe=i.signalProcess??((e,r)=>{process.kill(e,r)});let De=0;const nt=a(()=>{if(typeof process.getuid!="function")return 0;const e=t.registry?t.registry.allServices():t.services;if(e.length===0)return 0;const r=e.map(n=>({label:n.wsKey||path.basename(n.root),project:n.project,root:n.root,svc:n})),o=new Set;for(const n of e)n.proc?.pid&&o.add(n.proc.pid);for(const n of[...fe.keys(),...N.keys(),...I.keys()])n.pid&&o.add(n.pid);const s=[...new Set((0,process_name_1.defaultLinkDirs)().flatMap(n=>{try{return[n,fs.realpathSync(n)]}catch{return[n]}}))];let l=0;for(const n of(0,orphan_reaper_1.findOrphanExecutors)(st(),r,{linkDirs:s,uid:process.getuid(),selfPid:process.pid,ownGroups:o})){const{row:d}=n;try{Pe(n.group?-d.pgid:d.pid,"SIGKILL")}catch(g){if(g.code!=="ESRCH"||!n.group)continue;try{Pe(d.pid,"SIGKILL")}catch{continue}}l+=1;const u=n.owner.svc;console.log(`[gateway] reaped an orphaned executor of ${u.project} (${n.role}, pid ${d.pid}${n.group?`, group ${d.pgid}`:""}, parent gone) \u2014 a stopped tree it outlived: ${d.command.slice(0,160)}`),S(u,"warning",`orphan reap: ${u.project} (pid ${d.pid})`,`cluster-service:${u.project}`,{kind:"orphan-executor-reap",project:u.project,pid:d.pid,pgid:d.pgid,role:n.role,group:n.group})}return l},"reapOrphanExecutors"),ct=a(async()=>{{if(t.stopped())return;He(),i.reapForeign!==!1&&types_1.ORPHAN_REAP_EVERY_MS>0&&Date.now()-De>=types_1.ORPHAN_REAP_EVERY_MS&&(De=Date.now(),nt());for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&et();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=path.resolve(e.root)===path.resolve(t.root),o=(0,warm_start_1.serviceIdleSec)(t,e),s=(0,awake_1.idleStopAt)(e,o);if(s!==void 0&&e.state==="up"&&(!e.restart||e.restart.done)&&e.inflight===0&&Date.now()>s){e.lastIdleStop={at:Date.now(),why:`no request for ${o}s${(e.awakeUntil??0)>e.lastUsed?" after its hold ended":""}`},V(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const l=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,n=O(e.pool)>0;if(n||l){if(e.wedgedSince=void 0,n&&pe(e,"observed"),n&&!l&&types_1.POD_RECYCLE_ENABLED&&r){const d=podChangeClock(t,e.project).at;await be(e);const u=(e.staleVerifiedAt??0)>=d&&d>0,g=podRecycleReason(e,Date.now(),u?0:d,Je);if(g==="stale-source-set"&&!tt(e,d))continue;g&&rt(e,g)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,xe(e))}}},"sweepOnce");return{scanNow:O,spawnService:Y,ensureUp:we,scaleUp:Ke,killSpawned:V,markUp:pe,emitSvc:S,startSweeper:ot,rollPod:me,restartService:ze,recycleWedged:xe,stopRetiringTrees:Ye,finishDyingTrees:Fe,stopBuildHost:a(e=>e===void 0?_.stop():_.release(e),"stopBuildHost")}}m(createServiceLayer,"createServiceLayer"),a(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,i){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==i)return t}m(chooseStalePodKillTarget,"chooseStalePodKillTarget"),a(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var lt=Object.defineProperty;var _=(t,c)=>lt(t,"name",{value:c,configurable:!0});var it=Object.defineProperty,s=_((t,c)=>it(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,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.podChangeClock=podChangeClock,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")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),instance_1=require("./instance"),pod_sock_1=require("../../../pod-sock"),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"),own_log_1=require("./own-log"),inspector_secret_1=require("../../../inspector-secret"),pod_output_1=require("./pod-output"),serve_launch_1=require("./serve-launch"),shared_build_1=require("./shared-build"),awake_1=require("./awake"),warm_start_1=require("./warm-start"),protocol_1=require("../../../shared-build/protocol"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name"),orphan_reaper_1=require("./orphan-reaper");function eastWestEnv(t,c){const l={},p=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(p&&(l.LENSMCP_SERVICE_KEY=p),t.https){const y=require("../../../basic-ssl").caCertPath();fs.existsSync(y)&&(l.NODE_EXTRA_CA_CERTS=y)}return l}_(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(c=>setTimeout(c,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 c=2166136261;for(let l=0;l<t.length;l+=1)c^=t.charCodeAt(l),c=Math.imul(c,16777619);return c>>>0}_(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}_(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const l=path.join(t.pool.dir,"parent.sock");return c(l)?{socketPath:l}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}_(scaleTarget,"scaleTarget"),s(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}_(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,l){let p;if("socketPath"in t)p=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},y=>y.resume());else{const y=t.url.startsWith("https"),M=y?require("node:https"):http,j={method:"POST"};y&&isLoopbackHostname(new URL(t.url).hostname)&&(j.rejectUnauthorized=!1),l&&(j.lookup=l),p=M.request(`${t.url}/webpack/scale`,j,A=>A.resume())}return p.on("error",()=>{}),p.end(JSON.stringify({pods:c})),p}_(postScale,"postScale"),s(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}_(isOwnRunnerCommand,"isOwnRunnerCommand"),s(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,l){const p=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${p}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,l)}_(isRunnerOf,"isRunnerOf"),s(isRunnerOf,"isRunnerOf");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 c=t.split("/"),l=c[c.length-1]??"";if(TEST_FILE_RE.test(l))return!0;for(let p=0;p<c.length-1;p+=1)if(TEST_DIR_SEGMENTS.has(c[p]))return!0;return!1}_(isTestSourcePath,"isTestSourcePath"),s(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,l){const p=l?.ignoreTests??!0;let y;const M=new Map,j=`tree:${path.resolve(t)}`,A=s(m=>path.relative(t,m).split(path.sep).join("/"),"rel"),T=s(async(m,E)=>{const R=await(y??=gitIgnoredPaths(t)),D=[],F=new Set;let b=!1;const H=s(async(te,v)=>{if(D.length>=SCAN_FILE_CAP){b=!0;return}let O;try{O=await fs.promises.readdir(te,{withFileTypes:!0})}catch{return}v&&F.add(A(te));for(const z of O){if(D.length>=SCAN_FILE_CAP){b=!0;return}const W=z.name,re=path.join(te,W);if(z.isDirectory()){if(!v||W.startsWith(".")||SRC_EXCLUDE.has(W)||c?.has(W)||W.endsWith("-devserver")||R.has(`${A(re)}/`))continue;await H(re,!0)}else if(z.isFile()){const oe=W.lastIndexOf(".");if(oe<=0||!SRC_EXT.has(W.slice(oe)))continue;const J=A(re);if(R.has(J)||p&&isTestSourcePath(J))continue;D.push(J)}}},"walk");return await H(m,E),{paths:D,entered:F,truncated:b}},"walkDir"),L=s((m,E)=>{let R=M.get(m);return R||(R=E(),M.set(m,R)),R},"memo"),ce=s(async()=>{const m=M.get(j);if(!m)return;const E=await m;return E.truncated?void 0:E},"wideWalk"),B=new Set,le=s(()=>L(`files:${path.resolve(t)}`,async()=>{const m=await ce();return m?{paths:m.paths.filter(E=>!E.includes("/")),entered:B,truncated:!1}:T(t,!1)}),"rootFiles"),de=s(m=>L(`tree:${path.resolve(m)}`,async()=>{const E=await ce(),R=A(m);if(E?.entered.has(R)){const D=`${R}/`;return{paths:R===""?E.paths:E.paths.filter(F=>F.startsWith(D)),entered:B,truncated:!1}}return T(m,!0)}),"subtree"),X=s(m=>{const E=new Set;let R=0,D=m.some(b=>b.truncated);const F=s(b=>E.has(b)?!0:E.size>=SCAN_FILE_CAP?(D=!0,!1):(E.add(b),R^=fnv1a(b),!0),"add");for(const b of m)if(!b.paths.every(F))break;return{signature:`${E.size}:${(R>>>0).toString(36)}`,paths:E,truncated:D}},"compose");return{async snapshot(m){if(!m||m.length===0)return X([await L(j,()=>T(t,!0))]);const E=[await le()];for(const R of m)E.push(await de(R));return X(E)}}}_(sourceScanPass,"sourceScanPass"),s(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,l,p){return sourceScanPass(t,l,p).snapshot(c)}_(sourceSetSnapshot,"sourceSetSnapshot"),s(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,l,p){return(await sourceSetSnapshot(t,c,l,p)).signature}_(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,l,p){if(t.paths.size===c.paths.size){let A=!0;for(const T of t.paths)if(!c.paths.has(T)){A=!1;break}if(A)return null}const y=[],M=[];let j=t.truncated||c.truncated;for(const A of c.paths)t.paths.has(A)||(y.length+M.length<SOURCE_DIFF_PATH_CAP?y.push(A):j=!0);for(const A of t.paths)c.paths.has(A)||(y.length+M.length<SOURCE_DIFF_PATH_CAP?M.push(A):j=!0);return{changedAt:l,added:y,removed:M,truncated:j,...p!==void 0?{windowMs:p}:{}}}_(diffSourceSets,"diffSourceSets"),s(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,l,p){const y=c&&c>0?t-c:l;return Math.max(y,l)+p}_(sourceChangeWindowMs,"sourceChangeWindowMs"),s(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,l,p,y){const M=s(T=>{const L=path.relative(t,T).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(c);if(!M)return[];const j=new Set(l.filter(Boolean)),A=new Set([M]);for(const T of p)T.startsWith(".")||SRC_EXCLUDE.has(T)||y?.has(T)||T.endsWith("-devserver")||j.has(T)||A.add(T);return[...A].sort().map(T=>path.join(t,T))}_(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,l,p){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<p.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<p.cooldownMs?null:l>0&&l-t.spawnAt>p.graceMs?"stale-source-set":p.maxAgeMs>0&&c-t.spawnAt>p.maxAgeMs?"max-age":null}_(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,l,p,y){return!(p-c<y.cooldownMs||l<=0||l-t<=y.graceMs||p-l<y.settleMs)}_(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,l,p=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&p.length>0){const y=c-l,M=new Set(t.recentEvents.filter(j=>j.at>=y).map(j=>path.resolve(j.path)));return p.every(j=>M.has(path.resolve(j)))?"heard":"missed"}return t.lastFsSetEventAt>=c-l?"heard":"missed"}_(lensWatcherVerdict,"lensWatcherVerdict"),s(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,l){if(!t)return"unknown";if(t.degraded)return"degraded";const p=c-l;return t.lastWatchRunAt>=p||t.lastBuildAt>=p?"heard":"silent"}_(podWatcherVerdict,"podWatcherVerdict"),s(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,l,p){const y=t.hasListened?p.graceMs:Math.max(p.graceMs,p.neverListenedGraceMs);return l-t.spawnedAt<y||l-c<p.cooldownMs||t.probeFails<Math.max(2,p.failureThreshold)?null:"wedged-no-listener"}_(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}_(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function podChangeClock(t,c){const l=t.sourceSetChangedFor?.get(c);if(l!==void 0){const p=t.sourceSetChangesFor?.get(c);return{at:l,...p?{change:p}:{},scope:"project"}}return t.sourceSetScoped?.has(c)?{at:0,scope:"none"}:{at:t.sourceSetChangedAt??0,...t.sourceSetChange?{change:t.sourceSetChange}:{},scope:"workspace"}}_(podChangeClock,"podChangeClock"),s(podChangeClock,"podChangeClock");const changeEvidence=s(t=>{if(!t)return"no path evidence";const c=[...t.added.map(l=>`+${l}`),...t.removed.map(l=>`-${l}`)];return c.length?`${c.slice(0,3).join(", ")}${c.length>3?` (+${c.length-3} more)`:""}`:"no path evidence"},"changeEvidence");function createServiceLayer(t,c,l={}){const p=l.spawnProcess??node_child_process_1.spawn,y=l.probeAlive??pod_probe_1.probePodAlive,M=l.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),j=l.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),A=s((e,r="SIGTERM")=>{j.detach(e),M(e,r)},"killTree"),T=l.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),L=l.treeAlive??(e=>{if(!e.pid||!(e instanceof node_child_process_1.ChildProcess))return!1;try{return process.kill(-e.pid,0),!0}catch(r){return r.code==="EPERM"}}),ce=l.retirePollMs??1e3,B=l.rollReadyMs??types_1.POD_ROLL_READY_MS,le=l.rollPollMs??750,de=l.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,X=de>0?de:Number.POSITIVE_INFINITY,m=l.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,E=m>0?m:Number.POSITIVE_INFINITY;let R=0;const D=[],F=s(()=>R<E?(R+=1,Promise.resolve()):new Promise(e=>{D.push(()=>{R+=1,e()})}),"acquireColdSlot"),b=s(()=>{R-=1,D.shift()?.()},"releaseColdSlot"),{emit:H}=c,te=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),v=s((e,r,o,n,d)=>H(r,o,n,{wsKey:e.wsKey,...d},void 0,te(e)),"emitSvc"),O=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),z=s((e,r)=>{let o;try{o=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const n=e.proc?.pid;let d=0;for(const a of o.split(`
2
+ `)){const i=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!i)continue;const u=Number(i[1]),f=Number(i[2]);if(isRunnerOf(i[3],r,e.root)&&!(u===process.pid||u===n)&&!(n!==void 0&&f===n)){try{process.kill(-f,"SIGKILL")}catch{try{process.kill(u,"SIGKILL")}catch{}}d+=1}}return d>0&&(console.log(`[gateway] ${r}: reaped ${d} stale runner tree(s) \u2014 a foreign generation of this service (holding nx's task lock, or its pool)`),v(e,"warning",`stale-runner reap: ${e.project} (${d} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:d})),d},"reapForeignRunners"),W=s((e,r)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),...e.instanceEnv?{...e.instanceEnv,LENSMCP_SOCKET_ONLY:"1"}:{},...e.frozen?{LENSMCP_FREEZE:"1",LENSMCP_TYPECHECK:"off"}:{},NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},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)}),"serviceEnv"),re=s((e,r,o,n)=>{const d=W(e,r),a=e.wsKey||path.basename(e.root),i=(0,serve_launch_1.planServeLaunch)({stores:t.graphStores,root:e.root,project:e.project,target:e.decl.serveTarget??"serve-hmr",childCount:n,baseEnv:d});if(i.kind==="runner"){const $=j.offeredTo(e),h=p((0,process_name_1.namedExecPath)((0,process_name_1.processName)(a,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...i.script.execArgv,i.script.path,...(0,serve_launch_1.serveRunnerArgs)(o,n,e.root)],{cwd:e.root,detached:!0,env:$?{...i.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:i.env,stdio:["ignore","pipe","pipe","ipc"]});return $&&j.attach(e,r,h),h.send?.(i.message,k=>{k&&console.error(`[gateway] ${o}: could not hand the task to its runner (${k.message})`)}),{proc:h,launcher:"runner"}}console.log(`[gateway] ${o}: starting through nx run \u2014 ${i.reason}`);const u=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),f=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(a,e.project,"nx")),[g,w]=fs.existsSync(u)?[f,[u,"run",o,`--childCount=${n}`]]:["yarn",["nx","run",o,`--childCount=${n}`]];return{proc:p(g,w,{cwd:e.root,detached:!0,env:d,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),oe=new WeakMap,J=s((e,r,o)=>{const n=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,d=Math.max(1,e.decl.minPods??1),{proc:a,launcher:i}=re(e,r,n,d);oe.set(a,i),o==="active"&&(e.launcher=i),o==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const u=s(w=>{const $=w.toString();for(const h of $.split(`
3
+ `)){const k=(0,pod_output_1.classifyPodLine)(h),{clean:C,broken:G,healthy:ne}=k,S=C;if(G){e.lastErrors.push(C),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();(C!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=C,e.lastErrorEmitAt=x,v(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:C,fault:k.fault}))}ne&&(e.buildFailed=!1);const P=(e.faults??=new pod_output_1.PodFaults).observe(k);if(P){e.lastErrorEmitSig=void 0;const x=P.open.length===0;v(e,"info",`${x?"service recovered":`${P.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:x?"service-recovered":"service-fault-closed",project:e.project,line:C,closed:P.closed,open:P.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(S)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${n}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),v(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||(V(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),z(e,n),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.())},250).unref?.())}},"sniff"),f=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,g=s(w=>{const $=(0,own_log_1.linePrefixer)(e.project);return h=>{if(t.stopped())return;const k=(0,inspector_secret_1.redactInspectorUrls)(h.toString());u(k);const C=$(k);w.write(C),f?.(C)}},"relay");return a.stdout?.on("data",g(process.stdout)),a.stderr?.on("data",g(process.stderr)),a.on("exit",(w,$)=>{if(e.proc!==a||(e.proc=void 0,e.state="down",t.stopped()))return;const h=(0,exit_reason_1.describeExit)(w,$);if(console.log(`[gateway] ${n} exited (${w??"no code"}${$?`, ${$}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${h?` \u2014 ${h}`:""}`),v(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 k=Date.now();if(e.restarts=(e.restarts??[]).filter(G=>k-G<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){v(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(k);const C=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})`),v(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),Y(e))},C)}),a},"launchServiceProcess"),Y=s(e=>{if(t.stopped()||e.manualStopped)return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,o=Math.max(1,e.decl.minPods??1);l.reapForeign!==!1&&z(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${o})\u2026`),v(e,"info",`service starting: ${e.project} (${o} pod${o>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:o}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=J(e,e.generation,"active")},"spawnService"),De=s(async e=>{const r=e.pool.socks[0];if(!r)return;const o=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);o&&(e.activeWork=o.activeWork,o.watcher&&(e.watcher=o.watcher))},"refreshActiveWork"),Le=s(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),Ie=s(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const o=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),n=[];for(const d of e.pool.socks){const a=await(0,pod_probe_1.probePodAlive)(d,void 0,r);a&&(!a.keyFingerprint||a.keyFingerprint!==o)&&n.push({sock:d,pid:a.pid})}return n},"staleAdoptedSocks"),Oe=s((e,r)=>{const o=r.map(n=>n.sock);for(const{sock:n,pid:d}of r){const a=chooseStalePodKillTarget(d,Le(n),process.pid);if(a!==void 0)try{process.kill(a,"SIGTERM")}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(`${n}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(n=>!o.includes(n)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),pe=s((e,r)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const o=e.pool.socks.length;return v(e,"info",`service up: ${e.project} (${o} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:o,adopted:r==="adopted"}),!0},"markUp"),we=s(async e=>{if(e.manualStopped)return!1;if(O(e.pool)>0){const n=await Ie(e);if(e.manualStopped)return!1;if(n.length>0&&(console.error(`[gateway] ${e.project}: ${n.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),v(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:n.length}),e.proc&&V(e,"stale adoption (foreign east-west key)"),Oe(e,n)),e.pool.socks.length>0)return pe(e,"adopted"),!0}if(t.stopped())return!1;let r=!1;const o=s(()=>e.state!=="starting"&&!e.proc,"notStarted");if(o())if(e.coldWait)await e.coldWait;else{R>=E&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${R} starting, ${D.length} ahead (LENSMCP_MAX_COLD_STARTS=${E}).`),v(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:R,ahead:D.length}));const n=F();if(e.coldWait=n,await n,e.coldWait=void 0,r=!0,t.stopped()||e.manualStopped)return b(),!1;o()&&Y(e)}try{return await Ne(e)}finally{r&&b()}},"ensureUp"),Ne=s(async e=>{const r=Date.now()+t.startupTimeoutMs,o=Date.now()+12e3;for(;!t.stopped()&&!e.manualStopped&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.manualStopped||e.buildFailed&&Date.now()>o)return!1;if(O(e.pool)>0){e.state="up",e.buildFailed=!1;const n=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${n?`, cold start ${n}ms`:""})`),v(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${n??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:n}),!0}if(e.state==="down")return!1}return!1},"waitForPods"),Ke=s(e=>{const r=e.decl.maxPods??e.decl.minPods??1,o=e.pool.socks.length,n=scaleTarget(e);!n||o===0||o>=r||e.inflight<=o||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(n,o+1,l.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${o} \u2192 ${o+1} pods (inflight ${e.inflight})`),v(e,"info",`autoscale: ${e.project} ${o} \u2192 ${o+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:o,to:o+1,inflight:e.inflight}))},"scaleUp"),ue=l.killGraceMs??types_1.TREE_KILL_GRACE_MS,N=new Map,Ce=s(e=>{const r=N.get(e);if(!r||(N.delete(e),clearTimeout(r.timer),!L(e)))return;const{svc:o,why:n}=r;console.log(`[gateway] ${o.project}: its tree (pid ${e.pid}) was still running ${Math.round(ue/1e3)}s after it was told to stop (${n}) \u2014 killing it (SIGKILL).`),v(o,"warning",`tree force-killed: ${o.project} (it ignored SIGTERM)`,`cluster-service:${o.project}`,{kind:"tree-force-killed",project:o.project,why:n,pid:e.pid,graceMs:ue}),M(e,"SIGKILL")},"settleDyingTree"),ve=s((e,r,o)=>{if(A(r,"SIGTERM"),ue<=0||N.has(r))return;const n=setTimeout(()=>Ce(r),ue);n.unref?.(),N.set(r,{svc:e,why:o,timer:n})},"stopTree"),Fe=s(async(e=3e3)=>{const r=Date.now()+e;for(;N.size>0;){for(const[o,n]of[...N])L(o)||(clearTimeout(n.timer),N.delete(o));if(N.size===0||Date.now()>=r)break;await sleep(100)}for(const o of[...N.keys()])Ce(o)},"finishDyingTrees"),V=s((e,r,o)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),o?.announce!==!1&&v(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const n=e.proc;try{n.stdout?.removeAllListeners("data"),n.stderr?.removeAllListeners("data"),n.removeAllListeners("exit")}catch{}(0,discovery_1.stopRoutingTo)(e.pool),ve(e,n,r),e.proc=void 0,e.state="down",setTimeout(()=>{ge(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ge=s(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const o=path.join(e.pool.dir,r);try{const n=Number(fs.readFileSync(`${o}.owner`,"utf8").trim());if(Number.isFinite(n)&&n>0)try{process.kill(n,0);continue}catch{}}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let Q=0;const Se=[],We=s(()=>Q<X?(Q+=1,Promise.resolve()):new Promise(e=>{Se.push(()=>{Q+=1,e()})}),"acquireRollSlot"),Ge=s(()=>{Q-=1,Se.shift()?.()},"releaseRollSlot"),ye=s(async(e,r,o)=>{O(e.pool);for(const n of e.pool.socks){const d=await y(n,void 0,o);if(d&&r(d.podGeneration))return!0}return!1},"anyPodAnswers"),he=new Map,qe=s((e,r,o)=>{const n=T>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,o);if(A(r,"SIGTERM"),!n)return;const d=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,a=s(ne=>S=>{if(t.stopped())return;const P=(0,inspector_secret_1.redactInspectorUrls)(S.toString()).replace(/^(?=.)/gm,`[${e.project} g${o} retiring] `);ne.write(P),d?.(P)},"say"),i=a(process.stdout),u=a(process.stderr);r.stdout?.on("data",i),r.stderr?.on("data",u);let f,g=!1;const w=s(()=>{g=!0,clearTimeout(f),r.stdout?.removeListener("data",i),r.stderr?.removeListener("data",u),r.removeListener("close",w)},"releaseLogs");r.once("close",w);let $=!1;const h=s(()=>{$||($=!0,clearTimeout(k),clearInterval(G),t.stopped()?w():g||(f=setTimeout(w,1e3),f.unref?.()),he.delete(r),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,o),ge(e),e.pool.scannedAt=0)},"end"),k=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${o} was still running ${T}ms after it was retired \u2014 killing it.`),M(r,"SIGKILL"),h()},T);k.unref?.();const C=s(()=>{if(!$){if(t.stopped()){h();return}L(r)||(console.log(`[gateway] pod handoff: ${e.project} g${o} finished its in-flight work and exited.`),h())}},"look"),G=setInterval(C,ce);G.unref?.(),r.once("exit",C),he.set(r,()=>{h(),ve(e,r,"the gateway is stopping")})},"retireReplacedTree"),Ve=l.standbyGraceMs??3e4,I=new Map,Ue=s((e,r,o,n,d)=>{const a=d+B+Ve,i=setTimeout(()=>Me(r),Math.max(0,a-Date.now()));i.unref?.(),I.set(r,{svc:e,generation:o,reason:n,beganAt:d,deadlineAt:a,timer:i})},"holdStandby"),Be=s(e=>{const r=I.get(e);r&&(clearTimeout(r.timer),I.delete(e))},"releaseStandby"),Z=s((e,r)=>{const o=I.get(e);if(!o)return!1;clearTimeout(o.timer),I.delete(e);const{svc:n,generation:d}=o;try{e.stdout?.removeAllListeners("data"),e.stderr?.removeAllListeners("data")}catch{}return(0,discovery_1.stopRoutingTo)(n.pool,a=>(0,pod_sock_1.podSockGeneration)(a)===d),ve(n,e,`standby g${d}: ${r}`),setTimeout(()=>ge(n),1200).unref?.(),!0},"endStandby"),Te=s((e,r)=>{const o=r.oldServing?`old generation g${r.fromGen} kept serving`:`and the old generation g${r.fromGen} is NOT serving (${r.oldGone?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;console.error(`[gateway] pod handoff failed: ${e.project} g${r.generation}: ${r.why} \u2014 ${o}.`),e.rollFailure=`${r.why} \u2014 ${o}`,v(e,r.oldServing?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r.reason,generation:r.generation,error:r.why,durationMs:Date.now()-r.beganAt,oldServing:r.oldServing})},"reportStandbyFailed"),Me=s(e=>{const r=I.get(e);if(!r)return;const{svc:o,generation:n,reason:d,beganAt:a}=r,i=`the standby outlived its roll (${Math.round((Date.now()-a)/1e3)}s; a roll waits ${Math.round(B/1e3)}s for it)`;Z(e,i);const u=o.proc,f=t.serviceKeys[(0,service_keys_1.serviceKeyId)(o.wsKey,o.project)];(async()=>{const g=!u||u.exitCode!==null||u.signalCode!==null,w=g?!1:await Promise.race([ye(o,h=>h!==n,f).catch(()=>!1),sleep(2e3).then(()=>{})]),$=!t.stopped()&&!g&&(w??O(o.pool)>0);Te(o,{reason:d,generation:n,fromGen:o.generation??n-1,why:i,beganAt:a,oldServing:$,oldGone:g})})()},"outlivedRoll"),He=s(()=>{for(const[e,r]of[...I])Date.now()>=r.deadlineAt?Me(e):!r.svc.rolling&&r.svc.proc!==e&&(console.error(`[gateway] pod handoff: ${r.svc.project} g${r.generation} is a standby whose roll is over \u2014 stopping it.`),Z(e,"its roll is over"))},"sweepStandbys"),Ye=s(()=>{for(const e of[...he.values()])e();for(const e of[...I.keys()])Z(e,"the gateway is stopping")},"stopRetiringTrees"),$e=s((e,r)=>{if(e.rolling)return e.rolling;const o=e.proc;if(!o||t.stopped())return Promise.resolve(!1);if(e.launcher==="nx")return console.log(`[gateway] pod handoff skipped: ${e.project} runs through nx run, which starts one instance of a target at a time \u2014 no standby can overlap it (reason=${r}).`),Promise.resolve(!1);Q>=X&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${r} \u2014 ${Q} roll(s) in flight, ${Se.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${X}).`);const n=(async()=>{await We();try{return await Xe(e,o,r)}finally{Ge()}})().finally(()=>{e.rolling=void 0});return e.rolling=n,n},"rollPod"),Xe=s(async(e,r,o)=>{const n=e.proc;if(n!==r||t.stopped())return!1;const d=e.generation??0,a=d+1,i=Date.now();e.lastStaleRecycleAt=i,console.log(`[gateway] pod handoff start: ${e.project} g${d}\u2192g${a} reason=${o} \u2014 standby spawning, old generation keeps serving.`),v(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:a,fromGeneration:d});const u=J(e,a,"standby"),f=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];Ue(e,u,a,o,i);let g=!1;try{const w=i+B;let $;for(;!t.stopped()&&Date.now()<w&&I.has(u)&&!(u.exitCode!==null||u.signalCode!==null);){O(e.pool);for(const h of e.pool.socks)if((await y(h,void 0,f))?.podGeneration===a){$=h;break}if($)break;await sleep(le)}if(!I.has(u))return!1;if(!$||t.stopped()){const h=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(B/1e3)}s`;Z(u,h);const k=!t.stopped()&&e.proc===n&&n.exitCode===null&&n.signalCode===null&&await ye(e,C=>C!==a,f);return Te(e,{reason:o,generation:a,fromGen:d,why:h,beganAt:i,oldServing:k,oldGone:e.proc!==n||n.exitCode!==null||n.signalCode!==null}),!1}if(e.proc!==n)return Z(u,"the service was replaced underneath the roll"),!1;Be(u),g=!0,e.proc=u,e.launcher=oe.get(u),e.generation=a,e.spawnAt=i,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{n.stdout?.removeAllListeners("data"),n.stderr?.removeAllListeners("data"),n.removeAllListeners("exit")}catch{}return(0,discovery_1.stopRoutingTo)(e.pool,h=>(0,pod_sock_1.podSockGeneration)(h)!==a&&path.join(e.pool.dir,h)!==$),qe(e,n,d),setTimeout(()=>{ge(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${a} answered on ${path.basename($)} in ${Date.now()-i}ms; retiring g${d}.`),v(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:a,fromGeneration:d,durationMs:Date.now()-i}),!0}finally{g||Z(u,"its roll ended without it")}},"runRoll"),ze=s((e,r)=>{if(t.stopped())return{refused:"the gateway is stopping"};const o=e.restart&&!e.restart.done?e.restart:void 0,n=o?void 0:Je(e);if(n&&"refused"in n)return console.error(`[gateway] restart refused: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${n.refused}`),v(e,"warning",`restart refused: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"refused",project:e.project,reason:r.reason,error:n.refused}),{refused:n.refused};const d=n&&"applied"in n?n:void 0;if(e.manualStopped=!1,e.actionFailure=void 0,o)return{restart:o,already:!0};if(!e.proc&&e.state!=="down")return{refused:`${e.project} is served by pods this gateway adopted from an earlier one \u2014 it has no tree of its own to replace. \`lensmcp gateway restart\` starts every service under this gateway.`};const a=e.proc?r.hard||e.launcher==="nx"||e.state!=="up"?"hard":"rolling":"start",i={how:a,reason:r.reason,startedAt:Date.now(),...e.proc&&e.generation!==void 0?{fromGeneration:e.generation}:{},...d?{env:d.applied}:{}};d&&(e.instanceEnv=d.env),e.restart=i,e.lastUsed=i.startedAt;const u=i.env?{envFingerprint:i.env.fingerprint,envChanged:i.env.changed}:{},f=i.env?` Instance variables ${i.env.fingerprint}: ${i.env.changed.length?`changed ${i.env.changed.join(", ")}`:"unchanged"}.`:"";return console.log(`[gateway] restart asked: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${a} (${r.reason}).${f}`),v(e,"info",`restart asked: ${e.project} (${a})`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"started",project:e.project,how:a,reason:r.reason,...i.fromGeneration!==void 0?{fromGeneration:i.fromGeneration}:{},...u}),s(async()=>{if(a==="start")return await we(e)?{ok:!0}:{ok:!1,error:me(e)};if(a==="rolling")return e.rolling&&await e.rolling,e.proc?(e.rollFailure=void 0,await $e(e,r.reason)?{ok:!0}:{ok:!1,error:e.rollFailure??"the new tree did not take over"}):await we(e)?{ok:!0}:{ok:!1,error:me(e)};if(V(e,`restart asked (${r.reason})`,{announce:!1}),await sleep(1500),t.stopped())return{ok:!1,error:"the gateway is stopping"};let g=!1;e.proc||(await F(),g=!0,!e.proc&&e.state!=="starting"&&Y(e));try{const w=e.proc,$=e.generation,h=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],k=Date.now()+t.startupTimeoutMs;for(;!t.stopped()&&Date.now()<k;){if(!w||e.proc!==w||w.exitCode!==null||w.signalCode!==null)return{ok:!1,error:me(e)};if(await ye(e,C=>C===$,h))return pe(e,"observed"),e.buildFailed=!1,{ok:!0};await sleep(le)}return{ok:!1,error:t.stopped()?"the gateway is stopping":`no pod of the new tree answered within ${Math.round(t.startupTimeoutMs/1e3)}s`}}finally{g&&b()}},"run")().catch(g=>({ok:!1,error:g.message})).then(g=>{i.done={ok:g.ok,at:Date.now(),...e.generation!==void 0?{generation:e.generation}:{},...g.error?{error:g.error}:{}},g.ok&&(e.lastUsed=i.done.at);const w=Math.round((i.done.at-i.startedAt)/100)/10;g.ok?console.log(`[gateway] restarted: ${e.project} \u2014 g${i.done.generation??"?"} serving after ${w}s (${a}).`):console.error(`[gateway] restart failed: ${e.project} \u2014 ${g.error} (${a}, after ${w}s).`),v(e,g.ok?"info":"error",g.ok?`restarted: ${e.project}`:`restart failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:g.ok?"done":"failed",project:e.project,how:a,reason:r.reason,...i.fromGeneration!==void 0?{fromGeneration:i.fromGeneration}:{},...i.done.generation!==void 0?{generation:i.done.generation}:{},durationMs:i.done.at-i.startedAt,...g.error?{error:g.error}:{},...u})}),{restart:i,already:!1}},"restartService"),Je=s(e=>{if(!e.instanceBuiltins)return;const r=(0,instance_1.readInstanceEnvStrict)(e.root,e.instanceBuiltins);if("reason"in r)return{refused:`the instance variables of ${e.project} could not be read again \u2014 ${r.reason}. Nothing was restarted: the running tree keeps what it has. Fix the file, then restart it again.`};const o=e.instanceEnv??{};return{env:r.env,applied:{fingerprint:(0,instance_1.instanceEnvFingerprint)(r.env),fromFingerprint:(0,instance_1.instanceEnvFingerprint)(o),changed:(0,instance_1.changedInstanceEnvNames)(o,r.env)}}},"restartEnv"),me=s(e=>e.buildFailed?`its build failed${e.lastErrors?.length?`: ${e.lastErrors[e.lastErrors.length-1]}`:""}`:e.proc&&(e.proc.exitCode!==null||e.proc.signalCode!==null)?`its tree exited (${e.proc.exitCode??e.proc.signalCode})`:!e.proc&&e.state==="down"?"its tree exited before a pod answered":`no pod answered within ${Math.round(t.startupTimeoutMs/1e3)}s`,"startFailure"),xe=s(e=>{const r=Date.now();e.restarts=(e.restarts??[]).filter(n=>r-n<types_1.SERVICE_RESTART_WINDOW_MS);const o=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||o>types_1.SERVICE_MAX_RESTARTS){const n=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;v(e,"error",`service crash-looping: ${e.project} \u2014 ${n}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:o-1}),V(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=o,e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${o}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),v(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:o,inWindow:e.restarts.length}),V(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recycleWedged"),Qe={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 _e;const se=new Map,Ee=new Map;let ke=!1,je=0,fe=0;const Ze=s(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(n=>n.project));for(const n of t.routes)n.lens&&r.add(n.lens.project);const o=new Set;for(const n of r){const d=e[n];if(!d)continue;const a=path.relative(t.root,d).split(path.sep).filter(Boolean)[0];a&&!a.startsWith("..")&&o.add(a)}return[...o]},"managedBuckets"),et=s((e,r,o,n)=>{const d=t.projectSourceRoots?.[e];if(d?.length)return d;const a=t.projectRoots?.[e];if(!a)return;const i=sourceScopeDirs(t.root,a,o,r,n);return i.length>0?i:void 0},"scopeFor"),tt=s(()=>{if(ke||Date.now()-je<types_1.POD_STALE_SCAN_MS)return;ke=!0,je=Date.now();const e=je;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),o=r.exclude,n={ignoreTests:r.ignoreTests},d=sourceScanPass(t.root,o,n),a=await d.snapshot(),i=Date.now(),u=sourceChangeWindowMs(i,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),f=_e?diffSourceSets(_e,a,i,u):null;if(f&&(t.sourceSetChangedAt=i,t.sourceSetChange=f,H("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:f.added,removed:f.removed,truncated:f.truncated})),_e=a,!t.projectRoots){fe=e;return}let g;try{g=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(S=>S.isDirectory()).map(S=>S.name)}catch{return}const w=Ze(),$=new Set(t.services.map(S=>S.project));for(const S of t.routes)S.lens&&$.add(S.lens.project);const h=new Map;for(const S of $){const P=et(S,g,w,o);if(!P)continue;const x=P.join("|"),q=h.get(x)??{dirs:P,projects:[]};q.projects.push(S),h.set(x,q)}const k=Date.now(),C=sourceChangeWindowMs(k,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);fe=e;const G=s((S,P,x)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const q of S)t.sourceSetChangedFor.set(q,P),t.sourceSetChangesFor.set(q,x)},"stampScoped"),ne=new Set([...h.values()].flatMap(S=>S.projects));for(const S of[...t.sourceSetScoped??[]])ne.has(S)||t.sourceSetScoped.delete(S);for(const[S,{dirs:P,projects:x}]of h){const q=await d.snapshot(P),Re=se.get(S);if(Re||x.some(K=>Ee.has(K))){t.sourceSetScoped??=new Set;for(const K of x)t.sourceSetScoped.add(K)}se.set(S,q);const Ae=new Map;for(const K of x){const U=Ee.get(K);if(U===void 0||U===S)continue;const ee=Ae.get(U)??[];ee.push(K),Ae.set(U,ee)}for(const K of x)Ee.set(K,S);for(const[K,U]of Ae){const ee=se.get(K),ie=ee?diffSourceSets(ee,q,k,C):f;ie&&(G(U,ee?k:i,ie),H("info",`source file set changed \u2014 ${U.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:U,added:ie.added,removed:ie.removed,truncated:ie.truncated}))}if(!Re)continue;const ae=diffSourceSets(Re,q,k,C);ae&&(G(x,k,ae),H("info",`source file set changed in ${x.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:x,added:ae.added,removed:ae.removed,truncated:ae.truncated}))}for(const S of[...se.keys()])h.has(S)||se.delete(S)})().catch(()=>{}).finally(()=>{ke=!1})},"maybeScanSourceSet"),rt=s((e,r)=>{const o=podChangeClock(t,e.project),n=o.change,d=`${o.scope==="project"?"in its scope":"in the workspace (its scope is not known)"}: ${changeEvidence(n)}`,a=n?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,i=podWatcherVerdict(e.watcher,r,a),u=s(g=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${g} \u2014 no recycle.`)),!1),"keep");if(i==="heard")return e.staleVerifiedAt=r,v(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:i}),u(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`);if(i==="degraded")return console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0;const f=s(g=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${g}; the change ${d} \u2014 recycling.`)),!0),"recycling");return i==="silent"?types_1.POD_WATCHER_SILENT==="keep"?u("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)"):f("watcher alive but quiet across the change"):types_1.POD_WATCHER_UNVERIFIED==="keep"?u("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):f("watcher unverifiable (no testimony yet \u2014 a pod that never rebuilt, or an older devserver bundle)")},"podStaleRecycleAllowed"),ot=s((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){$e(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),v(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const o=!!e.decl.eager;V(e,`${r} \u2014 recycling stale pod`,{announce:!1}),o&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recyclePod"),st=s(()=>{const e=setInterval(()=>{ct().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),nt=l.listProcesses??(()=>{try{return(0,orphan_reaper_1.parsePsRows)((0,node_child_process_1.execFileSync)("ps",orphan_reaper_1.PS_ROW_ARGS,{encoding:"utf8",timeout:5e3}))}catch{return[]}}),Pe=l.signalProcess??((e,r)=>{process.kill(e,r)});let be=0;const at=s(()=>{if(typeof process.getuid!="function")return 0;const e=t.registry?t.registry.allServices():t.services;if(e.length===0)return 0;const r=e.map(a=>({label:a.wsKey||path.basename(a.root),project:a.project,root:a.root,svc:a})),o=new Set;for(const a of e)a.proc?.pid&&o.add(a.proc.pid);for(const a of[...he.keys(),...N.keys(),...I.keys()])a.pid&&o.add(a.pid);const n=[...new Set((0,process_name_1.defaultLinkDirs)().flatMap(a=>{try{return[a,fs.realpathSync(a)]}catch{return[a]}}))];let d=0;for(const a of(0,orphan_reaper_1.findOrphanExecutors)(nt(),r,{linkDirs:n,uid:process.getuid(),selfPid:process.pid,ownGroups:o})){const{row:i}=a;try{Pe(a.group?-i.pgid:i.pid,"SIGKILL")}catch(f){if(f.code!=="ESRCH"||!a.group)continue;try{Pe(i.pid,"SIGKILL")}catch{continue}}d+=1;const u=a.owner.svc;console.log(`[gateway] reaped an orphaned executor of ${u.project} (${a.role}, pid ${i.pid}${a.group?`, group ${i.pgid}`:""}, parent gone) \u2014 a stopped tree it outlived: ${i.command.slice(0,160)}`),v(u,"warning",`orphan reap: ${u.project} (pid ${i.pid})`,`cluster-service:${u.project}`,{kind:"orphan-executor-reap",project:u.project,pid:i.pid,pgid:i.pgid,role:a.role,group:a.group})}return d},"reapOrphanExecutors"),ct=s(async()=>{{if(t.stopped())return;He(),l.reapForeign!==!1&&types_1.ORPHAN_REAP_EVERY_MS>0&&Date.now()-be>=types_1.ORPHAN_REAP_EVERY_MS&&(be=Date.now(),at());for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&tt();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=path.resolve(e.root)===path.resolve(t.root),o=(0,warm_start_1.serviceIdleSec)(t,e),n=(0,awake_1.idleStopAt)(e,o);if(n!==void 0&&e.state==="up"&&(!e.restart||e.restart.done)&&e.inflight===0&&Date.now()>n){e.lastIdleStop={at:Date.now(),why:`no request for ${o}s${(e.awakeUntil??0)>e.lastUsed?" after its hold ended":""}`},V(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const d=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,a=O(e.pool)>0;if(a||d){if(e.wedgedSince=void 0,a&&pe(e,"observed"),a&&!d&&types_1.POD_RECYCLE_ENABLED&&r){const i=podChangeClock(t,e.project).at;await De(e);const u=(e.staleVerifiedAt??0)>=i&&i>0,f=podRecycleReason(e,Date.now(),u?0:i,Qe);if(f==="stale-source-set"&&!rt(e,i))continue;f&&ot(e,f)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,xe(e))}}},"sweepOnce");return{scanNow:O,spawnService:Y,ensureUp:we,scaleUp:Ke,killSpawned:V,markUp:pe,emitSvc:v,startSweeper:st,rollPod:$e,restartService:ze,recycleWedged:xe,stopRetiringTrees:Ye,finishDyingTrees:Fe,stopBuildHost:s(e=>e===void 0?j.stop():j.release(e),"stopBuildHost")}}_(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,l){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==l)return t}_(chooseStalePodKillTarget,"chooseStalePodKillTarget"),s(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The gateway's half of the shared compiler (issues/open/pod-memory-05): it starts the workspace's BUILD HOST
2
+ * The gateway's half of the shared compiler (issues/closed/pod-memory-05): it starts the workspace's BUILD HOST
3
3
  * (build-host.ts — one webpack process for every service that can share it) and relays between it and each
4
4
  * service's runner, over the two IPC channels it already owns. The runners never see the host and the host
5
5
  * never sees a runner: no socket, no address, nothing another account could connect to.
@@ -295,6 +295,9 @@ export interface ServiceRestart {
295
295
  startedAt: number;
296
296
  /** The pod generation it replaces (none when it was down). */
297
297
  fromGeneration?: number;
298
+ /** A worktree instance's service only: the variables its new tree is started with, read again from the instance's
299
+ * files when the restart was asked ({@link ServiceRestartEnv}). Never a value. */
300
+ env?: ServiceRestartEnv;
298
301
  /** Set once it settled: a pod of `generation` answers (`ok`), or why not. */
299
302
  done?: {
300
303
  ok: boolean;
@@ -303,6 +306,19 @@ export interface ServiceRestart {
303
306
  error?: string;
304
307
  };
305
308
  }
309
+ /**
310
+ * What a restart of a worktree instance's service applied (issues/closed/foodguard-127). The gateway holds the instance's
311
+ * variables — its hosts, `instance.resources.env`, `instance.env` — and gives them to every tree it starts; a restart
312
+ * reads the files again, so an edit to them takes. These say which variables, never what they are.
313
+ */
314
+ export interface ServiceRestartEnv {
315
+ /** instance.ts `instanceEnvFingerprint` of the variables the new tree is started with. */
316
+ fingerprint: string;
317
+ /** The same over what the service had before. Equal to `fingerprint` when nothing changed. */
318
+ fromFingerprint: string;
319
+ /** The names of the variables added, removed or changed since then — sorted. */
320
+ changed: string[];
321
+ }
306
322
  export interface ServiceCtl {
307
323
  project: string;
308
324
  decl: ClusterProjectDecl;
@@ -317,8 +333,12 @@ export interface ServiceCtl {
317
333
  * `sockNs`): passed as `LENSMCP_SOCK_NS`, and the pool is `$TMPDIR/<sockNs>/<svc>-devserver`. */
318
334
  sockNs?: string;
319
335
  /** A worktree instance's variables (instance.ts `InstanceRouting.env`): its hosts and `instance.env`, injected
320
- * into this service's tree under the gateway's own variables. */
336
+ * into this service's tree under the gateway's own variables. Read at registration; a restart of this service
337
+ * (lifecycle.ts `restartService`) reads the files again and replaces this one's — no other service's. */
321
338
  instanceEnv?: Readonly<Record<string, string>>;
339
+ /** The built-ins those variables were layered over at discovery (instance.ts `instanceBuiltinEnv`: the instance's
340
+ * name, apexes and hosts) — what a restart reads the files over again. Set exactly when `instanceEnv` is. */
341
+ instanceBuiltins?: Readonly<Record<string, string>>;
322
342
  /** Set while this service waits for a cold-start slot ({@link MAX_COLD_STARTS}): later requests wait on it
323
343
  * rather than queueing a second start. */
324
344
  coldWait?: Promise<void>;
@@ -879,7 +899,7 @@ export interface GatewayRuntime {
879
899
  * each current. Absent when the gateway was started without a graph — every service then starts through `nx run`. */
880
900
  readonly graphStores?: ProjectGraphStores;
881
901
  /** The gateway's `sharedCompiler` option — one webpack compilation for this workspace's services
882
- * (shared-build.ts, issues/open/pod-memory-05). `LENSMCP_SHARED_COMPILER` wins over it. */
902
+ * (shared-build.ts, issues/closed/pod-memory-05). `LENSMCP_SHARED_COMPILER` wins over it. */
883
903
  readonly sharedCompiler?: boolean;
884
904
  /** Managed lens frontend handoff state, installed by the child supervisor. */
885
905
  lensSupervisor?: LensFrontendSupervisor;
@@ -19,7 +19,7 @@ interface ServeResult {
19
19
  *
20
20
  * Owns the full devserver lifecycle:
21
21
  * 1. Starts webpack in watch mode — or, under a gateway running the shared compiler, joins the workspace's ONE
22
- * compilation (shared-build/attach.ts, issues/open/pod-memory-05) and is told about each build of its bundle
22
+ * compilation (shared-build/attach.ts, issues/closed/pod-memory-05) and is told about each build of its bundle
23
23
  * 2. On first successful build, starts main.devserver.js with proper env vars — in this process under the gateway's
24
24
  * serve runner (devserver-host.ts), forked otherwise
25
25
  * 3. On subsequent builds, POSTs /webpack/reload to devserver — attached: only when ITS bundle changed
@@ -1 +1 @@
1
- "use strict";var me=Object.defineProperty;var i=(e,o)=>me(e,"name",{value:o,configurable:!0});var pe=Object.defineProperty,s=i((e,o)=>pe(e,"name",{value:o,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_host_1=require("../../devserver-host"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry"),config_files_1=require("../../config-files"),signature_1=require("../../shared-build/signature");function definedEnv(e){const o={};for(const[$,n]of Object.entries(e))typeof n=="string"&&(o[$]=n);return o}i(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,o){process.env.NODE_ENV="development";const $=o.projectsConfigurations.projects[o.projectName],n=o.root,N=path.join(n,$.root),K=e.outputPath?path.join(n,e.outputPath):path.join(n,"dist",$.root),Z=e.configEnv||"development",D=path.join(n,"config",`config.${Z}.yaml`),T=3050;let d=T,a=o.projectName,j={};fs.existsSync(D)?(j=yaml.load(fs.readFileSync(D,"utf8")),d=Number(j.port)||d,a=j.serviceName||a):console.warn(`[serve] ${o.projectName}: no config file at ${D} \u2014 public devserver port defaults to ${T}. If another service also lacks its config, both bind ${T} \u2192 EADDRINUSE. Add config/config.${Z}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(a=e.serviceName);const x=e.servePrefix??"",y=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),ne=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),m=e.logFile?path.resolve(n,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(m){fs.mkdirSync(path.dirname(m),{recursive:!0}),fs.writeFileSync(m,"",{mode:384});try{fs.chmodSync(m,384)}catch{}}let A,I;e.gateway?.middleware&&(A=path.resolve(N,e.gateway.middleware),I=JSON.stringify(j));const ie=e.entryFile||"./src/deployments/service/main.ts",ce=e.tsConfigFile||"./tsconfig.app.json",ae=path.resolve(N,ie),Q=path.resolve(N,ce),le=(e.workers||[]).map(r=>({entryName:r.name,entryPath:path.resolve(N,r.entryPath)})),E={appName:o.projectName,appRoot:N,outputDir:K,main:ae,tsConfig:Q,assets:e.assets||[],additionalEntryPoints:le,port:d,serviceName:a,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:n,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let l=null,q=!1,F=!1,M=!1,p=!1;const ee=path.join(__dirname,"../../main.devserver.js"),oe=path.join(__dirname,"../../main.devserver.ts"),U=!fs.existsSync(ee)&&fs.existsSync(oe),C=U?oe:ee,ue=path.join(K,"main.js");function B(){l&&!l.killed&&(l.kill("SIGTERM"),l=null)}i(B,"K"),s(B,"cleanup");function O(){p=!0,B()}i(O,"U"),s(O,"stop");const de=(0,stop_on_signal_1.stopOnSignal)({stop:O,close:s(()=>b(),"close")});process.on("exit",O);function S(){if(p||q)return;if(!fs.existsSync(C)){console.error(`[serve] devserver script not found at ${C}`);return}const r=(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(d),SERVICE_NAME:a,SERVE_PREFIX:x,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||o.projectName||a,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(n,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:ue,...A&&{GATEWAY_MIDDLEWARE:A},...I&&{GATEWAY_CONFIG:I},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...y&&{LENSMCP_DEBUG_PORT:String(y)},...m&&{LENSMCP_LOG_FILE:m},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:M?"1":"0"});if((0,devserver_host_1.devserverInProcessOffered)()&&(!U||require.extensions[".ts"])){q=!0;for(const[u,f]of Object.entries(r))f!==void 0&&(process.env[u]=f);(0,devserver_host_1.markDevserverInProcess)(),require(C),G("in this process");return}const v=["--enable-source-maps"];U&&v.unshift("--require","@swc-node/register");const c=(0,node_child_process_1.fork)(C,{cwd:n,env:r,stdio:["inherit","inherit","inherit","ipc"],execArgv:v,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(n),a,"dev"))});l=c,c.on("exit",(u,f)=>{l===c&&(l=null),!(f||u===0||p)&&(console.error(`[serve] devserver exited unexpectedly (code=${u}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!p&&!l&&F&&S()},1500).unref?.())}),G()}i(S,"C"),s(S,"startDevServer");function G(r){console.log(`[serve] Devserver started on port ${d} (service: ${a}${x?`, prefix: /${x}`:""}${r?`, ${r}`:""})`),y?(console.warn(`[serve] DEBUGGER ENABLED for ${a}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),ne?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${y}+slot (workers ${y}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${y}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${a}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${a}\` opens pod inspectors without a restart`),m&&console.log(`[serve] Logs teed to ${m} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}i(G,"Z"),s(G,"announceDevServer");function ve(){if(!l||l.killed){console.log("[serve] Devserver not running, restarting..."),S();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(r=>{console.warn("[serve] Failed to notify devserver, restarting...",r.message),B(),S()})}i(ve,"he"),s(ve,"_notifyDevServerReload");let k=null;const W=[];function _(r){if(k){const v=k;k=null,v(r)}else W.push(r)}i(_,"w"),s(_,"pushResult");function re(){return W.length>0?Promise.resolve(W.shift()):new Promise(r=>{k=r})}i(re,"le"),s(re,"waitForNext");let b=s(()=>{},"closeBuild");function V(){return!F||!l&&!q?(F=!0,S(),!0):!1}i(V,"Q"),s(V,"builtOk");function H(){try{return JSON.stringify((0,signature_1.transpileCompilerOptions)(E.tsConfig,n))}catch{return}}i(H,"ee"),s(H,"compileShape");function P(){if(p)return;const{createDevWebpackConfig:r}=require("../../create-webpack-dev"),v=require("webpack"),c=E.webpackConfigPath?path.resolve(E.appRoot,E.webpackConfigPath):void 0;if(c)try{delete require.cache[require.resolve(c)]}catch{}const u=r(E),{watch:f,...t}=u,J=(0,config_files_1.configFilesOf)(E),Y=H();let X;t.plugins=[...t.plugins??[],new config_files_1.ConfigFilesPlugin({files:s(()=>J,"files"),changed:s(w=>{if(!c||!w.includes(c)){const h=H();if(h===void 0||h===Y)return}X=w.map(h=>path.relative(n,h)).sort().join(", ")},"changed")})];const R=v(t);M=(0,dev_devtool_1.bundleCarriesSourceMaps)(R.options.devtool);let g;const L=(0,compile_retry_1.createFailureRetry)(()=>{p||!g||(console.log(`[serve] ${o.projectName}: compiling again after the failure`),g.invalidate())}),se=s((w,h)=>{if(X!==void 0&&g&&!p){console.log(`[serve] ${o.projectName}: ${X} changed what it compiles with \u2014 compiling again with a new compiler`),L.dispose();const z=g;g=void 0,setImmediate(()=>z.close(()=>P()));return}if(w){if(console.error("[serve] Webpack error:",w),g&&!p){const{delayMs:z,retry:he}=L.failed();console.log(`[serve] ${o.projectName}: compiling again in ${Math.round(z/1e3)} s (retry ${he}) \u2014 webpack does not after a failure like this`)}_({success:!1});return}L.succeeded();const fe=h.hasErrors();if(console.info(h.toString({colors:!0,chunks:!1,assets:!0})),fe){_({success:!1});return}V(),_({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${o.projectName}: frozen \u2014 compiling once, no file watcher`),R.run((w,h)=>{se(w,h),R.close(()=>{})});return}g=R.watch(u.watchOptions||{},se),b=s(()=>{L.dispose(),g?.close(()=>{})},"closeBuild")}i(P,"$"),s(P,"compileOnItsOwn");function te(r){const v=r.accepted.projects.filter(t=>t!==o.projectName);console.log(`[serve] ${o.projectName}: compiled by the workspace's shared compilation ${r.accepted.group.slice(0,8)}${v.length?` with ${v.join(", ")}`:""} \u2014 no webpack in this process`);const c=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(a)),u=(0,attach_1.createAttachedTypecheck)({appName:o.projectName,tsConfig:Q,workspaceRoot:n,memoryLimit:e.memoryLimit||8192});let f;b=s(()=>{r.close(),u.dispose()},"closeBuild"),r.listen(t=>{if(!p)switch(t.type){case"watch-run":c.watchRun(t.paths);return;case"watch-closed":c.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${t.message}`),c.failed(`the shared compiler failed: ${t.message}`),_({success:!1});return;case"refused":console.log(`[serve] ${o.projectName}: compiling on its own from now on \u2014 ${t.reason}`),b(),P();return;case"built":{if(console.info(t.text),M=t.sourceMaps,u.built(t),!t.ok){c.failed(),_({success:!1});return}const J=t.hash!==f;f=t.hash;const Y=V();c.built(J&&!Y),_({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(i(te,"ue"),s(te,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const r=await(0,attach_1.joinSharedBuild)({project:o.projectName,target:o.targetName??"serve-hmr",...o.configurationName?{configuration:o.configurationName}:{},options:E,targetOptions:{...e},env:definedEnv(process.env)});r.kind==="attached"?te(r.session):(console.log(`[serve] ${o.projectName}: compiling on its own \u2014 ${r.reason}`),P())}else P();try{for(;;)yield await re()}finally{O(),b(),de()}}i(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
1
+ "use strict";var _e=Object.defineProperty;var i=(e,o)=>_e(e,"name",{value:o,configurable:!0});var ve=Object.defineProperty,s=i((e,o)=>ve(e,"name",{value:o,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_host_1=require("../../devserver-host"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry"),config_files_1=require("../../config-files"),signature_1=require("../../shared-build/signature");function definedEnv(e){const o={};for(const[j,n]of Object.entries(e))typeof n=="string"&&(o[j]=n);return o}i(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,o){process.env.NODE_ENV="development";const j=o.projectsConfigurations.projects[o.projectName],n=o.root,S=path.join(n,j.root),Q=e.outputPath?path.join(n,e.outputPath):path.join(n,"dist",j.root),ee=e.configEnv||"development",A=path.join(n,"config",`config.${ee}.yaml`),q=3050;let d=q,a=o.projectName,O={};fs.existsSync(A)?(O=yaml.load(fs.readFileSync(A,"utf8")),d=Number(O.port)||d,a=O.serviceName||a):console.warn(`[serve] ${o.projectName}: no config file at ${A} \u2014 public devserver port defaults to ${q}. If another service also lacks its config, both bind ${q} \u2192 EADDRINUSE. Add config/config.${ee}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(a=e.serviceName);const I=e.servePrefix??"",y=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),ce=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),g=e.logFile?path.resolve(n,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(g){fs.mkdirSync(path.dirname(g),{recursive:!0}),fs.writeFileSync(g,"",{mode:384});try{fs.chmodSync(g,384)}catch{}}let M,F;e.gateway?.middleware&&(M=path.resolve(S,e.gateway.middleware),F=JSON.stringify(O));const ae=e.entryFile||"./src/deployments/service/main.ts",le=e.tsConfigFile||"./tsconfig.app.json",ue=path.resolve(S,ae),oe=path.resolve(S,le),pe=(e.workers||[]).map(r=>({entryName:r.name,entryPath:path.resolve(S,r.entryPath)})),m={appName:o.projectName,appRoot:S,outputDir:Q,main:ue,tsConfig:oe,assets:e.assets||[],additionalEntryPoints:pe,port:d,serviceName:a,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:n,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let l=null,U=!1,G=!1,B=!1,h=!1;const re=path.join(__dirname,"../../main.devserver.js"),te=path.join(__dirname,"../../main.devserver.ts"),W=!fs.existsSync(re)&&fs.existsSync(te),L=W?te:re,de=path.join(Q,"main.js");function H(){l&&!l.killed&&(l.kill("SIGTERM"),l=null)}i(H,"Q"),s(H,"cleanup");function R(){h=!0,H()}i(R,"W"),s(R,"stop");const he=(0,stop_on_signal_1.stopOnSignal)({stop:R,close:s(()=>b(),"close")});process.on("exit",R);function P(){if(h||U)return;if(!fs.existsSync(L)){console.error(`[serve] devserver script not found at ${L}`);return}const r=(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(d),SERVICE_NAME:a,SERVE_PREFIX:I,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||o.projectName||a,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(n,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:de,...M&&{GATEWAY_MIDDLEWARE:M},...F&&{GATEWAY_CONFIG:F},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...y&&{LENSMCP_DEBUG_PORT:String(y)},...g&&{LENSMCP_LOG_FILE:g},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:B?"1":"0"});if((0,devserver_host_1.devserverInProcessOffered)()&&(!W||require.extensions[".ts"])){U=!0;for(const[u,f]of Object.entries(r))f!==void 0&&(process.env[u]=f);(0,devserver_host_1.markDevserverInProcess)(),require(L),J("in this process");return}const v=["--enable-source-maps"];W&&v.unshift("--require","@swc-node/register");const c=(0,node_child_process_1.fork)(L,{cwd:n,env:r,stdio:["inherit","inherit","inherit","ipc"],execArgv:v,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(n),a,"dev"))});l=c,c.on("exit",(u,f)=>{l===c&&(l=null),!(f||u===0||h)&&(console.error(`[serve] devserver exited unexpectedly (code=${u}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!h&&!l&&G&&P()},1500).unref?.())}),J()}i(P,"j"),s(P,"startDevServer");function J(r){console.log(`[serve] Devserver started on port ${d} (service: ${a}${I?`, prefix: /${I}`:""}${r?`, ${r}`:""})`),y?(console.warn(`[serve] DEBUGGER ENABLED for ${a}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),ce?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${y}+slot (workers ${y}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${y}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${a}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${a}\` opens pod inspectors without a restart`),g&&console.log(`[serve] Logs teed to ${g} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}i(J,"ee"),s(J,"announceDevServer");function fe(){if(!l||l.killed){console.log("[serve] Devserver not running, restarting..."),P();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(r=>{console.warn("[serve] Failed to notify devserver, restarting...",r.message),H(),P()})}i(fe,"me"),s(fe,"_notifyDevServerReload");let D=null;const V=[];function _(r){if(D){const v=D;D=null,v(r)}else V.push(r)}i(_,"w"),s(_,"pushResult");function se(){return V.length>0?Promise.resolve(V.shift()):new Promise(r=>{D=r})}i(se,"de"),s(se,"waitForNext");let b=s(()=>{},"closeBuild");function Y(){return!G||!l&&!U?(G=!0,P(),!0):!1}i(Y,"re"),s(Y,"builtOk");function X(){try{return JSON.stringify((0,signature_1.transpileCompilerOptions)(m.tsConfig,n))}catch{return}}i(X,"te"),s(X,"compileShape");function C(){if(h)return;const{createDevWebpackConfig:r}=require("../../create-webpack-dev"),v=require("webpack"),c=m.webpackConfigPath?path.resolve(m.appRoot,m.webpackConfigPath):void 0;if(c)try{delete require.cache[require.resolve(c)]}catch{}const u=r(m),{watch:f,...t}=u,z=(0,config_files_1.configFilesOf)(m),K=X(),{TsgoCheckPlugin:ge}=require("../../tsgo-check-plugin"),me=process.env.LENSMCP_TYPECHECK!=="off"&&!(t.plugins??[]).some(p=>p instanceof ge);let $,Z=!1;t.plugins=[...t.plugins??[],new config_files_1.ConfigFilesPlugin({files:s(()=>z,"files"),changed:s(p=>{let w=!1;if(!c||!p.includes(c)){const N=X();if(N===void 0)return;if(N===K){if(!me||p.every(k=>k===m.tsConfig))return;w=!0}}Z=($===void 0||Z)&&w,$=p.map(N=>path.relative(n,N)).sort().join(", ")},"changed")})];const T=v(t);B=(0,dev_devtool_1.bundleCarriesSourceMaps)(T.options.devtool);let E;const x=(0,compile_retry_1.createFailureRetry)(()=>{h||!E||(console.log(`[serve] ${o.projectName}: compiling again after the failure`),E.invalidate())}),ie=s((p,w)=>{if($!==void 0&&E&&!h){console.log(Z?`[serve] ${o.projectName}: ${$} changed what it type-checks with \u2014 fork-ts-checker reads that only when it starts, so a new compiler checks it`:`[serve] ${o.projectName}: ${$} changed what it compiles with \u2014 compiling again with a new compiler`),x.dispose();const k=E;E=void 0,setImmediate(()=>k.close(()=>C()));return}if(p){if(console.error("[serve] Webpack error:",p),E&&!h){const{delayMs:k,retry:Ee}=x.failed();console.log(`[serve] ${o.projectName}: compiling again in ${Math.round(k/1e3)} s (retry ${Ee}) \u2014 webpack does not after a failure like this`)}_({success:!1});return}x.succeeded();const N=w.hasErrors();if(console.info(w.toString({colors:!0,chunks:!1,assets:!0})),N){_({success:!1});return}Y(),_({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${o.projectName}: frozen \u2014 compiling once, no file watcher`),T.run((p,w)=>{ie(p,w),T.close(()=>{})});return}E=T.watch(u.watchOptions||{},ie),b=s(()=>{x.dispose(),E?.close(()=>{})},"closeBuild")}i(C,"L"),s(C,"compileOnItsOwn");function ne(r){const v=r.accepted.projects.filter(t=>t!==o.projectName);console.log(`[serve] ${o.projectName}: compiled by the workspace's shared compilation ${r.accepted.group.slice(0,8)}${v.length?` with ${v.join(", ")}`:""} \u2014 no webpack in this process`);const c=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(a)),u=(0,attach_1.createAttachedTypecheck)({appName:o.projectName,tsConfig:oe,workspaceRoot:n,memoryLimit:e.memoryLimit||8192});let f;b=s(()=>{r.close(),u.dispose()},"closeBuild"),r.listen(t=>{if(!h)switch(t.type){case"watch-run":c.watchRun(t.paths);return;case"watch-closed":c.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${t.message}`),c.failed(`the shared compiler failed: ${t.message}`),_({success:!1});return;case"refused":console.log(`[serve] ${o.projectName}: compiling on its own from now on \u2014 ${t.reason}`),b(),C();return;case"built":{if(console.info(t.text),B=t.sourceMaps,u.built(t),!t.ok){c.failed(),_({success:!1});return}const z=t.hash!==f;f=t.hash;const K=Y();c.built(z&&!K),_({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(i(ne,"fe"),s(ne,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const r=await(0,attach_1.joinSharedBuild)({project:o.projectName,target:o.targetName??"serve-hmr",...o.configurationName?{configuration:o.configurationName}:{},options:m,targetOptions:{...e},env:definedEnv(process.env)});r.kind==="attached"?ne(r.session):(console.log(`[serve] ${o.projectName}: compiling on its own \u2014 ${r.reason}`),C())}else C();try{for(;;)yield await se()}finally{R(),b(),he()}}i(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.25.39",
3
+ "version": "1.25.41",
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",
@@ -58,9 +58,9 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "@lensmcp/core": "1.25.39",
62
- "@lensmcp/node-instrumentation": "1.25.39",
63
- "@lensmcp/nx-plugin": "1.25.39",
61
+ "@lensmcp/core": "1.25.41",
62
+ "@lensmcp/node-instrumentation": "1.25.41",
63
+ "@lensmcp/nx-plugin": "1.25.41",
64
64
  "fastify": "^5.8.5",
65
65
  "@fastify/reply-from": "^12.6.2",
66
66
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
@@ -112,7 +112,7 @@ export interface BarrelAnalysis {
112
112
  }
113
113
  /** Measure every (project, library) pair reached through a root barrel. */
114
114
  export declare function analyzeBarrelImports(input: BarrelAnalysisInput): BarrelAnalysis;
115
- /** Executor memory per compiled module, MB — the fit to FoodGuard's seven watchers (issues/open/pod-memory-05). */
115
+ /** Executor memory per compiled module, MB — the fit to FoodGuard's seven watchers (issues/closed/pod-memory-05). */
116
116
  export declare const EXECUTOR_MB_PER_MODULE = 0.374;
117
117
  /** A finding's text, evidence and cost — the payload a finding event carries. */
118
118
  export interface HeavyBarrelFindingText {
@@ -40,6 +40,8 @@ export interface AttachedTypecheckOptions {
40
40
  workspaceRoot: string;
41
41
  memoryLimit: number;
42
42
  env?: NodeJS.ProcessEnv;
43
+ /** How often the tsconfig chain is read for a change. Default `CONFIG_POLL_MS` (config-files.ts). */
44
+ configPollMs?: number;
43
45
  }
44
46
  /**
45
47
  * The checker `LENSMCP_TYPECHECK` asks for, run in THIS process for THIS service — what its own compiler used to
@@ -52,5 +54,12 @@ export interface AttachedTypecheckOptions {
52
54
  * the watcher's changed files and adds its program's files to what it watches. Its program lists only the
53
55
  * tsconfig's own files, so the build host's file list for this bundle (`files`) is watched too — a library
54
56
  * save re-checks every service that compiles it, as before.
57
+ *
58
+ * Either one also follows the tsconfig and every file it extends (issues/closed/foodguard-115). The checker read them
59
+ * when it started, and the build host follows them only for what it compiles: a `paths` change moves the service to
60
+ * a new compiler, and a type-check-only edit compiles nothing again. On a change, tsgo checks again (its shim is
61
+ * derived from the chain for every check) and fork-ts-checker is made again: it reads its configuration again only
62
+ * when its own tsconfig changes, never a file that one extends. The webpack override is not watched: no checker
63
+ * reads it.
55
64
  */
56
65
  export declare function createAttachedTypecheck(opts: AttachedTypecheckOptions): AttachedTypecheck;
@@ -1,3 +1,3 @@
1
- "use strict";var T=Object.defineProperty;var f=(e,t)=>T(e,"name",{value:t,configurable:!0});var _=Object.defineProperty,o=f((e,t)=>_(e,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.JOIN_TIMEOUT_MS=void 0,exports.sharedBuildOffered=sharedBuildOffered,exports.joinSharedBuild=joinSharedBuild,exports.createAttachedTypecheck=createAttachedTypecheck;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fork_ts_check_reporter_1=require("../fork-ts-check-reporter"),tsgo_check_plugin_1=require("../tsgo-check-plugin"),protocol_1=require("./protocol");exports.JOIN_TIMEOUT_MS=12e4;function sharedBuildOffered(e=process.env,t=process){return e[protocol_1.SHARED_BUILD_ENV]==="1"&&typeof t.send=="function"}f(sharedBuildOffered,"sharedBuildOffered"),o(sharedBuildOffered,"sharedBuildOffered");function joinSharedBuild(e,t={}){const i=t.proc??process,c=t.timeoutMs??exports.JOIN_TIMEOUT_MS;return new Promise(n=>{const p=[];let d,s=!1;const u=o(r=>{if(!(0,protocol_1.isRunnerInbound)(r))return;const a=r.event;if(!s)return a.type==="refused"?l({kind:"refused",reason:a.reason}):a.type!=="accepted"?void 0:l({kind:"attached",session:m(a)});d?d(a):p.push(a)},"onMessage"),h=o(()=>{i.removeListener("message",u)},"close"),m=o(r=>({accepted:r,listen(a){d=a;for(const w of p.splice(0))a(w)},close:h}),"makeSession"),l=o(r=>{s||(s=!0,clearTimeout(k),r.kind==="refused"&&h(),n(r))},"finish"),k=setTimeout(()=>l({kind:"refused",reason:`the build host did not answer within ${Math.round(c/1e3)} s`}),c);k.unref?.(),i.on("message",u);const g={channel:protocol_1.SHARED_BUILD_CHANNEL,type:"join",join:e};try{i.send?.(g,void 0,void 0,r=>{r&&l({kind:"refused",reason:`could not reach the gateway (${r.message})`})})}catch(r){l({kind:"refused",reason:`could not reach the gateway (${r.message})`})}})}f(joinSharedBuild,"joinSharedBuild"),o(joinSharedBuild,"joinSharedBuild");function createAttachedTypecheck(e){const t=e.env??process.env,i=t.LENSMCP_TYPECHECK;if(i==="off")return{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};if(i==="tsgo"){if((0,tsgo_check_plugin_1.resolveTsgoBin)(e.workspaceRoot,t.LENSMCP_TSGO_BIN)){const n=new tsgo_check_plugin_1.TsgoCheckPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,...t.LENSMCP_TSGO_BIN?{binPath:t.LENSMCP_TSGO_BIN}:{}}),p=[];return n.apply({hooks:{done:{tap:o((d,s)=>p.push(s),"tap")}}}),{built:o(d=>{for(const s of p)s({hasErrors:o(()=>!d.ok,"hasErrors")})},"built"),dispose:o(()=>n.dispose(),"dispose")}}const c=`${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] ${e.appName}: LENSMCP_TYPECHECK=tsgo but ${c}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,detail:c})}return forkTsCheckerOnItsOwn(e)}f(createAttachedTypecheck,"createAttachedTypecheck"),o(createAttachedTypecheck,"createAttachedTypecheck");function forkTsCheckerOnItsOwn(e){const t=(0,fork_ts_check_reporter_1.loadForkTsChecker)(e.workspaceRoot),i=t?.default??t;if(typeof i!="function")return console.warn(`[serve] ${e.appName}: fork-ts-checker-webpack-plugin is not installed \u2014 this service is not type-checked`),{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};let c=[],n,p=!1;const d=o(()=>{n=require("webpack")({mode:"development",context:e.workspaceRoot,entry:{},infrastructureLogging:{level:"info"},plugins:[{apply(s){s.hooks.afterCompile.tapPromise("LensmcpAttachedTypecheck",()=>new Promise(u=>setTimeout(u,0))),s.hooks.afterCompile.tap("LensmcpAttachedTypecheck",u=>{for(const h of c)u.fileDependencies.add(h)})}},new i({async:!0,typescript:{configFile:e.tsConfig,memoryLimit:e.memoryLimit}}),new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot})]}).watch({ignored:["**/node_modules/**",`${path.join(e.workspaceRoot,"dist")}/**`]},()=>{})},"start");return{built(s){if(p)return;const u=s.files&&s.files.join(`
2
- `)!==c.join(`
3
- `);s.files&&(c=[...s.files]),n?u&&n.invalidate():d()},dispose(){p=!0;try{n?.close(()=>{})}catch{}n=void 0}}}f(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn"),o(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn");
1
+ "use strict";var y=Object.defineProperty;var k=(e,t)=>y(e,"name",{value:t,configurable:!0});var _=Object.defineProperty,o=k((e,t)=>_(e,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.JOIN_TIMEOUT_MS=void 0,exports.sharedBuildOffered=sharedBuildOffered,exports.joinSharedBuild=joinSharedBuild,exports.createAttachedTypecheck=createAttachedTypecheck;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),config_files_1=require("../config-files"),fork_ts_check_reporter_1=require("../fork-ts-check-reporter"),tsgo_check_plugin_1=require("../tsgo-check-plugin"),protocol_1=require("./protocol");exports.JOIN_TIMEOUT_MS=12e4;function sharedBuildOffered(e=process.env,t=process){return e[protocol_1.SHARED_BUILD_ENV]==="1"&&typeof t.send=="function"}k(sharedBuildOffered,"sharedBuildOffered"),o(sharedBuildOffered,"sharedBuildOffered");function joinSharedBuild(e,t={}){const c=t.proc??process,r=t.timeoutMs??exports.JOIN_TIMEOUT_MS;return new Promise(n=>{const p=[];let l,f=!1;const a=o(i=>{if(!(0,protocol_1.isRunnerInbound)(i))return;const d=i.event;if(!f)return d.type==="refused"?h({kind:"refused",reason:d.reason}):d.type!=="accepted"?void 0:h({kind:"attached",session:u(d)});l?l(d):p.push(d)},"onMessage"),s=o(()=>{c.removeListener("message",a)},"close"),u=o(i=>({accepted:i,listen(d){l=d;for(const w of p.splice(0))d(w)},close:s}),"makeSession"),h=o(i=>{f||(f=!0,clearTimeout(g),i.kind==="refused"&&s(),n(i))},"finish"),g=setTimeout(()=>h({kind:"refused",reason:`the build host did not answer within ${Math.round(r/1e3)} s`}),r);g.unref?.(),c.on("message",a);const m={channel:protocol_1.SHARED_BUILD_CHANNEL,type:"join",join:e};try{c.send?.(m,void 0,void 0,i=>{i&&h({kind:"refused",reason:`could not reach the gateway (${i.message})`})})}catch(i){h({kind:"refused",reason:`could not reach the gateway (${i.message})`})}})}k(joinSharedBuild,"joinSharedBuild"),o(joinSharedBuild,"joinSharedBuild");function createAttachedTypecheck(e){const t=e.env??process.env,c=t.LENSMCP_TYPECHECK;if(c==="off")return{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};if(c==="tsgo"){if((0,tsgo_check_plugin_1.resolveTsgoBin)(e.workspaceRoot,t.LENSMCP_TSGO_BIN)){const n=new tsgo_check_plugin_1.TsgoCheckPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,...t.LENSMCP_TSGO_BIN?{binPath:t.LENSMCP_TSGO_BIN}:{}}),p=[];n.apply({hooks:{done:{tap:o((a,s)=>p.push(s),"tap")}}});let l;const f=watchTypecheckConfig(e,a=>{l&&(console.log(`[serve] ${e.appName}: ${a} changed \u2014 type-checking again`),n.schedule())});return{built:o(a=>{l=a.ok;for(const s of p)s({hasErrors:o(()=>!a.ok,"hasErrors")})},"built"),dispose:o(()=>{f.close(),n.dispose()},"dispose")}}const r=`${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] ${e.appName}: LENSMCP_TYPECHECK=tsgo but ${r}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,detail:r})}return forkTsCheckerOnItsOwn(e)}k(createAttachedTypecheck,"createAttachedTypecheck"),o(createAttachedTypecheck,"createAttachedTypecheck");function forkTsCheckerOnItsOwn(e){const t=(0,fork_ts_check_reporter_1.loadForkTsChecker)(e.workspaceRoot),c=t?.default??t;if(typeof c!="function")return console.warn(`[serve] ${e.appName}: fork-ts-checker-webpack-plugin is not installed \u2014 this service is not type-checked`),{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};let r=[],n,p=!1;const l=o(()=>{try{n?.close(()=>{})}catch{}n=void 0},"close"),f=watchTypecheckConfig(e,s=>{p||!n||(console.log(`[serve] ${e.appName}: ${s} changed \u2014 type-checking again with a new fork-ts-checker`),l(),a())}),a=o(()=>{n=require("webpack")({mode:"development",context:e.workspaceRoot,entry:{},infrastructureLogging:{level:"info"},plugins:[{apply(s){s.hooks.afterCompile.tapPromise("LensmcpAttachedTypecheck",()=>new Promise(u=>setTimeout(u,0))),s.hooks.afterCompile.tap("LensmcpAttachedTypecheck",u=>{for(const h of r)u.fileDependencies.add(h)})}},new c({async:!0,typescript:{configFile:e.tsConfig,memoryLimit:e.memoryLimit}}),new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot})]}).watch({ignored:["**/node_modules/**",`${path.join(e.workspaceRoot,"dist")}/**`]},()=>{})},"start");return{built(s){if(p)return;const u=s.files&&s.files.join(`
2
+ `)!==r.join(`
3
+ `);s.files&&(r=[...s.files]),n?u&&n.invalidate():a()},dispose(){p=!0,f.close(),l()}}}k(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn"),o(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn");function watchTypecheckConfig(e,t){return(0,config_files_1.watchConfigFiles)({files:o(()=>(0,tsgo_check_plugin_1.tsconfigChain)(e.tsConfig),"files"),changed:o(c=>t(c.map(r=>path.relative(e.workspaceRoot,r)||r).join(", ")),"changed"),...e.configPollMs!==void 0?{intervalMs:e.configPollMs}:{}})}k(watchTypecheckConfig,"watchTypecheckConfig"),o(watchTypecheckConfig,"watchTypecheckConfig");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The shared compiler (issues/open/pod-memory-05): ONE webpack compilation for every service of a workspace
2
+ * The shared compiler (issues/closed/pod-memory-05): ONE webpack compilation for every service of a workspace
3
3
  * instead of one watcher per service. Three processes talk:
4
4
  *
5
5
  * runner (the serve executor, ATTACHED) ⇄ gateway (relay) ⇄ build host (one per workspace)
@@ -168,6 +168,21 @@ export declare function effectivePaths(tsConfig: string, seen?: Set<string>): {
168
168
  paths: Record<string, string[]>;
169
169
  anchorDir: string;
170
170
  } | null;
171
+ /**
172
+ * The tsconfig and every file it extends, as {@link effectivePaths} walks them — what a check's configuration is read
173
+ * from (issues/closed/foodguard-115). The tsconfig itself is always listed, even when it cannot be read: a half-saved
174
+ * file is watched until the next save. A file extended from node_modules changes only with an install, and is left
175
+ * out, as `configFilesOf` leaves it out. Read with this file's own parser, so a service that type-checks with tsgo
176
+ * never loads TypeScript into its process just to list its config files.
177
+ */
178
+ export declare function tsconfigChain(tsConfig: string): string[];
179
+ /**
180
+ * Write the shim for one check, and return its path. Called before EVERY check (issues/closed/foodguard-115): the
181
+ * shim's `paths` REPLACE the ones the tsconfig chain declares, so they are the one part of the configuration tsgo
182
+ * does not read for itself. Written once, a shim kept the aliases the chain had when the service started: FoodGuard
183
+ * added `@foodguard-shared/execution-graph` to `tsconfig.base.json`, the bundle built with it, and every check for
184
+ * the next 80 minutes still reported «TS2307: Cannot find module». The file is left alone when it would not change.
185
+ */
171
186
  export declare function writeShimTsconfig(appName: string, tsConfig: string, workspaceRoot: string): string;
172
187
  export declare class TsgoCheckPlugin {
173
188
  private readonly opts;
@@ -1,5 +1,5 @@
1
- "use strict";var v=Object.defineProperty;var a=(e,t)=>v(e,"name",{value:t,configurable:!0});var b=Object.defineProperty,l=a((e,t)=>b(e,"name",{value:t,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.tsgoIncremental=tsgoIncremental,exports.tsgoBuildInfoPath=tsgoBuildInfoPath,exports.tsgoCheckArgs=tsgoCheckArgs,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}a(checkerMaxProcs,"checkerMaxProcs"),l(checkerMaxProcs,"checkerMaxProcs");function tsgoIncremental(e=process.env){const t=(e.LENSMCP_TSGO_INCREMENTAL??"").trim().toLowerCase();return!(t==="0"||t==="false"||t==="off"||t==="no")}a(tsgoIncremental,"tsgoIncremental"),l(tsgoIncremental,"tsgoIncremental");function tsgoBuildInfoPath(e,t){return path.join(t,"tmp","lensmcp-tsgo",`${e}.tsbuildinfo`)}a(tsgoBuildInfoPath,"tsgoBuildInfoPath"),l(tsgoBuildInfoPath,"tsgoBuildInfoPath");function tsgoCheckArgs(e,t){return["--noEmit","--pretty","false",...t?["--incremental","--tsBuildInfoFile",t]:[],"-p",e]}a(tsgoCheckArgs,"tsgoCheckArgs"),l(tsgoCheckArgs,"tsgoCheckArgs");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()}))}a(stopWithTheProcess,"stopWithTheProcess"),l(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}a(shimNames,"shimNames"),l(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}}a(isExecutableFile,"isExecutableFile"),l(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 i=path.join(e,"package.json");if(fs.existsSync(i))try{const{name:s}=JSON.parse(fs.readFileSync(i,"utf8"));if(s===exports.TSGO_PACKAGE)return e}catch{}const n=path.dirname(e);if(n===e)break;e=n}}catch{}return null}a(resolveTsgoPackageDir,"resolveTsgoPackageDir"),l(resolveTsgoPackageDir,"resolveTsgoPackageDir");function packageDeclaredBin(e){try{const{bin:t}=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8")),i=typeof t=="string"?t:t?.tsgo;return i?path.join(e,i):null}catch{return null}}a(packageDeclaredBin,"packageDeclaredBin"),l(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const i=path.resolve(e,"..","..",".bin"),n=shimNames(t).map(o=>path.join(i,o)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...n]:n}a(ownBinCandidates,"ownBinCandidates"),l(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,i={}){const n=i.platform??process.platform,s=i.isExecutable??(r=>isExecutableFile(r,n));if(t)return s(t)?t:null;for(const r of shimNames(n)){const c=path.join(e,"node_modules",".bin",r);if(s(c))return c}const o=(i.ownPackageDir??resolveTsgoPackageDir)();return o?ownBinCandidates(o,n).find(s)??null:null}a(resolveTsgoBin,"resolveTsgoBin"),l(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{}}a(reportTsgoUnavailable,"reportTsgoUnavailable"),l(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",i=!1,n=!1,s=!1;for(let o=0;o<e.length;o++){const r=e[o],c=e[o+1];if(n){r===`
2
- `&&(n=!1,t+=r);continue}if(s){r==="*"&&c==="/"&&(s=!1,o++);continue}if(i){t+=r,r==="\\"?(t+=c??"",o++):r==='"'&&(i=!1);continue}if(r==='"'){i=!0,t+=r;continue}if(r==="/"&&c==="/"){n=!0,o++;continue}if(r==="/"&&c==="*"){s=!0,o++;continue}t+=r}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}a(parseJsonc,"parseJsonc"),l(parseJsonc,"parseJsonc");function resolveExtends(e,t){if(!e.startsWith(".")&&!path.isAbsolute(e))return null;const i=path.isAbsolute(e)?e:path.resolve(t,e);for(const n of[i,`${i}.json`,path.join(i,"tsconfig.json")])try{if(fs.statSync(n).isFile())return n}catch{}return null}a(resolveExtends,"resolveExtends"),l(resolveExtends,"resolveExtends");function effectivePaths(e,t=new Set){if(t.has(e))return null;t.add(e);let i;try{i=fs.readFileSync(e,"utf8")}catch{return null}const n=parseJsonc(i);if(!n)return null;const s=path.dirname(e),o=n.compilerOptions??{},r=Array.isArray(n.extends)?n.extends.filter(p=>typeof p=="string"):typeof n.extends=="string"?[n.extends]:[],c=l(()=>{for(const p of[...r].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=o.paths,h=typeof o.baseUrl=="string"?path.resolve(s,o.baseUrl):void 0;if(!u){const p=c();return p?{paths:p.paths,anchorDir:h??p.anchorDir}:null}return{paths:u,anchorDir:h??c()?.anchorDir??s}}a(effectivePaths,"effectivePaths"),l(effectivePaths,"effectivePaths");function writeShimTsconfig(e,t,i){const n=path.join(i,"tmp","lensmcp-tsgo");fs.mkdirSync(n,{recursive:!0});const s=path.join(n,`${e}.tsconfig.json`),o=effectivePaths(t),r=o?Object.fromEntries(Object.entries(o.paths).map(([c,u])=>[c,(Array.isArray(u)?u:[]).map(h=>path.isAbsolute(h)?h:path.resolve(o.anchorDir,h))])):void 0;return fs.writeFileSync(s,JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...r&&Object.keys(r).length?{paths:r}:{}}},null,2)),s}a(writeShimTsconfig,"writeShimTsconfig"),l(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{a(this,"TsgoCheckPlugin")}static{l(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)(),this.buildInfo=t.incremental??tsgoIncremental()?tsgoBuildInfoPath(t.appName,t.workspaceRoot):void 0}apply(t){t.hooks.done.tap("TsgoCheckPlugin",i=>{i.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(),{onHold:l(i=>this.held(i),"onHold")});this.isIdle()?t.release():this.ticket=t}held(t){if(!this.disposed&&(console.log(`[tsgo] ${this.opts.appName}: check held \u2014 ${t.availableMb} MB free, it waits for ${t.minFreeMb} MB (LENSMCP_TYPECHECK_MIN_FREE_MB)`),this.opts.publishEvents!==!1))try{(0,typecheck_bus_1.publishTypecheckHeld)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,availableMb:t.availableMb,minFreeMb:t.minFreeMb,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}forgetBuildInfo(){if(this.buildInfo)try{fs.rmSync(this.buildInfo,{force:!0})}catch{}}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(h){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${h.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${h.message}`}),this.leave()}if(!t){this.disabled=!0;const h=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${h}.`),this.publish({output:"",durationMs:0,toolError:h}),this.leave()}this.phase="running",this.dirty=!1;const i=Date.now(),n=i-this.askedAt,s=this.spawnImpl(t,tsgoCheckArgs(this.shimPath,this.buildInfo),{cwd:this.opts.workspaceRoot,stdio:["ignore","pipe","pipe"],env:{...process.env,GOMAXPROCS:process.env.GOMAXPROCS??String(this.opts.maxProcs??checkerMaxProcs())}});this.inflight=s;let o="",r=!1;const c=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{r=!0,s.kill("SIGKILL")},c);u.unref?.(),s.stdout?.on("data",h=>{o+=h.toString()}),s.stderr?.on("data",h=>{o+=h.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.forgetBuildInfo(),this.publish({output:"",durationMs:Date.now()-i,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(h,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-i,f=(d/1e3).toFixed(1),m=n>=1e3?`${f}s, waited ${(n/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(r){const k=`the check was killed after ${Math.round(c/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${k} \u2014 no verdict.`),this.forgetBuildInfo(),this.publish({output:"",durationMs:d,toolError:k}),this.finish(s)}if(p)return this.forgetBuildInfo(),this.finish(s);if(h===0)return console.log(`[tsgo] ${this.opts.appName}: types clean (${m})`),this.publish({output:"",durationMs:d}),this.finish(s);o.trim()&&process.stdout.write(o.endsWith(`
3
- `)?o:`${o}
4
- `);const g=(o.match(/error TS\d+/g)??[]).length;console.log(`[tsgo] ${this.opts.appName}: ${g} type error${g===1?"":"s"} (${m})`),g===0&&this.forgetBuildInfo(),this.publish({output:o,durationMs:d,...g===0?{toolError:`tsgo exited ${h} with no diagnostics: ${firstLine(o)}`}:{}}),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&&(this.inflight.kill("SIGKILL"),this.forgetBuildInfo()),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
1
+ "use strict";var k=Object.defineProperty;var a=(e,t)=>k(e,"name",{value:t,configurable:!0});var b=Object.defineProperty,l=a((e,t)=>b(e,"name",{value:t,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.tsgoIncremental=tsgoIncremental,exports.tsgoBuildInfoPath=tsgoBuildInfoPath,exports.tsgoCheckArgs=tsgoCheckArgs,exports.resolveTsgoPackageDir=resolveTsgoPackageDir,exports.resolveTsgoBin=resolveTsgoBin,exports.reportTsgoUnavailable=reportTsgoUnavailable,exports.effectivePaths=effectivePaths,exports.tsconfigChain=tsconfigChain,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}a(checkerMaxProcs,"checkerMaxProcs"),l(checkerMaxProcs,"checkerMaxProcs");function tsgoIncremental(e=process.env){const t=(e.LENSMCP_TSGO_INCREMENTAL??"").trim().toLowerCase();return!(t==="0"||t==="false"||t==="off"||t==="no")}a(tsgoIncremental,"tsgoIncremental"),l(tsgoIncremental,"tsgoIncremental");function tsgoBuildInfoPath(e,t){return path.join(t,"tmp","lensmcp-tsgo",`${e}.tsbuildinfo`)}a(tsgoBuildInfoPath,"tsgoBuildInfoPath"),l(tsgoBuildInfoPath,"tsgoBuildInfoPath");function tsgoCheckArgs(e,t){return["--noEmit","--pretty","false",...t?["--incremental","--tsBuildInfoFile",t]:[],"-p",e]}a(tsgoCheckArgs,"tsgoCheckArgs"),l(tsgoCheckArgs,"tsgoCheckArgs");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()}))}a(stopWithTheProcess,"stopWithTheProcess"),l(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}a(shimNames,"shimNames"),l(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}}a(isExecutableFile,"isExecutableFile"),l(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 n=path.dirname(e);if(n===e)break;e=n}}catch{}return null}a(resolveTsgoPackageDir,"resolveTsgoPackageDir"),l(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}}a(packageDeclaredBin,"packageDeclaredBin"),l(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const o=path.resolve(e,"..","..",".bin"),n=shimNames(t).map(i=>path.join(o,i)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...n]:n}a(ownBinCandidates,"ownBinCandidates"),l(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,o={}){const n=o.platform??process.platform,s=o.isExecutable??(r=>isExecutableFile(r,n));if(t)return s(t)?t:null;for(const r of shimNames(n)){const c=path.join(e,"node_modules",".bin",r);if(s(c))return c}const i=(o.ownPackageDir??resolveTsgoPackageDir)();return i?ownBinCandidates(i,n).find(s)??null:null}a(resolveTsgoBin,"resolveTsgoBin"),l(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{}}a(reportTsgoUnavailable,"reportTsgoUnavailable"),l(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",o=!1,n=!1,s=!1;for(let i=0;i<e.length;i++){const r=e[i],c=e[i+1];if(n){r===`
2
+ `&&(n=!1,t+=r);continue}if(s){r==="*"&&c==="/"&&(s=!1,i++);continue}if(o){t+=r,r==="\\"?(t+=c??"",i++):r==='"'&&(o=!1);continue}if(r==='"'){o=!0,t+=r;continue}if(r==="/"&&c==="/"){n=!0,i++;continue}if(r==="/"&&c==="*"){s=!0,i++;continue}t+=r}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}a(parseJsonc,"parseJsonc"),l(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 n of[o,`${o}.json`,path.join(o,"tsconfig.json")])try{if(fs.statSync(n).isFile())return n}catch{}return null}a(resolveExtends,"resolveExtends"),l(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 n=parseJsonc(o);if(!n)return null;const s=path.dirname(e),i=n.compilerOptions??{},r=Array.isArray(n.extends)?n.extends.filter(p=>typeof p=="string"):typeof n.extends=="string"?[n.extends]:[],c=l(()=>{for(const p of[...r].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=i.paths,h=typeof i.baseUrl=="string"?path.resolve(s,i.baseUrl):void 0;if(!u){const p=c();return p?{paths:p.paths,anchorDir:h??p.anchorDir}:null}return{paths:u,anchorDir:h??c()?.anchorDir??s}}a(effectivePaths,"effectivePaths"),l(effectivePaths,"effectivePaths");function tsconfigChain(e){const t=[],o=l(n=>{if(t.includes(n))return;t.push(n);let s;try{s=parseJsonc(fs.readFileSync(n,"utf8"))}catch{return}const i=s?.extends,r=Array.isArray(i)?i.filter(c=>typeof c=="string"):typeof i=="string"?[i]:[];for(const c of r){const u=resolveExtends(c,path.dirname(n));u&&!u.split(path.sep).includes("node_modules")&&o(u)}},"visit");return o(path.resolve(e)),t}a(tsconfigChain,"tsconfigChain"),l(tsconfigChain,"tsconfigChain");function writeShimTsconfig(e,t,o){const n=path.join(o,"tmp","lensmcp-tsgo");fs.mkdirSync(n,{recursive:!0});const s=path.join(n,`${e}.tsconfig.json`),i=effectivePaths(t),r=i?Object.fromEntries(Object.entries(i.paths).map(([h,p])=>[h,(Array.isArray(p)?p:[]).map(d=>path.isAbsolute(d)?d:path.resolve(i.anchorDir,d))])):void 0,c=JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...r&&Object.keys(r).length?{paths:r}:{}}},null,2);let u;try{u=fs.readFileSync(s,"utf8")}catch{}return u!==c&&fs.writeFileSync(s,c),s}a(writeShimTsconfig,"writeShimTsconfig"),l(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{a(this,"TsgoCheckPlugin")}static{l(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)(),this.buildInfo=t.incremental??tsgoIncremental()?tsgoBuildInfoPath(t.appName,t.workspaceRoot):void 0}apply(t){t.hooks.done.tap("TsgoCheckPlugin",o=>{o.hasErrors()||this.schedule()}),t.hooks.shutdown?.tap?.("TsgoCheckPlugin",()=>this.dispose()),t.hooks.watchClose?.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(),{onHold:l(o=>this.held(o),"onHold")});this.isIdle()?t.release():this.ticket=t}held(t){if(!this.disposed&&(console.log(`[tsgo] ${this.opts.appName}: check held \u2014 ${t.availableMb} MB free, it waits for ${t.minFreeMb} MB (LENSMCP_TYPECHECK_MIN_FREE_MB)`),this.opts.publishEvents!==!1))try{(0,typecheck_bus_1.publishTypecheckHeld)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,availableMb:t.availableMb,minFreeMb:t.minFreeMb,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}forgetBuildInfo(){if(this.buildInfo)try{fs.rmSync(this.buildInfo,{force:!0})}catch{}}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=writeShimTsconfig(this.opts.appName,this.opts.tsConfig,this.opts.workspaceRoot))}catch(h){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${h.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${h.message}`}),this.leave()}if(!t){this.disabled=!0;const h=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${h}.`),this.publish({output:"",durationMs:0,toolError:h}),this.leave()}this.phase="running",this.dirty=!1;const o=Date.now(),n=o-this.askedAt,s=this.spawnImpl(t,tsgoCheckArgs(this.shimPath,this.buildInfo),{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="",r=!1;const c=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{r=!0,s.kill("SIGKILL")},c);u.unref?.(),s.stdout?.on("data",h=>{i+=h.toString()}),s.stderr?.on("data",h=>{i+=h.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.forgetBuildInfo(),this.publish({output:"",durationMs:Date.now()-o,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(h,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-o,f=(d/1e3).toFixed(1),m=n>=1e3?`${f}s, waited ${(n/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(r){const y=`the check was killed after ${Math.round(c/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${y} \u2014 no verdict.`),this.forgetBuildInfo(),this.publish({output:"",durationMs:d,toolError:y}),this.finish(s)}if(p)return this.forgetBuildInfo(),this.finish(s);if(h===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})`),g===0&&this.forgetBuildInfo(),this.publish({output:i,durationMs:d,...g===0?{toolError:`tsgo exited ${h} 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&&(this.inflight.kill("SIGKILL"),this.forgetBuildInfo()),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
5
5
  `)[0]?.slice(0,300)??""}a(firstLine,"firstLine"),l(firstLine,"firstLine");