@lensmcp/cluster 1.24.1 → 1.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/checkout-cache.d.ts +5 -0
- package/checkout-cache.js +1 -0
- package/create-webpack-dev.d.ts +1 -1
- package/create-webpack-dev.js +1 -1
- package/devserver-notifier.d.ts +1 -1
- package/devserver-notifier.js +1 -1
- package/devserver-transport.d.ts +41 -0
- package/devserver-transport.js +1 -0
- package/executors/gateway/manifest.js +1 -1
- package/executors/gateway/runtime/auth.d.ts +7 -0
- package/executors/gateway/runtime/auth.js +1 -1
- package/executors/gateway/runtime/chooser.d.ts +2 -1
- package/executors/gateway/runtime/chooser.js +23 -10
- package/executors/gateway/runtime/control.d.ts +14 -0
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/discovery.d.ts +15 -2
- package/executors/gateway/runtime/discovery.js +1 -1
- package/executors/gateway/runtime/edge.d.ts +7 -0
- package/executors/gateway/runtime/edge.js +1 -1
- package/executors/gateway/runtime/guest-replay.d.ts +22 -0
- package/executors/gateway/runtime/guest-replay.js +2 -0
- package/executors/gateway/runtime/instance.d.ts +104 -0
- package/executors/gateway/runtime/instance.js +2 -0
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +2 -0
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/pod-probe.d.ts +17 -0
- package/executors/gateway/runtime/pod-probe.js +1 -1
- package/executors/gateway/runtime/proxy.d.ts +14 -0
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/registration-guards.js +1 -1
- package/executors/gateway/runtime/route-registry.d.ts +7 -0
- package/executors/gateway/runtime/scope.d.ts +18 -0
- package/executors/gateway/runtime/scope.js +1 -1
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/service-keys.d.ts +12 -3
- package/executors/gateway/runtime/service-keys.js +1 -1
- package/executors/gateway/runtime/shared-build.d.ts +1 -1
- package/executors/gateway/runtime/shared-build.js +3 -3
- package/executors/gateway/runtime/types.d.ts +47 -0
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/upgrade.d.ts +13 -8
- package/executors/gateway/runtime/upgrade.js +1 -1
- package/executors/gateway/runtime/workspace-middleware.d.ts +38 -0
- package/executors/gateway/runtime/workspace-middleware.js +1 -0
- package/executors/gateway/runtime/workspace-registry.d.ts +7 -0
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/trust.impl.js +3 -3
- package/inspector-secret.d.ts +6 -0
- package/inspector-secret.js +1 -1
- package/main.devserver.d.ts +4 -38
- package/main.devserver.js +10 -10
- package/package.json +4 -4
- package/shared-build/host.js +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type * as http from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* What a middleware is told besides the request: whose route it is. A middleware runs inside the daemon, so its
|
|
4
|
+
* `process.env` is the daemon owner's — a worktree instance's middleware reads the instance's own addresses (its
|
|
5
|
+
* `AUTH_API_RESOURCE`, …) from `env`: the instance's variables (instance.ts), empty for any other workspace.
|
|
6
|
+
* A middleware that takes one argument ignores it.
|
|
7
|
+
*/
|
|
8
|
+
export interface EdgeMiddlewareContext {
|
|
9
|
+
wsKey?: string;
|
|
10
|
+
root?: string;
|
|
11
|
+
env: Readonly<Record<string, string>>;
|
|
12
|
+
}
|
|
13
|
+
export type EdgeMiddleware = (req: http.IncomingMessage, ctx?: EdgeMiddlewareContext) => void;
|
|
14
|
+
export type GuestMiddlewarePolicy = 'own' | 'owner' | 'none';
|
|
15
|
+
export declare function guestMiddlewarePolicy(env?: NodeJS.ProcessEnv): GuestMiddlewarePolicy;
|
|
16
|
+
/**
|
|
17
|
+
* The `middleware` a workspace's own gateway target declares (relative to its root), found the way `lensmcp gateway
|
|
18
|
+
* start` finds the target: the project whose `targets.*.executor` is `@lensmcp/cluster:gateway`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function findGatewayMiddleware(root: string, projects: Readonly<Record<string, {
|
|
21
|
+
root: string;
|
|
22
|
+
}>>): string | undefined;
|
|
23
|
+
/** Load a middleware module fresh (an edit in the workspace takes effect on its next registration). */
|
|
24
|
+
export declare function loadMiddleware(file: string): EdgeMiddleware;
|
|
25
|
+
/** The daemon's middleware per workspace. */
|
|
26
|
+
export interface WorkspaceMiddleware {
|
|
27
|
+
/** A workspace registered: load its own middleware (`relPath` under `root`), or record that it has none. */
|
|
28
|
+
set(wsKey: string, root: string, relPath: string | undefined): void;
|
|
29
|
+
drop(wsKey: string): void;
|
|
30
|
+
/** The middleware for a route of workspace `wsKey` (undefined `wsKey` = the owner's, e.g. its dashboard route). */
|
|
31
|
+
for(wsKey: string | undefined): EdgeMiddleware | undefined;
|
|
32
|
+
}
|
|
33
|
+
export declare function createWorkspaceMiddleware(opts: {
|
|
34
|
+
ownerKey: string;
|
|
35
|
+
owner?: EdgeMiddleware;
|
|
36
|
+
policy?: GuestMiddlewarePolicy;
|
|
37
|
+
load?: (file: string) => EdgeMiddleware;
|
|
38
|
+
}): WorkspaceMiddleware;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var w=Object.defineProperty;var a=(r,t)=>w(r,"name",{value:t,configurable:!0});var c=Object.defineProperty,i=a((r,t)=>c(r,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.guestMiddlewarePolicy=guestMiddlewarePolicy,exports.findGatewayMiddleware=findGatewayMiddleware,exports.loadMiddleware=loadMiddleware,exports.createWorkspaceMiddleware=createWorkspaceMiddleware;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function guestMiddlewarePolicy(r=process.env){const t=r.LENSMCP_GUEST_MIDDLEWARE;return t==="owner"||t==="none"?t:"own"}a(guestMiddlewarePolicy,"guestMiddlewarePolicy"),i(guestMiddlewarePolicy,"guestMiddlewarePolicy");function findGatewayMiddleware(r,t){for(const n of Object.values(t)){let o;try{o=JSON.parse(fs.readFileSync(path.join(r,n.root,"project.json"),"utf8"))}catch{continue}for(const e of Object.values(o.targets??{}))if(e?.executor==="@lensmcp/cluster:gateway"&&typeof e.options?.middleware=="string"&&e.options.middleware)return e.options.middleware}}a(findGatewayMiddleware,"findGatewayMiddleware"),i(findGatewayMiddleware,"findGatewayMiddleware");function loadMiddleware(r){const t=require.resolve(r);delete require.cache[t];const n=require(t),o=n.default??n;if(typeof o!="function")throw new Error(`${r} exports no function`);return o}a(loadMiddleware,"loadMiddleware"),i(loadMiddleware,"loadMiddleware");function createWorkspaceMiddleware(r){const t=r.policy??"own",n=r.load??loadMiddleware,o=new Map;return{set(e,l,s){if(e===r.ownerKey)return;if(!s){o.set(e,void 0);return}const d=path.resolve(l,s);try{o.set(e,n(d)),t==="own"&&console.log(`[gateway] '${e}' routes run its own middleware: ${d}`)}catch(u){o.set(e,void 0),console.warn(`[gateway] could not load '${e}''s middleware ${d} (${u.message}) \u2014 its routes run with NO middleware until it registers again`)}},drop(e){o.delete(e)},for(e){if(e===void 0||e===r.ownerKey||t==="owner")return r.owner;if(t!=="none")return o.get(e)}}}a(createWorkspaceMiddleware,"createWorkspaceMiddleware"),i(createWorkspaceMiddleware,"createWorkspaceMiddleware");
|
|
@@ -17,6 +17,13 @@ export interface WorkspaceEntry {
|
|
|
17
17
|
procStamp?: string;
|
|
18
18
|
/** ISO timestamp of the last register. */
|
|
19
19
|
updatedAt: string;
|
|
20
|
+
/** Set when the workspace is a worktree INSTANCE of another (runtime/instance.ts): the chooser lists it under its
|
|
21
|
+
* base, and the base's dashboard lists it among its worktrees. */
|
|
22
|
+
instance?: {
|
|
23
|
+
name: string;
|
|
24
|
+
baseKey: string;
|
|
25
|
+
apex: string;
|
|
26
|
+
};
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* The start-time identity stamp for a pid (`''` when `ps` is unavailable — Windows, restricted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var ie=Object.defineProperty;var c=(e,r)=>ie(e,"name",{value:r,configurable:!0});var se=Object.defineProperty,o=c((e,r)=>se(e,"name",{value:r,configurable:!0}),"o");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")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");const frozen=o(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),U=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),B=e.configEnv||"development",R=path.join(i,"config",`config.${B}.yaml`),$=3050;let d=$,l=r.projectName,S={};fs.existsSync(R)?(S=yaml.load(fs.readFileSync(R,"utf8")),d=Number(S.port)||d,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${R} \u2014 public devserver port defaults to ${$}. If another service also lacks its config, both bind ${$} \u2192 EADDRINUSE. Add config/config.${B}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const O=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),z=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let T,D;e.gateway?.middleware&&(T=path.resolve(g,e.gateway.middleware),D=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",Z=path.resolve(g,K),W=path.resolve(g,Q),ee=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),q={appName:r.projectName,appRoot:g,outputDir:U,main:Z,tsConfig:W,assets:e.assets||[],additionalEntryPoints:ee,port:d,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let u=null,k=!1,I=!1,m=!1;const V=path.join(__dirname,"../../main.devserver.js"),H=path.join(__dirname,"../../main.devserver.ts"),J=!fs.existsSync(V)&&fs.existsSync(H),x=J?H:V,oe=path.join(U,"main.js");function A(){u&&!u.killed&&(u.kill("SIGTERM"),u=null)}c(A,"H"),o(A,"cleanup");function N(){m=!0,A()}c(N,"k"),o(N,"stop");const te=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>N(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",N);function E(){if(m)return;if(!fs.existsSync(x)){console.error(`[serve] devserver script not found at ${x}`);return}const t=["--enable-source-maps"];J&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(x,{cwd:i,env:{...process.env,PORT:String(d),SERVICE_NAME:l,SERVE_PREFIX:O,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:oe,...T&&{GATEWAY_MIDDLEWARE:T},...D&&{GATEWAY_CONFIG:D},...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)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:I?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});u=n,n.on("exit",(a,f)=>{u===n&&(u=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!u&&k&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${d} (service: ${l}${O?`, prefix: /${O}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. 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.`),z?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. 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 ${l}\` (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 ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!u||u.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),A(),E()})}c(re,"ne"),o(re,"_notifyDevServerReload");let y=null;const M=[];function h(t){if(y){const n=y;y=null,n(t)}else M.push(t)}c(h,"h"),o(h,"pushResult");function Y(){return M.length>0?Promise.resolve(M.shift()):new Promise(t=>{y=t})}c(Y,"ee"),o(Y,"waitForNext");let w=o(()=>{},"closeBuild");function F(){return!k||!u?(k=!0,E(),!0):!1}c(F,"J"),o(F,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(q),{watch:f,...P}=a,s=n(P);I=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const j=o((L,C)=>{if(L){console.error("[serve] Webpack error:",L),h({success:!1});return}const ne=C.hasErrors();if(console.info(C.toString({colors:!0,chunks:!1,assets:!0})),ne){h({success:!1});return}F(),h({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),s.run((L,C)=>{j(L,C),s.close(()=>{})});return}const G=s.watch(a.watchOptions||{},j);w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"A"),o(b,"compileOnItsOwn");function X(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:W,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),I=s.sourceMaps,f.built(s),!s.ok){a.tellWatching(),h({success:!1});return}const j=s.hash!==P;P=s.hash;const G=F();a.built(j&&!G),h({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(c(X,"re"),o(X,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:q,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?X(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await Y()}finally{N(),w(),te.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var v=Object.defineProperty;var
|
|
2
|
-
`).some(S=>{const f=S.trim();if(f.startsWith("#"))return!1;const y=f.split(/\s+/);return y[0]===
|
|
1
|
+
"use strict";var v=Object.defineProperty;var h=(o,t)=>v(o,"name",{value:t,configurable:!0});var m=Object.defineProperty,d=h((o,t)=>m(o,"name",{value:t,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=trustExecutor,exports.collectHosts=collectHosts;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")),basic_ssl_1=require("../../basic-ssl"),checkout_cache_1=require("../../checkout-cache"),hostname_1=require("./hostname");async function trustExecutor(o,t){const r=(0,checkout_cache_1.checkoutCacheDir)(t.root,"davnx-webpack"),c=(0,basic_ssl_1.ensureCaSync)(r),a=process.env.LENSMCP_TRUST_HOSTS_ONLY==="1",u=collectHosts(o,t);if(u.length===0&&console.log("[trust] no concrete hostnames found (only wildcards?) \u2014 keychain step only."),process.platform!=="darwin"){console.log("[trust] non-macOS platform \u2014 do these manually:"),console.log(` \u2022 trust ${c} in your OS/browser store (Linux: update-ca-certificates / certutil)`);for(const e of u)console.log(` \u2022 /etc/hosts: "127.0.0.1 ${e}" and "::1 ${e}"`);return{success:!0}}const s=caSha256Fingerprint(c),p=a||s!==void 0&&(0,node_child_process_1.spawnSync)("security",["find-certificate","-a","-c","lensmcp local dev CA","-Z","/Library/Keychains/System.keychain"],{encoding:"utf8"}).stdout?.includes(s)===!0;if(a)console.log("[trust] hosts-only: a shared daemon serves TLS with its own CA \u2014 skipping the keychain step.");else if(p)console.log("[trust] CA already in the System keychain \u2014 skipping.");else{if(console.log(`[trust] adding CA to the System keychain (sudo will prompt): ${c}`),(0,node_child_process_1.spawnSync)("sudo",["security","add-trusted-cert","-d","-r","trustRoot","-k","/Library/Keychains/System.keychain",c],{stdio:"inherit"}).status!==0)return console.error("[trust] add-trusted-cert failed \u2014 rerun in a terminal so sudo can prompt."),{success:!1};console.log("[trust] CA trusted. Restart your browser once (it caches certificate verdicts).")}const i=fs.readFileSync("/etc/hosts","utf8"),n=d((e,g)=>i.split(`
|
|
2
|
+
`).some(S=>{const f=S.trim();if(f.startsWith("#"))return!1;const y=f.split(/\s+/);return y[0]===e&&y.slice(1).includes(g)}),"hasEntry"),l=[];for(const e of u)n("127.0.0.1",e)||l.push(`127.0.0.1 ${e}`),n("::1",e)||l.push(`::1 ${e}`);if(l.length===0)console.log("[trust] /etc/hosts already complete (both families for every hostname).");else{if(console.log(`[trust] appending to /etc/hosts (sudo):
|
|
3
3
|
${l.join(`
|
|
4
4
|
`)}`),(0,node_child_process_1.spawnSync)("sudo",["tee","-a","/etc/hosts"],{input:l.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`,stdio:["pipe","ignore","inherit"]}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["dscacheutil","-flushcache"],{stdio:"inherit"}),(0,node_child_process_1.spawnSync)("sudo",["killall","-HUP","mDNSResponder"],{stdio:"inherit"})}if(!a){const
|
|
6
|
+
`,stdio:["pipe","ignore","inherit"]}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["dscacheutil","-flushcache"],{stdio:"inherit"}),(0,node_child_process_1.spawnSync)("sudo",["killall","-HUP","mDNSResponder"],{stdio:"inherit"})}if(!a){const e=(0,node_child_process_1.spawnSync)("security",["verify-cert","-c",c,"-p","basic"],{stdio:"ignore"});console.log(`[trust] CA trust check: ${e.status===0?"OK":"NOT TRUSTED YET (browser restart may be needed)"}`)}for(const e of u)console.log(`[trust] ${e} \u2192 https ready (hosts + cert SAN ride the gateway config)`);return{success:!0}}h(trustExecutor,"trustExecutor"),d(trustExecutor,"trustExecutor");function caSha256Fingerprint(o){const t=(0,node_child_process_1.spawnSync)("openssl",["x509","-in",o,"-noout","-fingerprint","-sha256"],{encoding:"utf8"});if(t.status!==0||!t.stdout)return;const r=t.stdout.split("=")[1]?.replaceAll(":","").trim().toUpperCase();return r&&/^[0-9A-F]{64}$/.test(r)?r:void 0}h(caSha256Fingerprint,"caSha256Fingerprint"),d(caSha256Fingerprint,"caSha256Fingerprint");function collectHosts(o,t){const r=[],c=t.projectName?t.projectsConfigurations?.projects[t.projectName]:void 0,a=o.serveTarget??"serve-hmr",u=c?.targets?.[a]?.options?.gateway?.routes??[];for(const s of u)s.host&&(0,hostname_1.isValidHostname)(s.host)?r.push(s.host):s.host&&console.warn(`[trust] ignoring invalid hostname in ${a} routes: ${JSON.stringify(s.host)}`);for(const s of Object.values(t.projectsConfigurations?.projects??{}))try{const p=JSON.parse(fs.readFileSync(path.join(t.root,s.root,"project.json"),"utf8")),i=p.cluster??p.davnx,n=i?.host;n&&!(0,hostname_1.isValidHostname)(n)&&console.warn(`[trust] ignoring invalid hostname in ${s.root}/project.json: ${JSON.stringify(n)}`),n&&(0,hostname_1.isValidHostname)(n)&&(r.push(n),i?.service&&i.internalHost!==!1&&r.push(typeof i.internalHost=="string"?i.internalHost:`internal.${n}`))}catch{}return[...new Set([...r,"lensmcp.local",...o.hosts??[]])]}h(collectHosts,"collectHosts"),d(collectHosts,"collectHosts");
|
package/inspector-secret.d.ts
CHANGED
|
@@ -37,3 +37,9 @@ export declare function forStream(stream: StdioStream, underGateway: boolean, te
|
|
|
37
37
|
* `/json/list` for the target. That opt-out already warns that its ports are reachable by every account.
|
|
38
38
|
*/
|
|
39
39
|
export declare function inspectorPublishExecArgv(fixedPorts: boolean): string[];
|
|
40
|
+
/**
|
|
41
|
+
* The TCP port out of a `ws://host:port/uuid` inspector URL, or `null`. Here rather than in main.devserver.ts, which
|
|
42
|
+
* starts a devserver the moment it is loaded: a spec that required it for this one function booted a parent and
|
|
43
|
+
* its pods inside the jest process, and they kept the run from ever exiting.
|
|
44
|
+
*/
|
|
45
|
+
export declare function inspectorPortOf(url: string | undefined): number | null;
|
package/inspector-secret.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var t=(r,e)=>o(r,"name",{value:e,configurable:!0});var n=Object.defineProperty,s=t((r,e)=>n(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=redactInspectorUrls,exports.streamMayCarrySecrets=streamMayCarrySecrets,exports.forStream=forStream,exports.inspectorPublishExecArgv=inspectorPublishExecArgv,exports.inspectorPortOf=inspectorPortOf;const INSPECTOR_TOKEN_RE=/((?:wss?:\/\/[^\s/]+\/|ws=[^\s/]+\/))([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;function redactInspectorUrls(r){return r.replace(INSPECTOR_TOKEN_RE,"$1<inspector-token-redacted>")}t(redactInspectorUrls,"redactInspectorUrls"),s(redactInspectorUrls,"redactInspectorUrls");function streamMayCarrySecrets(r,e){return!e&&r.isTTY===!0}t(streamMayCarrySecrets,"streamMayCarrySecrets"),s(streamMayCarrySecrets,"streamMayCarrySecrets");function forStream(r,e,c){return streamMayCarrySecrets(r,e)?c:redactInspectorUrls(c)}t(forStream,"forStream"),s(forStream,"forStream");function inspectorPublishExecArgv(r){return r?[]:["--inspect-publish-uid=stderr"]}t(inspectorPublishExecArgv,"inspectorPublishExecArgv"),s(inspectorPublishExecArgv,"inspectorPublishExecArgv");function inspectorPortOf(r){const e=/^wss?:\/\/[^/]*?:(\d+)\//.exec(r??""),c=e?Number(e[1]):NaN;return Number.isInteger(c)&&c>0?c:null}t(inspectorPortOf,"inspectorPortOf"),s(inspectorPortOf,"inspectorPortOf");
|
package/main.devserver.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Serializable } from 'node:child_process';
|
|
2
|
-
import { redactInspectorUrls } from './inspector-secret';
|
|
2
|
+
import { inspectorPortOf, redactInspectorUrls } from './inspector-secret';
|
|
3
|
+
import { gatewayManaged, isOwnerOnlyTransport, mayDriveThePool } from './devserver-transport';
|
|
3
4
|
export interface GatewayRoute {
|
|
4
5
|
/** Hostname to match — exact (`api.tetros.localhost`) or wildcard (`*.tetros.localhost`). */
|
|
5
6
|
host?: string;
|
|
@@ -15,22 +16,6 @@ export interface GatewayRoute {
|
|
|
15
16
|
*/
|
|
16
17
|
prependPrefix?: string;
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Did this request arrive on the OWNER-ONLY control socket, rather than the public TCP port?
|
|
20
|
-
*
|
|
21
|
-
* `requestHandler` serves both: the Unix socket at `$TMPDIR/<ws>/<svc>-devserver/parent.sock`
|
|
22
|
-
* (0700 dir, see issues/closed/codex-security-23) and `server.listen(publicPort)`. Admin branches were
|
|
23
|
-
* reachable on both, so `POST /webpack/debug` — which opens a Node inspector in every pod and
|
|
24
|
-
* returns the ws:// URLs, and whose UUID is the only thing guarding `Runtime.evaluate` — could be
|
|
25
|
-
* called by anything that could reach the port (issues/closed/codex-security-05).
|
|
26
|
-
*
|
|
27
|
-
* Node's `Server.address()` returns the PATH STRING for a pipe/Unix socket and an object
|
|
28
|
-
* `{ address, family, port }` for TCP. That is the discriminator, and it needs no extra plumbing
|
|
29
|
-
* through the handler.
|
|
30
|
-
*/
|
|
31
|
-
export declare function isOwnerOnlyTransport(req: {
|
|
32
|
-
socket?: unknown;
|
|
33
|
-
}): boolean;
|
|
34
19
|
/** Either end of an IPC channel: a pod or worker as its parent holds it, or the pod's own `process`. */
|
|
35
20
|
export interface IpcEndpoint {
|
|
36
21
|
connected?: boolean;
|
|
@@ -47,24 +32,7 @@ export interface IpcEndpoint {
|
|
|
47
32
|
* did not go, for a caller that has something else to do then.
|
|
48
33
|
*/
|
|
49
34
|
export declare function tell(endpoint: IpcEndpoint, message: Serializable): boolean;
|
|
50
|
-
|
|
51
|
-
export declare function gatewayManaged(env?: NodeJS.ProcessEnv): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* May this request DRIVE THE POOL — `/webpack/reload`, `/webpack/watching`, `/webpack/scale`?
|
|
54
|
-
*
|
|
55
|
-
* Under a gateway, only the control socket may. The TCP port is not an identity: it comes from a
|
|
56
|
-
* tracked config file, so every checkout of a repo has the same number. A second checkout's FIRST
|
|
57
|
-
* build has no control socket yet, so its plugin fell back to `localhost:<port>` — and that port
|
|
58
|
-
* belonged to the first checkout, whose pods it hot-swapped (issues/closed/foodguard-35). Nothing checked
|
|
59
|
-
* who was asking. The gateway and the service's own build plugin both reach the control socket,
|
|
60
|
-
* which lives in the workspace's owner-only socket dir; nobody else has a reason to.
|
|
61
|
-
*
|
|
62
|
-
* A devserver run by hand has no gateway and no second checkout behind one door; its port stays
|
|
63
|
-
* its admin surface, as before.
|
|
64
|
-
*/
|
|
65
|
-
export declare function mayDriveThePool(req: {
|
|
66
|
-
socket?: unknown;
|
|
67
|
-
}, managed?: boolean): boolean;
|
|
35
|
+
export { gatewayManaged, isOwnerOnlyTransport, mayDriveThePool };
|
|
68
36
|
/**
|
|
69
37
|
* Opt-out that restores the pre-`06` PREDICTABLE inspector ports (`base+slot`, `base+20+i`).
|
|
70
38
|
*
|
|
@@ -106,6 +74,4 @@ export declare function inspectorPortFor(basePort: number, offset: number, env?:
|
|
|
106
74
|
* (control socket) still gets the attachable URL; everyone else gets the shape and no secret. What the
|
|
107
75
|
* devserver writes to its own stdout/stderr and to its log tee is redacted the same way (issues/closed/pod-memory-06).
|
|
108
76
|
*/
|
|
109
|
-
export { redactInspectorUrls };
|
|
110
|
-
/** The TCP port out of a `ws://host:port/uuid` inspector URL, or `null`. */
|
|
111
|
-
export declare function inspectorPortOf(url: string | undefined): number | null;
|
|
77
|
+
export { inspectorPortOf, redactInspectorUrls };
|
package/main.devserver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var Ve=Object.defineProperty;var f=(i,h)=>Ve(i,"name",{value:h,configurable:!0});var Ke=Object.defineProperty,n=f((i,h)=>Ke(i,"name",{value:h,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=exports.inspectorPortOf=exports.DEBUG_FIXED_PORTS_ENV=exports.mayDriveThePool=exports.isOwnerOnlyTransport=exports.gatewayManaged=void 0,exports.tell=tell,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),v8=tslib_1.__importStar(require("node:v8")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),inspector_secret_1=require("./inspector-secret");Object.defineProperty(exports,"inspectorPortOf",{enumerable:!0,get:n(function(){return inspector_secret_1.inspectorPortOf},"get")}),Object.defineProperty(exports,"redactInspectorUrls",{enumerable:!0,get:n(function(){return inspector_secret_1.redactInspectorUrls},"get")});const devserver_transport_1=require("./devserver-transport");Object.defineProperty(exports,"gatewayManaged",{enumerable:!0,get:n(function(){return devserver_transport_1.gatewayManaged},"get")}),Object.defineProperty(exports,"isOwnerOnlyTransport",{enumerable:!0,get:n(function(){return devserver_transport_1.isOwnerOnlyTransport},"get")}),Object.defineProperty(exports,"mayDriveThePool",{enumerable:!0,get:n(function(){return devserver_transport_1.mayDriveThePool},"get")});const schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),stdio_guard_1=require("./stdio-guard"),bundle_parent_1=require("./bundle-parent"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),pod_memory_1=require("./pod-memory"),pod_memory_events_1=require("./pod-memory-events"),heap_snapshot_1=require("./heap-snapshot"),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const i=process.env.GATEWAY_CONFIG;if(!i)return{};try{return JSON.parse(i)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const i=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof i=="function"?i:typeof i.default=="function"?i.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(i){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,i)}function applyGatewayMiddleware(i){if(gatewayMiddleware)try{gatewayMiddleware(i,GATEWAY_CONFIG)}catch(h){console.error("[gateway] Middleware error:",h)}}f(applyGatewayMiddleware,"applyGatewayMiddleware"),n(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(i,h){if(i.startsWith("*.")){const y=i.slice(1);return h.endsWith(y)||h===i.slice(2)}return h===i}f(hostMatches,"hostMatches"),n(hostMatches,"hostMatches");function matchGatewayRoute(i){if(GATEWAY_ROUTES.length===0)return;const h=(i.headers.host||"").split(":")[0],y=i.url||"/";for(const S of GATEWAY_ROUTES)if(!(S.host&&!hostMatches(S.host,h))&&!(S.prefix&&!y.startsWith(S.prefix)))return S}f(matchGatewayRoute,"matchGatewayRoute"),n(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_NS=process.env.LENSMCP_SOCK_NS||LENSMCP_WS_KEY,SOCK_DIR=SOCK_NS?path.join(os.tmpdir(),SOCK_NS,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),SOCKET_ONLY=process.env.LENSMCP_SOCKET_ONLY==="1",POD_GENERATION=(()=>{const i=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(i)&&i>1?Math.floor(i):1})();function childSockPath(i){return path.join(SOCK_DIR,POD_GENERATION>1?`child-${i}-g${POD_GENERATION}.sock`:`child-${i}.sock`)}f(childSockPath,"childSockPath"),n(childSockPath,"childSockPath");function sockOwnerPath(i){return`${i}.owner`}f(sockOwnerPath,"sockOwnerPath"),n(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(i){return`${i}.retiring`}f(sockRetiringPath,"sockRetiringPath"),n(sockRetiringPath,"sockRetiringPath");function tell(i,h){if(!i.connected||typeof i.send!="function")return!1;try{return i.send(h,()=>{})}catch{return!1}}f(tell,"tell"),n(tell,"tell");function refusePoolCommand(i,h){console.warn(`[devserver] refused ${i.method} ${i.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),h.writeHead(404,{"content-type":"application/json"}),h.end(JSON.stringify({error:"not found"}))}f(refusePoolCommand,"refusePoolCommand"),n(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(i=process.env){const h=(i[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return h==="1"||h==="true"||h==="yes"||h==="on"}f(usesFixedInspectorPorts,"usesFixedInspectorPorts"),n(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(i,h,y=process.env){if(!usesFixedInspectorPorts(y))return 0;const S=Math.trunc(i)+Math.trunc(h);return Number.isInteger(S)&&S>1024&&S<=65535?S:0}f(inspectorPortFor,"inspectorPortFor"),n(inspectorPortFor,"inspectorPortFor");function ensureOwnerOnlyDir(i){try{fs.mkdirSync(i,{recursive:!0,mode:448})}catch{}try{const h=fs.lstatSync(i);h.isDirectory()&&(h.mode&63)!==0&&fs.chmodSync(i,448)}catch{}}f(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),n(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(i,h=process.pid){ensureOwnerOnlyDir(path.dirname(i));try{const y=sockOwnerPath(i);try{fs.unlinkSync(y)}catch{}fs.writeFileSync(y,String(h),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(i)}catch{}try{fs.unlinkSync(sockRetiringPath(i))}catch{}}f(claimSock,"claimSock"),n(claimSock,"claimSock");function sockOwnedByAnother(i,h=process.pid){try{const y=fs.readFileSync(sockOwnerPath(i),"utf8").trim();return!!y&&y!==String(h)}catch{return!1}}f(sockOwnedByAnother,"sockOwnedByAnother"),n(sockOwnedByAnother,"sockOwnedByAnother");function cleanupSock(i,h=process.pid){if(i){try{const y=fs.readFileSync(sockOwnerPath(i),"utf8").trim();if(y&&y!==String(h))return}catch{}try{fs.unlinkSync(i)}catch{}try{fs.unlinkSync(sockOwnerPath(i))}catch{}try{fs.unlinkSync(sockRetiringPath(i))}catch{}}}f(cleanupSock,"cleanupSock"),n(cleanupSock,"cleanupSock");function serviceKeyFingerprint(){const i=process.env.LENSMCP_SERVICE_KEY;if(!i)return;const{createHash:h}=require("node:crypto");return h("sha256").update(i).digest("hex").slice(0,8)}f(serviceKeyFingerprint,"serviceKeyFingerprint"),n(serviceKeyFingerprint,"serviceKeyFingerprint");function rootModuleOf(i){let h;for(const y of i.getModules?.()?.values()??[])typeof y?.metatype!="function"||typeof y.distance!="number"||!Number.isFinite(y.distance)||(!h||y.distance<h.distance)&&(h=y);return h?.metatype}f(rootModuleOf,"rootModuleOf"),n(rootModuleOf,"rootModuleOf");let lastImportedModule;function takeImportedModule(){const i=lastImportedModule;return lastImportedModule=void 0,i}f(takeImportedModule,"takeImportedModule"),n(takeImportedModule,"takeImportedModule");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}eval(`(() => {
|
|
2
2
|
const key = ${JSON.stringify(resolved)};
|
|
3
3
|
const previous = require.cache[key];
|
|
4
4
|
delete require.cache[key]; // delete from CJS cache
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
const at = owner && Array.isArray(owner.children) ? owner.children.indexOf(previous) : -1;
|
|
10
10
|
if (at >= 0) owner.children.splice(at, 1);
|
|
11
11
|
}
|
|
12
|
-
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}h(importFresh,"importFresh"),n(importFresh,"importFresh");function debounce(s,u){let f=null;return(...S)=>{f&&clearTimeout(f),f=setTimeout(()=>s(...S),u)}}if(h(debounce,"debounce"),n(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const s=n(f=>{if(!inspector.url())try{inspector.open(f,"127.0.0.1",!1)}catch(G){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,G.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const S=inspector.url();S&&tell(process,{type:"inspector-url",url:S})},"openInspector"),u=process.env.CHILD_DEBUG_PORT;if(u!==void 0&&u!==""){const f=Number(u);Number.isInteger(f)&&(f===0||f>1024)&&s(f)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let f=null,S=!1,G,J=!1;const De=(0,hot_swap_registries_1.registryPruningEnabled)(),te=n((t,i)=>{const p=(0,hot_swap_registries_1.describePrune)(t,i);p&&console.log(p)},"logPrune");let F={app:null,handler:n((t,i)=>{i.statusCode=503,i.end("starting")},"handler"),inflight:0};const re=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),R=n(t=>{const i=globalThis.__lensmcpActiveWork;if(typeof i!="function")return{count:0};try{const p=i(t);return{count:Number(p?.count??0),...p?.oldestStartedAt?{oldestStartedAt:p.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),ge=n(t=>{const i=globalThis.__lensmcpStopWorkIntake;if(typeof i=="function")try{return i(t)}catch{return}},"stopWorkIntake"),oe=n(t=>{const i=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof i!="function"))try{return i(t)??void 0}catch{return}},"workIntakeReport"),be=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),q=new Set,A=(0,swap_recycle_1.swapRecyclePolicy)();let L,j=0;const Ae=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let ne=!1,X=!1;const xe=(0,pod_memory_1.podMemoryPolicy)();let Ne=0;const me=new Set;let y=!1;const v=n(()=>{if(!y)return;let t=0;for(const i of me)i.deref()===void 0?me.delete(i):t+=1;return t},"liveContainers"),_=(0,pod_memory_1.createPodMemoryMonitor)({policy:xe,state:n(()=>({gen:F.gen??0,swaps:j,...L===void 0?{}:{baseline:L},busy:S||J||N.size>0,lastBusyAt:T}),"state"),send:n(t=>{tell(process,t)},"send"),containers:v,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:n(()=>ie(),"onRetained")}),W=n(()=>((0,pod_memory_1.fullGc)()&&_.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),$=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,i=`${path.sep}node_modules${path.sep}`,p=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:n(g=>p.has(g)||g.startsWith(t)&&!g.slice(t.length).includes(i)&&!g.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let se=!1;function x(){if(!(!$||se))try{const t=$();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),se=!0}}h(x,"C"),n(x,"releaseLibraryErrorFrames");const N=new Map;let T=Date.now();const C=n(()=>A.settleMs<=0||!S&&!J&&N.size===0&&Date.now()-T>=A.settleMs,"settled");let V;function he(t){if(!(!A.enabled||ne||X)){if(A.settleMs<=0){z(t);return}V&&clearTimeout(V),V=setTimeout(()=>{V=void 0,C()?z(t):he(t)},A.settleMs),V.unref?.()}}h(he,"_e"),n(he,"recycleCheckWhenSettled");const fe=n(()=>{if(A.stuckCloseMs<=0)return 0;const t=Date.now();let i=0;for(const p of N.values())t-p>=A.stuckCloseMs&&(i+=1);return i},"stuckCloses");function z(t){if(!A.enabled||ne||X||!process.send)return;const i=v8.getHeapStatistics().heap_size_limit,p=n(m=>{const E=_.oldestRetained();return{swaps:j,usedBytes:m,limitBytes:i,stuckCloses:fe(),settled:C(),...L===void 0?{}:{baselineBytes:L},...E?{retained:{generation:E.gen,ageMs:Date.now()-E.closedAt,gcs:E.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(A,p(process.memoryUsage().heapUsed)))return;const g=(0,swap_recycle_1.recycleReason)(A,p(W()));g&&(ne=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${g}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:g}))}h(z,"ee"),n(z,"considerRecycle");let Y;function ie(){if(!A.enabled||A.retainedMs<=0||ne||X)return;const t=_.oldestRetained();if(!t)return;const i=t.closedAt+A.retainedMs-Date.now();if(i<=0){z("with a closed generation still in memory");return}Y||(Y=setTimeout(()=>{Y=void 0,_.measure("retention-check").catch(()=>{})},i),Y.unref?.())}h(ie,"ae"),n(ie,"retainedRecycleCheck");function ae(t,i){const p={};i&&(p.module=i),t&&(p.app=t);try{const g=t?.app;if(g&&typeof g=="object"&&(p.nestApp=g,g.container&&typeof g.container=="object")){p.container=g.container;const m=rootModuleOf(g.container);m&&(p.rootModule=m)}}catch{}return p}h(ae,"ye"),n(ae,"generationTargets");async function ce(t,i){if(t.closed||(t.closed=!0,q.delete(t),i&&console.error(`[child] ${i}`),!t.app))return;N.set(t,Date.now());const p=A.enabled&&A.stuckCloseMs>0?setTimeout(()=>{N.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(A.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),z("with a previous app stuck in close()"))},A.stuckCloseMs):void 0;p?.unref?.();const g=t.app;try{await t.app.close()}catch(m){console.error("[child] error closing a previous app:",m)}finally{N.delete(t),T=Date.now(),p&&clearTimeout(p),t.app=null,t.handler=(m,E)=>{E.statusCode=503,E.end("closed")},t.gen!==void 0&&_.closed(t.gen,j,ae(g,t.module)),t.module=void 0}if(t.superseded){try{te((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(m){console.error("[child] could not release the previous app's class registrations:",m)}t.superseded=void 0}he("after closing the previous app")}h(ce,"ge"),n(ce,"closeGeneration");async function Q(t){for(;q.size>=be;){const E=q.values().next().value;if(!E)break;await ce(E,`closing a still-draining generation early \u2014 ${q.size} were pending and the cap is ${be} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}q.add(t);const i=Date.now(),p=n(()=>({inflight:t.inflight,work:R(t.workGen).count}),"busy");for(;re>0&&Date.now()-i<re;){if(t.closed)return;const E=p();if(E.inflight===0&&E.work===0)break;await new Promise(H=>{setTimeout(H,250).unref?.()})}if(t.closed)return;const g=p(),m=oe(t.workGen);g.inflight>0||g.work>0?console.error(`[child] swap drain expired after ${Date.now()-i}ms \u2014 closing the previous app with ${g.inflight} in-flight request(s) and ${g.work} background job(s) still running. `+(m?`Previous app still processing ${m.running} queue job(s); ${m.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${re}ms) if this is legitimate work.`):(Date.now()-i>250||m?.runningAtStop||m?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-i}ms \u2014 closing it`+(m?` (it finished ${m.finishedAfterStop} queue job(s) after the swap; ${m.takenAfterStop} were taken after the swap)`:"")),await ce(t)}h(Q,"ce"),n(Q,"drainThenClose");async function Z(){if(S)return J=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),f;S=!0,T=Date.now(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(b,U)=>{console.error(`[child] uncaughtException (${U}):`,b)}),process.on("unhandledRejection",b=>{const U=b instanceof Error?b.stack||b.message:String(b);U.includes("FronteggContextScope")||U.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",b)});let t;if(De)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const i=global.__lensmcpBeginWorkGeneration,p=typeof i=="function"?i():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(b){if(!(b instanceof SyntaxError))throw b;console.warn(`[child] bundle was unreadable mid-write (${b.message}) \u2014 retrying the swap once`),await new Promise(U=>setTimeout(U,400)),await importFresh(BUNDLE_PATH)}}catch(b){throw S=!1,T=Date.now(),b}if(typeof global.createChildApp!="function")throw S=!1,T=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let g;if(t)try{g=(0,hot_swap_registries_1.supersededSince)(t),te((0,hot_swap_registries_1.pruneRegistries)(g,"build"),"at the swap")}catch(b){console.error("[child] could not release superseded class registrations:",b)}let m;try{m=await global.createChildApp()}catch(b){throw S=!1,T=Date.now(),takeImportedModule(),b}const E=F;g?.size&&(E.app?E.superseded=g:te((0,hot_swap_registries_1.pruneRegistries)(g,"serve"),"at the swap")),f=m;const H=takeImportedModule();F={app:m,handler:m.handler,inflight:0,gen:++Ne,...H?{module:H}:{},...p===void 0?{}:{workGen:p}};const de=ae(m,void 0).container;de&&(me.add(new WeakRef(de)),y=!0);const K=E.app&&p!==void 0?ge(p):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(K?.workers?`; its ${K.workers} queue worker(s) stopped taking new jobs, previous app still processing ${K.runningAtStop} job(s)`:"")),x(),E.app&&(j+=1,Q(E).catch(b=>console.error("[child] background close error:",b)),setTimeout(()=>z("30s after a hot swap"),3e4).unref?.(),_.swapped(E.gen,()=>E.closed===!0&&!N.has(E))),S=!1,T=Date.now(),J?(J=!1,Z()):m}h(Z,"le"),n(Z,"swapNow");const ye=debounce(async()=>{try{console.log("[child] /webpack/reload"),await Z()}catch(t){console.error("[child] reload failed:",t)}},250),le=http.createServer((t,i)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){i.writeHead(200,{"content-type":"application/json"}),i.end(JSON.stringify({ok:!0})),ye();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const g=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&g!==m){i.writeHead(404,{"content-type":"text/plain"}),i.end("not found");return}const E=R();i.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),i.end(JSON.stringify({ok:!0,pid:process.pid,swapping:S,inflight:F.inflight,oldestInflightAt:F.oldestAt,activeWork:E.count,oldestWorkAt:E.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...G?{watcher:G}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const p=F;p.inflight+=1,p.oldestAt===void 0&&(p.oldestAt=Date.now()),i.on("close",()=>{p.inflight-=1,p.inflight===0&&(p.oldestAt=void 0),_.activity()}),p.handler(t,i)});le.keepAliveTimeout=0;const I=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(I),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const i=(0,schema_first_instance_1.prewarmZodFor)(t);i?.created&&console.log(`[child] zod${i.version?` ${i.version}`:""}: built ${i.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const i=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);i?.patched&&console.log(`[child] ioredis${i.versions.length?` ${i.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await Z(),L=(await _.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock(I),le.listen(I,()=>{console.log(`[child] Listening on ${I}`),tell(process,{type:"ready",socketPath:I})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const i=Number(t.port);s(Number.isInteger(i)&&i>1024?i:0);return}if(t.type==="watcher"){const i=t.testimony;i&&typeof i=="object"&&(G=i);return}if(t.type==="heap-snapshot"){_e(t);return}if(t.type==="recycle-declined"){ne=!1;return}if(t.type==="retire"){ee();return}if(t.type==="reload"){if(X)return;try{await Z(),tell(process,{type:"reloaded",socketPath:I})}catch(i){console.error("[child] reload error:",i),tell(process,{type:"reload-error",error:String(i?.stack||i)})}}}});let pe=!1;async function _e(t){const i=n(p=>{tell(process,{type:"heap-snapshot-result",id:t.id,...p})},"reply");if(pe)return i({ok:!1,refused:"a snapshot of this pod is already being written"});pe=!0;try{const p=W(),g=_.tracker.views().map(de=>de.gen),m=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(m.maxHeapBytes=t.maxHeapBytes);const E=(0,heap_snapshot_1.heapSnapshotRefusal)(m,{live:p,...t.available?{available:t.available}:{}});if(E)return i({ok:!1,refused:E,live:p});console.log(`[child] writing a heap snapshot (${Math.round(p/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const H=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);i({ok:!0,file:t.file,bytes:H.bytes,ms:H.ms,live:p,gen:F.gen??0,closedAlive:g})}catch(p){i({ok:!1,error:p?.message??String(p)})}finally{pe=!1}}h(_e,"De"),n(_e,"takeHeapSnapshot");async function ee(){if(X)return;X=!0,_.stop(),console.log("[child] retiring \u2014 a fresh pod has taken over; finishing in-flight work, then exiting");const t=ge(Number.POSITIVE_INFINITY);t?.workers&&console.log(`[child] its ${t.workers} queue worker(s) stopped taking new jobs, still processing ${t.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath(I),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(p=>{setTimeout(p,Ae).unref?.()}),cleanupSock(I),le.close();const i=Date.now();for(;Date.now()-i<re&&(le.closeIdleConnections?.(),!(F.inflight===0&&R().count===0));)await new Promise(p=>{setTimeout(p,250).unref?.()});setTimeout(()=>process.exit(0),3e4).unref?.();try{await f?.close()}catch(p){console.error("[child] close error while retiring:",p)}process.exit(0)}h(ee,"re"),n(ee,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const we=n(async t=>{console.log(`[child ${t}] shutting down\u2026`);const i=f?.close().catch(g=>console.error("[child] close error:",g)),p=new Promise(g=>le.close(()=>g()));cleanupSock(I),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([i,p]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{inspector?.close?.(),we(t)})})})()}else{let s=h(function(y){L.push(y),L.length>A&&L.splice(0,L.length-A),be?.write((0,inspector_secret_1.redactInspectorUrls)(y.replace(q,"")));for(const v of j)try{const _=v.plain?y.replace(q,""):y;v.res.write(v.redact?(0,inspector_secret_1.redactInspectorUrls)(_):_)}catch{j.delete(v)}},"z");n(s,"emitLogLine");const u=12,f=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",S=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),G=n(y=>({project:f,service:SERVICE_NAME||f,pod:y.id,pid:y.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),J=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],De="\x1B[0m",te=Math.max(1,Number(process.env.CHILD_COUNT||1)),F=LENSMCP_WS_KEY||path.basename(process.cwd()),re=n(y=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(F,SERVICE_NAME||"service",y)),"execPathFor");let R=(()=>{const y=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(y)&&y>1024?y:0})();const ge=20;R&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${R}+slot, workers ${R}+${ge}+i). Any account on this host can connect to 127.0.0.1:${R} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const oe=process.env.LENSMCP_LOG_FILE||"",be=oe?(()=>{try{fs.mkdirSync(path.dirname(oe),{recursive:!0});const y=fs.createWriteStream(oe,{flags:"a",mode:384});try{fs.chmodSync(oe,384)}catch{}return y}catch(y){return console.warn(`[parent] cannot open log file ${oe}:`,y.message),null}})():null,q=/\x1b\[[0-9;]*m/g,A=2e3,L=[],j=new Set,Ae=gatewayManaged();for(const y of["log","info","warn","error"]){const v=console[y].bind(console),_=y==="log"||y==="info"?process.stdout:process.stderr;console[y]=(...W)=>{v(...W.map($=>typeof $=="string"?(0,inspector_secret_1.forStream)(_,Ae,$):$)),s(W.map($=>typeof $=="string"?$:util.inspect($)).join(" ")+`
|
|
13
|
-
`)}}const ne=n(
|
|
14
|
-
`+String(x),"stack")}}),$=n((x,
|
|
15
|
-
`;T.write((0,inspector_secret_1.forStream)(T,Ae,`${v}${C}`)),s(`${v}${C}`)},"write"),se=n((x,N)=>{if(!x)return;const T=readline.createInterface({input:x});T.on("line",C=>$(N,C)),T.on("close",()=>{})},"attach");se(y.stdout,"stdout"),se(y.stderr,"stderr")},"wireProcLogging"),me=n(y=>Ne(y.proc,X(y.id),v=>y.tail.push(v)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const y=httpProxy.createProxyServer({});let v=Number(process.env.PORT)||0;v||(v=9090);const _=[],W=new Set;function $(){let e=0;for(;W.has(e);)e++;return W.add(e),e}h($,"I"),n($,"allocDebugSlot");let se=1,x=0,N=!1,T=te,C=[],V=0;const he=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),fe=n(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),z=n(e=>e.booting&&!fe(e)&&Date.now()-e.spawnedAt<he,"stillBooting");function Y(){return _.filter(e=>e.healthy&&typeof e.socketPath=="string")}h(Y,"J"),n(Y,"healthyChildren");let ie;function ae(e){if(!e||typeof e!="object")return;const o=e;if(o.schemaVersion===1){ie=o;for(const l of _)tell(l.proc,{type:"watcher",testimony:o})}}h(ae,"ye"),n(ae,"recordWatcherTestimony");function ce(e,o=256*1024){return new Promise(l=>{let d="",a=!1;e.on("data",r=>{a||(d+=r,d.length>o&&(a=!0))}),e.on("end",()=>{if(a||!d)return l(void 0);try{l(JSON.parse(d))}catch{l(void 0)}}),e.on("error",()=>l(void 0))})}h(ce,"ge"),n(ce,"readJsonBody");function Q(){const e=Y();if(e.length===0)return null;const o=x%e.length;return x=(x+1)%e.length,e[o]}h(Q,"ce"),n(Q,"pickChild");function Z(e,o){const l=`Child#${e.id}`,d=SERVICE_NAME||f,a=Date.now();if(o.type==="pod-memory"){const r=o.report;if(!r||typeof r.live!="number")return;e.memory.latest=r,e.memory.history.push(r),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(l,r)}`),S.publish((0,pod_memory_events_1.buildPodMemoryEvent)(G(e),r))}else if(o.type==="pod-memory-retained"){const r=o.retained;if(!r||typeof r.gen!="number")return;e.memory.retained.set(r.gen,{...r,at:a}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(l,d,r)}`),S.publish((0,pod_memory_events_1.buildPodRetainedEvent)(G(e),r,a))}else{const r=o.released;if(!r||typeof r.gen!="number")return;e.memory.retained.delete(r.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(l,d,r)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(G(e),r,a))}}h(Z,"le"),n(Z,"recordPodMemory");const ye=new Map;let le=0;async function I(){const e=se++,o=childSockPath(e);cleanupSock(o);const l=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&l.unshift("--require","@swc-node/register"),l.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const d=R?$():-1,a=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:o,...d>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(R,d))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:l,execPath:re("pod")}),r={id:e,proc:a,healthy:!1,lastError:null,debugSlot:d,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:V,memory:{history:[],retained:new Map}};return a.on("error",w=>console.error(`[parent] Child#${e}: ${w.message}`)),a.on("message",w=>{if(!w||typeof w!="object")return;const c=w;c.type==="ready"?(r.socketPath=String(c.socketPath),r.healthy=!0,r.booting=!1,console.log(`[parent] Child#${r.id} ready on socket ${r.socketPath}`),ie&&tell(a,{type:"watcher",testimony:ie}),V!==r.bootReloadSeq&&tell(a,{type:"reload"})):c.type==="recycle-request"?g(r,String(c.reason||"it has outgrown its bound")):c.type==="inspector-url"?(r.inspectorUrl=String(c.url),console.log(`[parent] Child#${r.id} Debugger listening on ${c.url}`)):c.type==="reloaded"?(r.socketPath=String(c.socketPath),r.healthy=!0,console.log(`[parent] Child#${r.id} hot-swapped on socket ${r.socketPath}`)):c.type==="reload-error"?(r.lastError=String(c.error||"unknown reload error"),console.error(`[parent] Child#${r.id} reload error: ${r.lastError}`),r.healthy=!1):c.type==="boot-error"?(r.lastError=String(c.error||"unknown boot error"),console.error(`[parent] Child#${r.id} boot error: ${r.lastError}`),r.healthy=!1,r.booting=!1):c.type==="pod-memory"||c.type==="pod-memory-retained"||c.type==="pod-memory-released"?Z(r,c):c.type==="heap-snapshot-result"&&ye.get(Number(c.id))?.(c)}),a.on("exit",(w,c)=>{r.booting=!1;for(const M of r.memory.retained.values()){const O={gen:M.gen,closedAt:M.closedAt,ageMs:Date.now()-M.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${r.id}`,SERVICE_NAME||f,O)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(G(r),O,Date.now()))}r.memory.retained.clear(),r.memory.latest&&S.publish((0,pod_memory_events_1.buildPodExitedEvent)(G(r),{code:w,signal:c},Date.now())),r.debugSlot>=0&&(W.delete(r.debugSlot),r.debugSlot=-1);const P=c==="SIGINT"||c==="SIGTERM"||w===0;if(P?console.log(`[parent] Child#${r.id} exited cleanly (code=${w}, signal=${c??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${r.id} crashed (code=${w}, signal=${c??"none"}, pid=${r.proc.pid}, ${r.healthy?"was serving":"was not ready"})`,w,c,r.tail.snapshot())),r.lastError||(r.lastError=`Child exited abnormally (code=${w}, signal=${c??"none"})`)),r.healthy=!1,cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0,!P&&!N){const M=_.indexOf(r);M>=0&&_.splice(M,1);const O=Date.now();if(C=C.filter(B=>O-B<3e4),C.length>=5){console.error(`[parent] crash-loop detected (${C.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}C.push(O),setTimeout(()=>{const B=_.filter(Pe=>Pe.proc.killed===!1).length;!N&&B<T&&(console.log(`[parent] respawning a crashed pod (${B}/${T} live)`),I().catch(()=>{}))},1500).unref?.()}}),_.push(r),me(r),r}h(I,"M"),n(I,"spawnChild");async function pe(e){T=Math.max(T,e);const o=_.filter(d=>d.proc.killed===!1),l=e-o.length;for(let d=0;d<l;d++)await I()}h(pe,"pe"),n(pe,"ensurePoolSize");async function _e(){const e=_.filter(o=>(!o.healthy||typeof o.socketPath!="string")&&!z(o));await Promise.all(e.map(async o=>{try{o.proc.kill("SIGTERM")}catch{}cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0;const l=_.indexOf(o);l>=0&&_.splice(l,1),await I()}))}h(_e,"De"),n(_e,"respawnUnhealthy");const ee=new Set;let we=!1,t=0;const i=3e4,p=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function g(e,o){const l=n(a=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${o}) \u2014 not now: ${a}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(N||!_.includes(e))return;if(we)return l("another pod is already being replaced");const d=Date.now()-t;if(d<i)return l(`a pod was replaced ${Math.round(d/1e3)}s ago`);we=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${o}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const a=await I(),r=Date.now()+he;for(;!a.healthy&&!a.lastError&&!fe(a)&&Date.now()<r;)await new Promise(P=>{setTimeout(P,100).unref?.()});if(N)return;if(!a.healthy){const P=_.indexOf(a);P>=0&&_.splice(P,1);try{a.proc.kill("SIGTERM")}catch{}return l(`its successor Child#${a.id} did not come up (${a.lastError??"no answer in time"})`)}const w=_.indexOf(e);w>=0&&_.splice(w,1),e.healthy=!1,ee.add(e);const c=setTimeout(()=>{if(!fe(e)){console.error(`[parent] Child#${e.id} was still running ${p}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},p);if(c.unref?.(),e.proc.once("exit",()=>{clearTimeout(c),ee.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}t=Date.now(),console.log(`[parent] Child#${a.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${a.proc.pid})`)}catch(a){l(`could not start a successor: ${a?.message??String(a)}`)}finally{we=!1}}h(g,"_"),n(g,"recyclePod");const m=[];function E(e){const o=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),l=path.join(o,`${e}.js`);if(!fs.existsSync(l))return console.error(`[parent] Worker "${e}" bundle not found: ${l}`),null;const d=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&d.unshift("--require","@swc-node/register");try{d.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const a=inspectorPortFor(R||9229,ge+Math.max(0,WORKER_NAMES.indexOf(e)));d.push(R?`--inspect=127.0.0.1:${a}`:`--inspect-port=127.0.0.1:${a}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const r=(0,node_child_process_1.fork)(l,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:d,execPath:re(`w-${e}`)}),w={name:e,proc:r,debugPort:a};return m.push(w),r.on("error",c=>console.error(`[parent] Worker "${e}": ${c.message}`)),Ne(r,`\x1B[35m[worker:${e}] \x1B[0m`,c=>{const P=/Debugger listening on (wss?:\/\/\S+)/.exec(c);P&&(w.inspectorUrl=P[1])}),r.on("exit",(c,P)=>{P==="SIGINT"||P==="SIGTERM"||c===0||console.error(`[parent] Worker "${e}" crashed (code=${c}, signal=${P??"none"})`);const M=m.indexOf(w);M>=0&&m.splice(M,1)}),console.log(`[parent] Worker "${e}" started (pid=${r.pid})`),w}h(E,"E"),n(E,"spawnWorkerProcess");const H=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),de=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),K=new Set,b=n(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function U(e,o){if(K.delete(e),!b(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${o} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}h(U,"x"),n(U,"killDrainingWorker");function Ge(e){try{e.proc.kill("SIGTERM")}catch{}if(b(e))return;const o=[...K].filter(d=>d.name===e.name);for(;o.length>=de;)U(o.shift(),`was still running when a later reload replaced its successor (cap ${de})`);K.add(e);const l=setTimeout(()=>U(e,`was still running ${H}ms after SIGTERM`),H);l.unref?.(),e.proc.once("exit",()=>{clearTimeout(l),K.delete(e)})}h(Ge,"Ue"),n(Ge,"retireWorker");function Le(){for(const e of[...m])Ge(e);m.length=0;for(const e of WORKER_NAMES)E(e)}h(Le,"He"),n(Le,"restartWorkers");const Be=debounce(async()=>{V+=1;const e=Y();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const o of e)tell(o.proc,{type:"reload"})}await _e(),WORKER_NAMES.length>0&&Le()},200),Ee=n(async(e,o)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!mayDriveThePool(e))return refusePoolCommand(e,o);o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0})),ce(e).then(a=>ae(a?.watcher)),Be();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!mayDriveThePool(e))return refusePoolCommand(e,o);const a=await ce(e);ae(a),o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,relayed:ie!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!mayDriveThePool(e))return refusePoolCommand(e,o);let a="";e.on("data",r=>{a+=r}),e.on("end",async()=>{let r=0;try{r=Number(JSON.parse(a||"{}").pods)||0}catch{}r=Math.max(1,Math.min(r,16)),await pe(r),o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,pods:r}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const a=new URL(e.url,"http://localhost").searchParams,r=Math.min(Math.max(Number(a.get("tail")??"100")||0,0),A),w=a.get("follow")!=="0",c=a.get("plain")==="1",P=!isOwnerOnlyTransport(e);o.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const M=r===0?"":L.slice(-r).join(""),O=c?M.replace(q,""):M;if(o.write(P?(0,inspector_secret_1.redactInspectorUrls)(O):O),!w){o.end();return}const B={res:o,plain:c,redact:P};j.add(B),e.on("close",()=>j.delete(B));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!isOwnerOnlyTransport(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(a)&&a>1024&&(R=a),R||(R=9229);const r=_.filter(c=>c.proc.exitCode===null&&c.proc.signalCode===null);for(const c of r)c.debugSlot<0&&(c.debugSlot=$()),tell(c.proc,{type:"debug-open",port:inspectorPortFor(R,c.debugSlot)});for(const c of m)try{c.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&r.some(c=>!c.inspectorUrl);)await new Promise(c=>setTimeout(c,50));o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,basePort:R,fixedPorts:usesFixedInspectorPorts(),pods:r.map(c=>({id:c.id,inspectorUrl:c.inspectorUrl??null})),workers:m.map(c=>({name:c.name,port:c.debugPort||inspectorPortOf(c.inspectorUrl),inspectorUrl:c.inspectorUrl??null}))},null,2)+`
|
|
16
|
-
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!isOwnerOnlyTransport(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),r=[...
|
|
17
|
-
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!isOwnerOnlyTransport(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=n((
|
|
18
|
-
`)},"reply"),r=new URL(e.url,"http://localhost").searchParams,w=Number(r.get("pod")||""),c=r.has("maxMb")?Number(r.get("maxMb")):void 0;if(c!==void 0&&!(Number.isFinite(c)&&c>0)){a(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const
|
|
12
|
+
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}f(importFresh,"importFresh"),n(importFresh,"importFresh");function debounce(i,h){let y=null;return(...S)=>{y&&clearTimeout(y),y=setTimeout(()=>i(...S),h)}}if(f(debounce,"debounce"),n(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const i=n(y=>{if(!inspector.url())try{inspector.open(y,"127.0.0.1",!1)}catch(G){console.warn(`[child] inspector.open(${y}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,G.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const S=inspector.url();S&&tell(process,{type:"inspector-url",url:S})},"openInspector"),h=process.env.CHILD_DEBUG_PORT;if(h!==void 0&&h!==""){const y=Number(h);Number.isInteger(y)&&(y===0||y>1024)&&i(y)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let y=null,S=!1,G,J=!1;const Ce=(0,hot_swap_registries_1.registryPruningEnabled)(),te=n((t,s)=>{const p=(0,hot_swap_registries_1.describePrune)(t,s);p&&console.log(p)},"logPrune");let F={app:null,handler:n((t,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const re=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),R=n(t=>{const s=globalThis.__lensmcpActiveWork;if(typeof s!="function")return{count:0};try{const p=s(t);return{count:Number(p?.count??0),...p?.oldestStartedAt?{oldestStartedAt:p.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),_e=n(t=>{const s=globalThis.__lensmcpStopWorkIntake;if(typeof s=="function")try{return s(t)}catch{return}},"stopWorkIntake"),oe=n(t=>{const s=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof s!="function"))try{return s(t)??void 0}catch{return}},"workIntakeReport"),be=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),q=new Set,N=(0,swap_recycle_1.swapRecyclePolicy)();let L,j=0;const Ne=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let ne=!1,X=!1;const xe=(0,pod_memory_1.podMemoryPolicy)();let Ae=0;const ge=new Set;let u=!1;const P=n(()=>{if(!u)return;let t=0;for(const s of ge)s.deref()===void 0?ge.delete(s):t+=1;return t},"liveContainers"),m=(0,pod_memory_1.createPodMemoryMonitor)({policy:xe,state:n(()=>({gen:F.gen??0,swaps:j,...L===void 0?{}:{baseline:L},busy:S||J||A.size>0,lastBusyAt:T}),"state"),send:n(t=>{tell(process,t)},"send"),containers:P,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:n(()=>ie(),"onRetained")}),W=n(()=>((0,pod_memory_1.fullGc)()&&m.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),$=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,s=`${path.sep}node_modules${path.sep}`,p=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:n(_=>p.has(_)||_.startsWith(t)&&!_.slice(t.length).includes(s)&&!_.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let se=!1;function x(){if(!(!$||se))try{const t=$();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),se=!0}}f(x,"D"),n(x,"releaseLibraryErrorFrames");const A=new Map;let T=Date.now();const D=n(()=>N.settleMs<=0||!S&&!J&&A.size===0&&Date.now()-T>=N.settleMs,"settled");let Y;function he(t){if(!(!N.enabled||ne||X)){if(N.settleMs<=0){z(t);return}Y&&clearTimeout(Y),Y=setTimeout(()=>{Y=void 0,D()?z(t):he(t)},N.settleMs),Y.unref?.()}}f(he,"_e"),n(he,"recycleCheckWhenSettled");const fe=n(()=>{if(N.stuckCloseMs<=0)return 0;const t=Date.now();let s=0;for(const p of A.values())t-p>=N.stuckCloseMs&&(s+=1);return s},"stuckCloses");function z(t){if(!N.enabled||ne||X||!process.send)return;const s=v8.getHeapStatistics().heap_size_limit,p=n(g=>{const E=m.oldestRetained();return{swaps:j,usedBytes:g,limitBytes:s,stuckCloses:fe(),settled:D(),...L===void 0?{}:{baselineBytes:L},...E?{retained:{generation:E.gen,ageMs:Date.now()-E.closedAt,gcs:E.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(N,p(process.memoryUsage().heapUsed)))return;const _=(0,swap_recycle_1.recycleReason)(N,p(W()));_&&(ne=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${_}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:_}))}f(z,"ee"),n(z,"considerRecycle");let K;function ie(){if(!N.enabled||N.retainedMs<=0||ne||X)return;const t=m.oldestRetained();if(!t)return;const s=t.closedAt+N.retainedMs-Date.now();if(s<=0){z("with a closed generation still in memory");return}K||(K=setTimeout(()=>{K=void 0,m.measure("retention-check").catch(()=>{})},s),K.unref?.())}f(ie,"ae"),n(ie,"retainedRecycleCheck");function ae(t,s){const p={};s&&(p.module=s),t&&(p.app=t);try{const _=t?.app;if(_&&typeof _=="object"&&(p.nestApp=_,_.container&&typeof _.container=="object")){p.container=_.container;const g=rootModuleOf(_.container);g&&(p.rootModule=g)}}catch{}return p}f(ae,"ye"),n(ae,"generationTargets");async function ce(t,s){if(t.closed||(t.closed=!0,q.delete(t),s&&console.error(`[child] ${s}`),!t.app))return;A.set(t,Date.now());const p=N.enabled&&N.stuckCloseMs>0?setTimeout(()=>{A.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(N.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),z("with a previous app stuck in close()"))},N.stuckCloseMs):void 0;p?.unref?.();const _=t.app;try{await t.app.close()}catch(g){console.error("[child] error closing a previous app:",g)}finally{A.delete(t),T=Date.now(),p&&clearTimeout(p),t.app=null,t.handler=(g,E)=>{E.statusCode=503,E.end("closed")},t.gen!==void 0&&m.closed(t.gen,j,ae(_,t.module)),t.module=void 0}if(t.superseded){try{te((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(g){console.error("[child] could not release the previous app's class registrations:",g)}t.superseded=void 0}he("after closing the previous app")}f(ce,"ge"),n(ce,"closeGeneration");async function Q(t){for(;q.size>=be;){const E=q.values().next().value;if(!E)break;await ce(E,`closing a still-draining generation early \u2014 ${q.size} were pending and the cap is ${be} (each holds a whole app: DI container, DB pool, queue workers). Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.`)}q.add(t);const s=Date.now(),p=n(()=>({inflight:t.inflight,work:R(t.workGen).count}),"busy");for(;re>0&&Date.now()-s<re;){if(t.closed)return;const E=p();if(E.inflight===0&&E.work===0)break;await new Promise(H=>{setTimeout(H,250).unref?.()})}if(t.closed)return;const _=p(),g=oe(t.workGen);_.inflight>0||_.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${_.inflight} in-flight request(s) and ${_.work} background job(s) still running. `+(g?`Previous app still processing ${g.running} queue job(s); ${g.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${re}ms) if this is legitimate work.`):(Date.now()-s>250||g?.runningAtStop||g?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-s}ms \u2014 closing it`+(g?` (it finished ${g.finishedAfterStop} queue job(s) after the swap; ${g.takenAfterStop} were taken after the swap)`:"")),await ce(t)}f(Q,"ce"),n(Q,"drainThenClose");async function Z(){if(S)return J=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),y;S=!0,T=Date.now(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(b,U)=>{console.error(`[child] uncaughtException (${U}):`,b)}),process.on("unhandledRejection",b=>{const U=b instanceof Error?b.stack||b.message:String(b);U.includes("FronteggContextScope")||U.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",b)});let t;if(Ce)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const s=global.__lensmcpBeginWorkGeneration,p=typeof s=="function"?s():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(b){if(!(b instanceof SyntaxError))throw b;console.warn(`[child] bundle was unreadable mid-write (${b.message}) \u2014 retrying the swap once`),await new Promise(U=>setTimeout(U,400)),await importFresh(BUNDLE_PATH)}}catch(b){throw S=!1,T=Date.now(),b}if(typeof global.createChildApp!="function")throw S=!1,T=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let _;if(t)try{_=(0,hot_swap_registries_1.supersededSince)(t),te((0,hot_swap_registries_1.pruneRegistries)(_,"build"),"at the swap")}catch(b){console.error("[child] could not release superseded class registrations:",b)}let g;try{g=await global.createChildApp()}catch(b){throw S=!1,T=Date.now(),takeImportedModule(),b}const E=F;_?.size&&(E.app?E.superseded=_:te((0,hot_swap_registries_1.pruneRegistries)(_,"serve"),"at the swap")),y=g;const H=takeImportedModule();F={app:g,handler:g.handler,inflight:0,gen:++Ae,...H?{module:H}:{},...p===void 0?{}:{workGen:p}};const de=ae(g,void 0).container;de&&(ge.add(new WeakRef(de)),u=!0);const V=E.app&&p!==void 0?_e(p):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(V?.workers?`; its ${V.workers} queue worker(s) stopped taking new jobs, previous app still processing ${V.runningAtStop} job(s)`:"")),x(),E.app&&(j+=1,Q(E).catch(b=>console.error("[child] background close error:",b)),setTimeout(()=>z("30s after a hot swap"),3e4).unref?.(),m.swapped(E.gen,()=>E.closed===!0&&!A.has(E))),S=!1,T=Date.now(),J?(J=!1,Z()):g}f(Z,"le"),n(Z,"swapNow");const ye=debounce(async()=>{try{console.log("[child] /webpack/reload"),await Z()}catch(t){console.error("[child] reload failed:",t)}},250),le=http.createServer((t,s)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({ok:!0})),ye();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const _=t.headers["x-internal-token"],g=process.env.LENSMCP_SERVICE_KEY;if(g&&_!==g){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const E=R();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:S,inflight:F.inflight,oldestInflightAt:F.oldestAt,activeWork:E.count,oldestWorkAt:E.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...G?{watcher:G}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const p=F;p.inflight+=1,p.oldestAt===void 0&&(p.oldestAt=Date.now()),s.on("close",()=>{p.inflight-=1,p.inflight===0&&(p.oldestAt=void 0),m.activity()}),p.handler(t,s)});le.keepAliveTimeout=0;const I=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(I),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const s=(0,schema_first_instance_1.prewarmZodFor)(t);s?.created&&console.log(`[child] zod${s.version?` ${s.version}`:""}: built ${s.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const s=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);s?.patched&&console.log(`[child] ioredis${s.versions.length?` ${s.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await Z(),L=(await m.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock(I),le.listen(I,()=>{console.log(`[child] Listening on ${I}`),tell(process,{type:"ready",socketPath:I})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const s=Number(t.port);i(Number.isInteger(s)&&s>1024?s:0);return}if(t.type==="watcher"){const s=t.testimony;s&&typeof s=="object"&&(G=s);return}if(t.type==="heap-snapshot"){me(t);return}if(t.type==="recycle-declined"){ne=!1;return}if(t.type==="retire"){ee();return}if(t.type==="reload"){if(X)return;try{await Z(),tell(process,{type:"reloaded",socketPath:I})}catch(s){console.error("[child] reload error:",s),tell(process,{type:"reload-error",error:String(s?.stack||s)})}}}});let pe=!1;async function me(t){const s=n(p=>{tell(process,{type:"heap-snapshot-result",id:t.id,...p})},"reply");if(pe)return s({ok:!1,refused:"a snapshot of this pod is already being written"});pe=!0;try{const p=W(),_=m.tracker.views().map(de=>de.gen),g=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(g.maxHeapBytes=t.maxHeapBytes);const E=(0,heap_snapshot_1.heapSnapshotRefusal)(g,{live:p,...t.available?{available:t.available}:{}});if(E)return s({ok:!1,refused:E,live:p});console.log(`[child] writing a heap snapshot (${Math.round(p/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const H=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);s({ok:!0,file:t.file,bytes:H.bytes,ms:H.ms,live:p,gen:F.gen??0,closedAlive:_})}catch(p){s({ok:!1,error:p?.message??String(p)})}finally{pe=!1}}f(me,"$e"),n(me,"takeHeapSnapshot");async function ee(){if(X)return;X=!0,m.stop(),console.log("[child] retiring \u2014 a fresh pod has taken over; finishing in-flight work, then exiting");const t=_e(Number.POSITIVE_INFINITY);t?.workers&&console.log(`[child] its ${t.workers} queue worker(s) stopped taking new jobs, still processing ${t.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath(I),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(p=>{setTimeout(p,Ne).unref?.()}),cleanupSock(I),le.close();const s=Date.now();for(;Date.now()-s<re&&(le.closeIdleConnections?.(),!(F.inflight===0&&R().count===0));)await new Promise(p=>{setTimeout(p,250).unref?.()});setTimeout(()=>process.exit(0),3e4).unref?.();try{await y?.close()}catch(p){console.error("[child] close error while retiring:",p)}process.exit(0)}f(ee,"re"),n(ee,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const we=n(async t=>{console.log(`[child ${t}] shutting down\u2026`);const s=y?.close().catch(_=>console.error("[child] close error:",_)),p=new Promise(_=>le.close(()=>_()));cleanupSock(I),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([s,p]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{inspector?.close?.(),we(t)})})})()}else{let i=f(function(u){L.push(u),L.length>N&&L.splice(0,L.length-N),be?.write((0,inspector_secret_1.redactInspectorUrls)(u.replace(q,"")));for(const P of j)try{const m=P.plain?u.replace(q,""):u;P.res.write(P.redact?(0,inspector_secret_1.redactInspectorUrls)(m):m)}catch{j.delete(P)}},"z");n(i,"emitLogLine");const h=12,y=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",S=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),G=n(u=>({project:y,service:SERVICE_NAME||y,pod:u.id,pid:u.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),J=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Ce="\x1B[0m",te=Math.max(1,Number(process.env.CHILD_COUNT||1)),F=LENSMCP_WS_KEY||path.basename(process.cwd()),re=n(u=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(F,SERVICE_NAME||"service",u)),"execPathFor");let R=(()=>{const u=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(u)&&u>1024?u:0})();const _e=20;R&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${R}+slot, workers ${R}+${_e}+i). Any account on this host can connect to 127.0.0.1:${R} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const oe=process.env.LENSMCP_LOG_FILE||"",be=oe?(()=>{try{fs.mkdirSync(path.dirname(oe),{recursive:!0});const u=fs.createWriteStream(oe,{flags:"a",mode:384});try{fs.chmodSync(oe,384)}catch{}return u}catch(u){return console.warn(`[parent] cannot open log file ${oe}:`,u.message),null}})():null,q=/\x1b\[[0-9;]*m/g,N=2e3,L=[],j=new Set,Ne=(0,devserver_transport_1.gatewayManaged)();for(const u of["log","info","warn","error"]){const P=console[u].bind(console),m=u==="log"||u==="info"?process.stdout:process.stderr;console[u]=(...W)=>{P(...W.map($=>typeof $=="string"?(0,inspector_secret_1.forStream)(m,Ne,$):$)),i(W.map($=>typeof $=="string"?$:util.inspect($)).join(" ")+`
|
|
13
|
+
`)}}const ne=n(u=>J[(u-1)%J.length],"colorFor"),X=n(u=>{if(te===1)return"";const P=`[child#${u}] `;return`${ne(u)}${P}${Ce}`},"tagFor"),xe=n(u=>u.startsWith("{")&&u.endsWith("}"),"isJsonLog"),Ae=n((u,P,m)=>{const W=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:n(x=>`
|
|
14
|
+
`+String(x),"stack")}}),$=n((x,A)=>{const T=x==="stdout"?process.stdout:process.stderr;try{m?.(A)}catch{}const D=xe(A)?W(A):`${A}
|
|
15
|
+
`;T.write((0,inspector_secret_1.forStream)(T,Ne,`${P}${D}`)),i(`${P}${D}`)},"write"),se=n((x,A)=>{if(!x)return;const T=readline.createInterface({input:x});T.on("line",D=>$(A,D)),T.on("close",()=>{})},"attach");se(u.stdout,"stdout"),se(u.stderr,"stderr")},"wireProcLogging"),ge=n(u=>Ae(u.proc,X(u.id),P=>u.tail.push(P)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const u=httpProxy.createProxyServer({});let P=Number(process.env.PORT)||0;P||(P=9090);const m=[],W=new Set;function $(){let e=0;for(;W.has(e);)e++;return W.add(e),e}f($,"I"),n($,"allocDebugSlot");let se=1,x=0,A=!1,T=te,D=[],Y=0;const he=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),fe=n(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),z=n(e=>e.booting&&!fe(e)&&Date.now()-e.spawnedAt<he,"stillBooting");function K(){return m.filter(e=>e.healthy&&typeof e.socketPath=="string")}f(K,"J"),n(K,"healthyChildren");let ie;function ae(e){if(!e||typeof e!="object")return;const o=e;if(o.schemaVersion===1){ie=o;for(const l of m)tell(l.proc,{type:"watcher",testimony:o})}}f(ae,"ye"),n(ae,"recordWatcherTestimony");function ce(e,o=256*1024){return new Promise(l=>{let d="",a=!1;e.on("data",r=>{a||(d+=r,d.length>o&&(a=!0))}),e.on("end",()=>{if(a||!d)return l(void 0);try{l(JSON.parse(d))}catch{l(void 0)}}),e.on("error",()=>l(void 0))})}f(ce,"ge"),n(ce,"readJsonBody");function Q(){const e=K();if(e.length===0)return null;const o=x%e.length;return x=(x+1)%e.length,e[o]}f(Q,"ce"),n(Q,"pickChild");function Z(e,o){const l=`Child#${e.id}`,d=SERVICE_NAME||y,a=Date.now();if(o.type==="pod-memory"){const r=o.report;if(!r||typeof r.live!="number")return;e.memory.latest=r,e.memory.history.push(r),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(l,r)}`),S.publish((0,pod_memory_events_1.buildPodMemoryEvent)(G(e),r))}else if(o.type==="pod-memory-retained"){const r=o.retained;if(!r||typeof r.gen!="number")return;e.memory.retained.set(r.gen,{...r,at:a}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(l,d,r)}`),S.publish((0,pod_memory_events_1.buildPodRetainedEvent)(G(e),r,a))}else{const r=o.released;if(!r||typeof r.gen!="number")return;e.memory.retained.delete(r.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(l,d,r)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(G(e),r,a))}}f(Z,"le"),n(Z,"recordPodMemory");const ye=new Map;let le=0;async function I(){const e=se++,o=childSockPath(e);cleanupSock(o);const l=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&l.unshift("--require","@swc-node/register"),l.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const d=R?$():-1,a=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:o,...d>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(R,d))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:l,execPath:re("pod")}),r={id:e,proc:a,healthy:!1,lastError:null,debugSlot:d,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:Y,memory:{history:[],retained:new Map}};return a.on("error",w=>console.error(`[parent] Child#${e}: ${w.message}`)),a.on("message",w=>{if(!w||typeof w!="object")return;const c=w;c.type==="ready"?(r.socketPath=String(c.socketPath),r.healthy=!0,r.booting=!1,console.log(`[parent] Child#${r.id} ready on socket ${r.socketPath}`),ie&&tell(a,{type:"watcher",testimony:ie}),Y!==r.bootReloadSeq&&tell(a,{type:"reload"})):c.type==="recycle-request"?_(r,String(c.reason||"it has outgrown its bound")):c.type==="inspector-url"?(r.inspectorUrl=String(c.url),console.log(`[parent] Child#${r.id} Debugger listening on ${c.url}`)):c.type==="reloaded"?(r.socketPath=String(c.socketPath),r.healthy=!0,console.log(`[parent] Child#${r.id} hot-swapped on socket ${r.socketPath}`)):c.type==="reload-error"?(r.lastError=String(c.error||"unknown reload error"),console.error(`[parent] Child#${r.id} reload error: ${r.lastError}`),r.healthy=!1):c.type==="boot-error"?(r.lastError=String(c.error||"unknown boot error"),console.error(`[parent] Child#${r.id} boot error: ${r.lastError}`),r.healthy=!1,r.booting=!1):c.type==="pod-memory"||c.type==="pod-memory-retained"||c.type==="pod-memory-released"?Z(r,c):c.type==="heap-snapshot-result"&&ye.get(Number(c.id))?.(c)}),a.on("exit",(w,c)=>{r.booting=!1;for(const O of r.memory.retained.values()){const M={gen:O.gen,closedAt:O.closedAt,ageMs:Date.now()-O.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${r.id}`,SERVICE_NAME||y,M)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(G(r),M,Date.now()))}r.memory.retained.clear(),r.memory.latest&&S.publish((0,pod_memory_events_1.buildPodExitedEvent)(G(r),{code:w,signal:c},Date.now())),r.debugSlot>=0&&(W.delete(r.debugSlot),r.debugSlot=-1);const v=c==="SIGINT"||c==="SIGTERM"||w===0;if(v?console.log(`[parent] Child#${r.id} exited cleanly (code=${w}, signal=${c??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${r.id} crashed (code=${w}, signal=${c??"none"}, pid=${r.proc.pid}, ${r.healthy?"was serving":"was not ready"})`,w,c,r.tail.snapshot())),r.lastError||(r.lastError=`Child exited abnormally (code=${w}, signal=${c??"none"})`)),r.healthy=!1,cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0,!v&&!A){const O=m.indexOf(r);O>=0&&m.splice(O,1);const M=Date.now();if(D=D.filter(B=>M-B<3e4),D.length>=5){console.error(`[parent] crash-loop detected (${D.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}D.push(M),setTimeout(()=>{const B=m.filter(ve=>ve.proc.killed===!1).length;!A&&B<T&&(console.log(`[parent] respawning a crashed pod (${B}/${T} live)`),I().catch(()=>{}))},1500).unref?.()}}),m.push(r),ge(r),r}f(I,"O"),n(I,"spawnChild");async function pe(e){T=Math.max(T,e);const o=m.filter(d=>d.proc.killed===!1),l=e-o.length;for(let d=0;d<l;d++)await I()}f(pe,"pe"),n(pe,"ensurePoolSize");async function me(){const e=m.filter(o=>(!o.healthy||typeof o.socketPath!="string")&&!z(o));await Promise.all(e.map(async o=>{try{o.proc.kill("SIGTERM")}catch{}cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0;const l=m.indexOf(o);l>=0&&m.splice(l,1),await I()}))}f(me,"$e"),n(me,"respawnUnhealthy");const ee=new Set;let we=!1,t=0;const s=3e4,p=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function _(e,o){const l=n(a=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${o}) \u2014 not now: ${a}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(A||!m.includes(e))return;if(we)return l("another pod is already being replaced");const d=Date.now()-t;if(d<s)return l(`a pod was replaced ${Math.round(d/1e3)}s ago`);we=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${o}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const a=await I(),r=Date.now()+he;for(;!a.healthy&&!a.lastError&&!fe(a)&&Date.now()<r;)await new Promise(v=>{setTimeout(v,100).unref?.()});if(A)return;if(!a.healthy){const v=m.indexOf(a);v>=0&&m.splice(v,1);try{a.proc.kill("SIGTERM")}catch{}return l(`its successor Child#${a.id} did not come up (${a.lastError??"no answer in time"})`)}const w=m.indexOf(e);w>=0&&m.splice(w,1),e.healthy=!1,ee.add(e);const c=setTimeout(()=>{if(!fe(e)){console.error(`[parent] Child#${e.id} was still running ${p}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},p);if(c.unref?.(),e.proc.once("exit",()=>{clearTimeout(c),ee.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}t=Date.now(),console.log(`[parent] Child#${a.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${a.proc.pid})`)}catch(a){l(`could not start a successor: ${a?.message??String(a)}`)}finally{we=!1}}f(_,"f"),n(_,"recyclePod");const g=[];function E(e){const o=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),l=path.join(o,`${e}.js`);if(!fs.existsSync(l))return console.error(`[parent] Worker "${e}" bundle not found: ${l}`),null;const d=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&d.unshift("--require","@swc-node/register");try{d.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const a=inspectorPortFor(R||9229,_e+Math.max(0,WORKER_NAMES.indexOf(e)));d.push(R?`--inspect=127.0.0.1:${a}`:`--inspect-port=127.0.0.1:${a}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const r=(0,node_child_process_1.fork)(l,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:d,execPath:re(`w-${e}`)}),w={name:e,proc:r,debugPort:a};return g.push(w),r.on("error",c=>console.error(`[parent] Worker "${e}": ${c.message}`)),Ae(r,`\x1B[35m[worker:${e}] \x1B[0m`,c=>{const v=/Debugger listening on (wss?:\/\/\S+)/.exec(c);v&&(w.inspectorUrl=v[1])}),r.on("exit",(c,v)=>{v==="SIGINT"||v==="SIGTERM"||c===0||console.error(`[parent] Worker "${e}" crashed (code=${c}, signal=${v??"none"})`);const O=g.indexOf(w);O>=0&&g.splice(O,1)}),console.log(`[parent] Worker "${e}" started (pid=${r.pid})`),w}f(E,"E"),n(E,"spawnWorkerProcess");const H=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),de=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),V=new Set,b=n(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function U(e,o){if(V.delete(e),!b(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${o} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}f(U,"x"),n(U,"killDrainingWorker");function Ge(e){try{e.proc.kill("SIGTERM")}catch{}if(b(e))return;const o=[...V].filter(d=>d.name===e.name);for(;o.length>=de;)U(o.shift(),`was still running when a later reload replaced its successor (cap ${de})`);V.add(e);const l=setTimeout(()=>U(e,`was still running ${H}ms after SIGTERM`),H);l.unref?.(),e.proc.once("exit",()=>{clearTimeout(l),V.delete(e)})}f(Ge,"Ue"),n(Ge,"retireWorker");function Le(){for(const e of[...g])Ge(e);g.length=0;for(const e of WORKER_NAMES)E(e)}f(Le,"He"),n(Le,"restartWorkers");const Be=debounce(async()=>{Y+=1;const e=K();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const o of e)tell(o.proc,{type:"reload"})}await me(),WORKER_NAMES.length>0&&Le()},200),Ee=n(async(e,o)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,o);o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0})),ce(e).then(a=>ae(a?.watcher)),Be();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,o);const a=await ce(e);ae(a),o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,relayed:ie!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,o);let a="";e.on("data",r=>{a+=r}),e.on("end",async()=>{let r=0;try{r=Number(JSON.parse(a||"{}").pods)||0}catch{}r=Math.max(1,Math.min(r,16)),await pe(r),o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,pods:r}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const a=new URL(e.url,"http://localhost").searchParams,r=Math.min(Math.max(Number(a.get("tail")??"100")||0,0),N),w=a.get("follow")!=="0",c=a.get("plain")==="1",v=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);o.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const O=r===0?"":L.slice(-r).join(""),M=c?O.replace(q,""):O;if(o.write(v?(0,inspector_secret_1.redactInspectorUrls)(M):M),!w){o.end();return}const B={res:o,plain:c,redact:v};j.add(B),e.on("close",()=>j.delete(B));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(a)&&a>1024&&(R=a),R||(R=9229);const r=m.filter(c=>c.proc.exitCode===null&&c.proc.signalCode===null);for(const c of r)c.debugSlot<0&&(c.debugSlot=$()),tell(c.proc,{type:"debug-open",port:inspectorPortFor(R,c.debugSlot)});for(const c of g)try{c.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&r.some(c=>!c.inspectorUrl);)await new Promise(c=>setTimeout(c,50));o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({ok:!0,basePort:R,fixedPorts:usesFixedInspectorPorts(),pods:r.map(c=>({id:c.id,inspectorUrl:c.inspectorUrl??null})),workers:g.map(c=>({name:c.name,port:c.debugPort||(0,inspector_secret_1.inspectorPortOf)(c.inspectorUrl),inspectorUrl:c.inspectorUrl??null}))},null,2)+`
|
|
16
|
+
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),r=[...m,...ee].map(w=>({id:w.id,pid:w.proc.pid??0,podGeneration:POD_GENERATION,healthy:w.healthy,retiring:ee.has(w),latest:w.memory.latest??null,history:w.memory.history,retained:[...w.memory.retained.values()]}));o.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),o.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:y,bundle:{path:a},pods:r},null,2)+`
|
|
17
|
+
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){o.writeHead(404,{"content-type":"application/json"}),o.end(JSON.stringify({error:"not found"}));return}const a=n((C,ke)=>{o.writeHead(C,{"content-type":"application/json"}),o.end(JSON.stringify(ke,null,2)+`
|
|
18
|
+
`)},"reply"),r=new URL(e.url,"http://localhost").searchParams,w=Number(r.get("pod")||""),c=r.has("maxMb")?Number(r.get("maxMb")):void 0;if(c!==void 0&&!(Number.isFinite(c)&&c>0)){a(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const v=Number.isInteger(w)&&w>0?m.find(C=>C.id===w):K()[0];if(!v||fe(v)){a(404,{ok:!1,error:w?`no pod #${w} \u2014 running: ${m.map(C=>`#${C.id}`).join(", ")||"none"}`:"no pod is serving"});return}if(ye.size>0){a(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const O=(0,heap_snapshot_1.heapSnapshotPolicy)(),M=(0,heap_snapshot_1.heapSnapshotDir)();let B;try{(0,heap_snapshot_1.ensureHeapDir)(M);const C=(0,heap_snapshot_1.pruneHeapSnapshots)(M,O);C.length&&console.log(`[parent] removed ${C.length} older heap snapshot(s) from ${M}`),B=path.join(M,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||y,v.id,v.proc.pid??0,v.memory.latest?.gen??0,Date.now()))}catch(C){a(500,{ok:!1,error:`cannot prepare ${M}: ${C.message}`});return}const ve=++le,Me=(0,heap_snapshot_1.availableSystemMemory)();let Pe;const k=await new Promise(C=>{const ke=setTimeout(()=>C(void 0),6e5);ke.unref?.(),ye.set(ve,Ye=>{clearTimeout(ke),C(Ye)}),Pe=n(()=>{clearTimeout(ke),C(void 0)},"onExit"),v.proc.once("exit",Pe);const je={type:"heap-snapshot",id:ve,file:B,...Me?{available:Me}:{},...c!==void 0?{maxHeapBytes:Math.floor(c*1048576)}:{}};tell(v.proc,je)||Pe()}).finally(()=>{ye.delete(ve),Pe&&v.proc.off("exit",Pe)});if(!k){a(504,{ok:!1,pod:v.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}k.ok?console.log(`[parent] Child#${v.id} wrote a ${Math.round((k.bytes??0)/1048576)} MB heap snapshot in ${k.ms} ms \u2192 ${k.file}`):console.warn(`[parent] Child#${v.id} heap snapshot not taken: ${k.refused??k.error}`),a(k.ok?200:k.refused?409:500,{ok:k.ok,pod:v.id,pid:v.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...Me?{available:Me}:{},...k.file?{file:k.file,bytes:k.bytes,ms:k.ms}:{},...k.live!==void 0?{live:k.live}:{},...k.gen!==void 0?{gen:k.gen,closedAlive:k.closedAlive??[]}:{},...k.refused?{refused:k.refused}:{},...k.error?{error:k.error}:{}});return}const l=matchGatewayRoute(e);if(l?.prependPrefix&&!e.url?.startsWith(l.prependPrefix)&&(e.url=l.prependPrefix+(e.url??"/")),l?.target){applyGatewayMiddleware(e),u.web(e,o,{target:l.target,autoRewrite:typeof l.target=="string",changeOrigin:!0},a=>{console.error(`[parent] proxy error to ${l.target}:`,a),o.statusCode=502,o.setHeader("content-type","text/plain; charset=utf-8"),o.end(`Upstream error from ${l.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){o.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),o.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const d=Q();if(!d){o.statusCode=503,o.setHeader("content-type","text/plain; charset=utf-8");const a=m.map(r=>`#${r.id}: ${r.lastError??"no error recorded"}`).join(`
|
|
19
19
|
`);o.end(`No healthy children available. POST /webpack/reload to recover.
|
|
20
20
|
|
|
21
21
|
Last errors:
|
|
22
|
-
${a}`);return}
|
|
23
|
-
${d.lastError}`)})},"requestHandler");let ue,Se,
|
|
22
|
+
${a}`);return}u.web(e,o,{target:{socketPath:d.socketPath},headers:{"x-proxy-child-id":String(d.id)}},a=>{console.error(`[parent] proxy error to Child#${d.id}:`,a),d.healthy=!1,d.lastError=String(a?.stack||a),o.statusCode=502,o.setHeader("content-type","text/plain; charset=utf-8"),o.end(`Upstream error from Child#${d.id}. Try /webpack/reload.
|
|
23
|
+
${d.lastError}`)})},"requestHandler");let ue,Se,De;const Te=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),o=GATEWAY_ROUTES.map(d=>d.host).filter(d=>!!d),l=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");Se=e.getCertificateSync(l,SERVICE_NAME||"lensmcp.dev",o),De=e.caCertPath(),ue=require("node:https").createServer({key:Se,cert:Se},Ee)}else ue=http.createServer(Ee);const We=n((e,o,l)=>{const d=matchGatewayRoute(e);if(d?.prependPrefix&&!e.url?.startsWith(d.prependPrefix)&&(e.url=d.prependPrefix+(e.url??"/")),d?.target){u.ws(e,o,l,{target:d.target,changeOrigin:!0},()=>o.destroy());return}const a=Q();if(!a){o.destroy();return}u.ws(e,o,l,{target:{socketPath:a.socketPath}},()=>o.destroy())},"upgradeHandler");ue.on("upgrade",We);const Ue=2e3;let Fe=!1;ue.on("error",e=>{if(e.code==="EADDRINUSE"){Fe||(Fe=!0,console.warn(`[parent] public port ${P} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${Ue/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!A&&!ue.listening&&ue.listen(P)},Ue).unref?.();return}console.error("[parent] public server error:",e)}),await pe(te);for(const e of WORKER_NAMES)E(e);SOCKET_ONLY?console.log(`[parent] socket-only: no TCP listener (port ${P} is not bound \u2014 the gateway reaches this service through its socket pool)`):ue.listen(P,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${P}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(o=>`${o.host??"*"}${o.prefix??"/"}\u2192${o.target??"children"}`).join(" ")}`),De&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${De}`)),console.log(`POST ${e}://localhost:${P}/webpack/reload to trigger rolling swap/respawn`)});const Re=Number(process.env.INTERNAL_PORT||0);if(Re>0&&!SOCKET_ONLY){const e=n((l,d)=>{const a=Q();if(!a){d.statusCode=503,d.setHeader("content-type","text/plain; charset=utf-8"),d.end("No healthy children available.");return}u.web(l,d,{target:{socketPath:a.socketPath}},r=>{a.healthy=!1,a.lastError=String(r?.stack||r),d.statusCode=502,d.end(`Upstream error from Child#${a.id}.`)})},"internalHandler"),o=http.createServer(e);o.on("upgrade",(l,d,a)=>{const r=Q();if(!r){d.destroy();return}u.ws(l,d,a,{target:{socketPath:r.socketPath}},()=>d.destroy())}),o.on("error",l=>{console.warn(`[parent] internal port ${Re} unavailable (${l.code}).`)}),o.listen(Re,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${Re}`)}),Te.push(o)}const qe=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of qe){if(!Number.isInteger(e)||e===Number(P))continue;const o=GATEWAY_HTTPS?require("node:https").createServer({key:Se,cert:Se},Ee):http.createServer(Ee);o.on("upgrade",We),o.on("error",l=>{console.warn(`[parent] extra port ${e} unavailable (${l.code}) \u2014 main port still up.`)}),o.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),Te.push(o)}const $e=path.join(SOCK_DIR,"parent.sock");claimSock($e);const Ie=http.createServer(Ee);Ie.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),Ie.listen($e),Te.push(Ie);const He=n(async e=>{A=!0,console.log(`[${e}] [parent] shutting down\u2026`);const o=sockOwnedByAnother($e);cleanupSock($e);for(const l of Te)if(!(l===Ie&&o))try{l.close()}catch{}for(const l of g)try{l.proc.kill("SIGTERM")}catch{}for(const l of[...V])U(l,"was still draining when its devserver shut down");for(const l of ee)try{l.proc.kill("SIGTERM")}catch{}for(const l of m){try{l.proc.kill("SIGTERM")}catch{}l.socketPath&&cleanupSock(l.socketPath,l.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{He(e)}));const Oe=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(Oe)&&Oe>0){const e=setInterval(()=>{try{process.kill(Oe,0)}catch(o){if(o.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${Oe}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),He("gateway-gone")}},5e3);e.unref()}})()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.3",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@lensmcp/core": "1.24.
|
|
61
|
-
"@lensmcp/node-instrumentation": "1.24.
|
|
62
|
-
"@lensmcp/nx-plugin": "1.24.
|
|
60
|
+
"@lensmcp/core": "1.24.3",
|
|
61
|
+
"@lensmcp/node-instrumentation": "1.24.3",
|
|
62
|
+
"@lensmcp/nx-plugin": "1.24.3",
|
|
63
63
|
"fastify": "^5.8.5",
|
|
64
64
|
"@fastify/reply-from": "^12.6.2",
|
|
65
65
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|