@lensmcp/cluster 1.25.35 → 1.25.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config-files.d.ts +34 -0
- package/config-files.js +1 -0
- package/devserver-notifier.d.ts +3 -1
- package/devserver-notifier.js +1 -1
- package/executors/gateway/jwks-verify.d.ts +15 -0
- package/executors/gateway/jwks-verify.js +1 -1
- package/executors/gateway/main.prod-gateway.js +1 -1
- package/executors/gateway/prod-runtime/handler.js +1 -1
- package/executors/gateway/prod-runtime/types.d.ts +6 -2
- package/executors/gateway/prod-runtime/upgrade.js +6 -6
- package/executors/serve/serve.impl.js +1 -1
- package/main.devserver.js +6 -6
- package/package.json +4 -4
- package/service-build-events.d.ts +2 -2
- package/service-build-events.js +2 -2
- package/shared-build/host.d.ts +14 -1
- package/shared-build/host.js +3 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DevWebpackOptions } from './create-webpack-dev';
|
|
2
|
+
/** The tsconfig, every file it extends (the `paths` may come from any of them), and the webpack override. */
|
|
3
|
+
export declare function configFilesOf(options: Pick<DevWebpackOptions, 'tsConfig' | 'workspaceRoot' | 'appRoot' | 'webpackConfigPath'>): string[];
|
|
4
|
+
export interface ConfigFilesPluginOptions {
|
|
5
|
+
/** The files to watch — read on every compilation, so a set that grows is watched as it grows. */
|
|
6
|
+
files(): Iterable<string>;
|
|
7
|
+
/** Some of them changed: called before the compile the change started (`watchRun`), with the ones that did. */
|
|
8
|
+
changed(paths: string[]): void;
|
|
9
|
+
}
|
|
10
|
+
/** The webpack hooks this taps. */
|
|
11
|
+
interface ConfigFilesCompiler {
|
|
12
|
+
hooks: {
|
|
13
|
+
watchRun: {
|
|
14
|
+
tap(name: string, fn: (compiler: {
|
|
15
|
+
modifiedFiles?: ReadonlySet<string>;
|
|
16
|
+
removedFiles?: ReadonlySet<string>;
|
|
17
|
+
}) => void): void;
|
|
18
|
+
};
|
|
19
|
+
afterCompile: {
|
|
20
|
+
tap(name: string, fn: (compilation: {
|
|
21
|
+
fileDependencies: {
|
|
22
|
+
add(file: string): void;
|
|
23
|
+
};
|
|
24
|
+
}) => void): void;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Watches {@link ConfigFilesPluginOptions.files} beside the modules, and says when one of them changed. */
|
|
29
|
+
export declare class ConfigFilesPlugin {
|
|
30
|
+
private readonly options;
|
|
31
|
+
constructor(options: ConfigFilesPluginOptions);
|
|
32
|
+
apply(compiler: ConfigFilesCompiler): void;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
package/config-files.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var c=Object.defineProperty;var n=(e,i)=>c(e,"name",{value:i,configurable:!0});var a=Object.defineProperty,s=n((e,i)=>a(e,"name",{value:i,configurable:!0}),"s"),l=Object.defineProperty,t=s((e,i)=>l(e,"name",{value:i,configurable:!0}),"t");Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConfigFilesPlugin=void 0,exports.configFilesOf=configFilesOf;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),workspace_paths_1=require("./workspace-paths");function configFilesOf(e){const i=((0,workspace_paths_1.readWorkspacePaths)(e.tsConfig,e.workspaceRoot)?.configFiles??[path.resolve(e.workspaceRoot,e.tsConfig)]).filter(o=>!o.split(path.sep).includes("node_modules"));return e.webpackConfigPath&&i.push(path.resolve(e.appRoot,e.webpackConfigPath)),[...new Set(i)]}n(configFilesOf,"configFilesOf"),s(configFilesOf,"configFilesOf"),t(configFilesOf,"configFilesOf");class ConfigFilesPlugin{static{n(this,"ConfigFilesPlugin")}static{s(this,"ConfigFilesPlugin")}static{t(this,"ConfigFilesPlugin")}constructor(i){this.options=i}apply(i){i.hooks.afterCompile.tap("LensmcpConfigFiles",o=>{for(const r of this.options.files())o.fileDependencies.add(r)}),i.hooks.watchRun.tap("LensmcpConfigFiles",o=>{if(!o.modifiedFiles&&!o.removedFiles)return;const r=new Set(this.options.files()),f=[...o.modifiedFiles??[],...o.removedFiles??[]].filter(p=>r.has(p));f.length>0&&this.options.changed([...new Set(f)])})}}exports.ConfigFilesPlugin=ConfigFilesPlugin;
|
package/devserver-notifier.d.ts
CHANGED
|
@@ -63,7 +63,9 @@ export declare class DevserverNotifier {
|
|
|
63
63
|
tellWatching(): void;
|
|
64
64
|
/**
|
|
65
65
|
* A build without errors finished. `reload: false` — the bundle this devserver loads did not change (a shared
|
|
66
|
-
* compilation rebuilt for another service): the testimony is updated, the pods are left alone
|
|
66
|
+
* compilation rebuilt for another service): the testimony is updated, the pods are left alone, and the build it
|
|
67
|
+
* ends goes with it as `unchanged` — announced as started, it ended in neither a swap nor a failure, and read as
|
|
68
|
+
* running for good (issues/closed/foodguard-114).
|
|
67
69
|
*/
|
|
68
70
|
built(reload?: boolean): Promise<void>;
|
|
69
71
|
}
|
package/devserver-notifier.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var A=Object.defineProperty;var g=(
|
|
1
|
+
"use strict";var A=Object.defineProperty;var g=(s,e)=>A(s,"name",{value:e,configurable:!0});var p=Object.defineProperty,u=g((s,e)=>p(s,"name",{value:e,configurable:!0}),"u"),m=Object.defineProperty,a=u((s,e)=>m(s,"name",{value:e,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DevserverNotifier=void 0,exports.devserverControlSock=devserverControlSock;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),notice_generation_1=require("./notice-generation");class DevserverNotifier{static{g(this,"DevserverNotifier")}static{u(this,"DevserverNotifier")}static{a(this,"DevserverNotifier")}static{this.RECENT_PATH_CAP=32}static{this.BUILD_PATH_CAP=50}constructor(e,t=!1,i,o=Number(process.env.LENSMCP_GATEWAY_PID??0)>0,c=(0,notice_generation_1.generationFromEnv)()){this.testimony={schemaVersion:1,lastWatchRunAt:0,lastBuildAt:0,buildsSinceSpawn:0,degraded:!1,recentPaths:[]},this.managed=o,this.secure=t,this.base=`${t?"https":"http"}://localhost:${e}`,this.controlSock=i,this.generation=c}post(e,t){const i=JSON.stringify(t),o={"content-type":"application/json",...this.generation!==void 0?{[notice_generation_1.NOTICE_GENERATION_HEADER]:String(this.generation)}:{}},c=a((n,r,d)=>{if(n.statusCode!==409||this.generation===void 0){n.resume(),n.on("end",r);return}let h="";n.setEncoding("utf8"),n.on("data",l=>{h+=l}),n.on("end",()=>{let l;try{l=JSON.parse(h).generation}catch{}d(new notice_generation_1.NotThisGeneration(this.generation,l))})},"answered"),b=a(()=>new Promise((n,r)=>{const d=(this.secure?require("node:https"):require("node:http")).request(`${this.base}${e}`,{method:"POST",headers:o,rejectUnauthorized:!1},h=>c(h,n,r));d.on("error",r),d.end(i)}),"viaTcp"),v=this.controlSock;return!v||!fs.existsSync(v)?this.managed?Promise.reject(new Error("the devserver control socket is not up yet")):b():new Promise((n,r)=>{const d=require("node:http").request({socketPath:v,path:e,method:"POST",headers:o},h=>c(h,n,r));d.on("error",r),d.end(i)}).catch(n=>{if(this.managed||n instanceof notice_generation_1.NotThisGeneration)throw n;return b()})}snapshot(){return{...this.testimony,recentPaths:[...this.testimony.recentPaths]}}watchRun(e){if(this.testimony.lastWatchRunAt=Date.now(),e.length){const t=[...this.testimony.recentPaths,...e];this.testimony.recentPaths=t.slice(-DevserverNotifier.RECENT_PATH_CAP)}this.building??={startedAt:this.testimony.lastWatchRunAt,changed:new Set,more:0};for(const t of e)this.building.changed.has(t)||(this.building.changed.size>=DevserverNotifier.BUILD_PATH_CAP?this.building.more+=1:this.building.changed.add(t));this.testimony.building=this.buildAccount(),this.tellWatching()}buildAccount(){if(this.building)return{startedAt:this.building.startedAt,changedFiles:[...this.building.changed],...this.building.more>0?{moreChanged:this.building.more}:{}}}failed(e){const t=Date.now(),i=this.buildAccount()??(e!==void 0?{startedAt:t,changedFiles:[]}:void 0);this.building=void 0,delete this.testimony.building,this.testimony.lastFailedAt=t;const o=i?{...i,endedAt:t,...e?{error:e.slice(0,FAILURE_TEXT_CAP)}:{}}:void 0;this.post("/webpack/watching",{...this.testimony,...o?{failed:o}:{}}).catch(()=>{})}degrade(e){this.testimony.degraded=e,this.tellWatching()}tellWatching(){this.post("/webpack/watching",this.testimony).catch(()=>{})}async built(e=!0){this.testimony.lastBuildAt=Date.now(),this.testimony.buildsSinceSpawn+=1;const t=this.buildAccount();if(this.building=void 0,delete this.testimony.building,!e){const i=t?{...t,endedAt:this.testimony.lastBuildAt}:void 0;this.post("/webpack/watching",{...this.testimony,...i?{unchanged:i}:{}}).catch(()=>{});return}try{await this.post("/webpack/reload",{watcher:this.testimony,...t?{build:{...t,endedAt:this.testimony.lastBuildAt}}:{}}),console.log("[DevServerReloadPlugin] notified devserver to reload")}catch(i){i instanceof notice_generation_1.NotThisGeneration?console.log(`[DevServerReloadPlugin] not this generation's devserver (${i.message}) \u2014 not reloaded; this generation's devserver loads the build when it starts`):console.log("[DevServerReloadPlugin] devserver not running yet")}}}exports.DevserverNotifier=DevserverNotifier;const FAILURE_TEXT_CAP=500;function devserverControlSock(s,e=process.env){const t=e.LENSMCP_SOCK_NS||e.LENSMCP_WS_KEY||"",i=t?path.join(os.tmpdir(),t,`${s}-devserver`):path.join(os.tmpdir(),`${s}-devserver`);return path.join(i,"parent.sock")}g(devserverControlSock,"devserverControlSock"),u(devserverControlSock,"devserverControlSock"),a(devserverControlSock,"devserverControlSock");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
1
2
|
export interface JwksVerifierOptions {
|
|
2
3
|
/** The IdP JWKS endpoint, e.g. `https://issuer/.well-known/jwks.json`. */
|
|
3
4
|
jwksUrl: string;
|
|
@@ -52,6 +53,14 @@ export interface JwksVerifier extends JwtVerifier {
|
|
|
52
53
|
* fetch nor a wait. True when the token's key is now cached. Never rejects.
|
|
53
54
|
*/
|
|
54
55
|
awaitKey(token: string): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* `verify`, except that a `kid` miss the shared refresh could still fix waits for it — the same ONE refresh, the
|
|
58
|
+
* same `missWaitMs` bound and the same cooldown as `awaitKey` — and is then verified against the refreshed set.
|
|
59
|
+
* Everything else is answered SYNCHRONOUSLY, never as a promise: a hit, a token refused before any key lookup, a
|
|
60
|
+
* miss inside the cooldown with nothing in flight. So a hot-path caller awaits only an `instanceof Promise` answer
|
|
61
|
+
* and a hit costs it no microtask. The promise never rejects.
|
|
62
|
+
*/
|
|
63
|
+
verifyOrAwait(token: string): Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
55
64
|
/**
|
|
56
65
|
* Load the key set in the background, retrying with a capped exponential backoff until it lands or the attempts run
|
|
57
66
|
* out. For a boot whose IdP may not be up yet: it never throws, the caller never waits on it, and `stop()` cancels
|
|
@@ -59,4 +68,10 @@ export interface JwksVerifier extends JwtVerifier {
|
|
|
59
68
|
*/
|
|
60
69
|
warm(opts?: JwksWarmOptions): Promise<boolean>;
|
|
61
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The prod gateway's `identify` over a JWKS verifier: the verified claims of the `Authorization: Bearer` token, a
|
|
73
|
+
* promise only on a `kid` miss the shared refresh could still fix ({@link JwksVerifier.verifyOrAwait}). One function
|
|
74
|
+
* for the entry (`main.prod-gateway.ts`) and the specs, so they drive the gateway through what the entry wires.
|
|
75
|
+
*/
|
|
76
|
+
export declare function bearerIdentify(verifier: Pick<JwksVerifier, 'verifyOrAwait'>): (req: IncomingMessage) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
62
77
|
export declare function createJwksVerifier(opts: JwksVerifierOptions): JwksVerifier;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var V=Object.defineProperty;var
|
|
1
|
+
"use strict";var V=Object.defineProperty;var m=(n,o)=>V(n,"name",{value:o,configurable:!0});var D=Object.defineProperty,w=m((n,o)=>D(n,"name",{value:o,configurable:!0}),"w"),W=Object.defineProperty,i=w((n,o)=>W(n,"name",{value:o,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.bearerIdentify=bearerIdentify,exports.createJwksVerifier=createJwksVerifier;const node_crypto_1=require("node:crypto"),jwt_claims_js_1=require("./jwt-claims.js");function bearerIdentify(n){return o=>{const d=String(o.headers.authorization??"");return d.startsWith("Bearer ")?n.verifyOrAwait(d.slice(7)):void 0}}m(bearerIdentify,"bearerIdentify"),w(bearerIdentify,"bearerIdentify"),i(bearerIdentify,"bearerIdentify");const ALG_TO_HASH={RS256:"sha256",RS384:"sha384",RS512:"sha512"},b64urlJsonSafe=i(n=>JSON.parse(Buffer.from(n,"base64url").toString("utf8")),"b64urlJsonSafe");function createJwksVerifier(n){const o=(n.algorithms??["RS256"]).filter(r=>r in ALG_TO_HASH),d=n.cacheMaxAgeMs??6e5,J=n.missRefreshMs??3e4,O=n.missWaitMs??2e3,S=n.fetchTimeoutMs??1e4,P=n.clockToleranceSec??60,I=n.fetchImpl??globalThis.fetch,M=n.audience===void 0?void 0:Array.isArray(n.audience)?n.audience:[n.audience];let c=new Map,u,f,T=0,v=!1;const h=new Map,x=i(async r=>{const e=await I(n.jwksUrl,{headers:{accept:"application/json"},signal:r});if(!e.ok)throw new Error(`JWKS ${n.jwksUrl} \u2192 HTTP ${e.status}`);const t=await e.json(),a=new Map;for(const s of t.keys??[])if(!(s.kty!=="RSA"||!s.n||!s.e)&&!(s.use&&s.use!=="sig"))try{const b=(0,node_crypto_1.createPublicKey)({key:s,format:"jwk"});a.set(s.kid??"__single__",b)}catch{}a.size>0&&(c=a)},"load"),y=i(()=>{if(!u){const r=new AbortController;u=new Promise((e,t)=>{const a=setTimeout(()=>{r.abort(),t(new Error(`JWKS ${n.jwksUrl} \u2192 no answer in ${S} ms`))},S);a.unref?.(),x(r.signal).then(()=>{clearTimeout(a),e()},s=>{clearTimeout(a),t(s)})}).finally(()=>{u=void 0})}return u},"refresh"),p=i(()=>{if(f)return f;if(!(!u&&Date.now()<T))return f=y().catch(()=>{}).finally(()=>{f=void 0,T=Date.now()+J}),f},"refreshOnMiss"),j=setInterval(()=>{y().catch(()=>{})},d);j.unref?.();const g=i(r=>{const e=r.split(".");if(e.length!==3)return;let t;try{t=b64urlJsonSafe(e[0])}catch{return}if(!(!t.alg||!o.includes(t.alg)))return{parts:e,header:t}},"parse"),l=i(r=>r.kid!==void 0?c.get(r.kid):c.get("__single__")??(c.size===1?[...c.values()][0]:void 0),"keyFor"),k=i(({parts:r,header:e},t)=>{let a;try{a=(0,node_crypto_1.verify)(ALG_TO_HASH[e.alg],Buffer.from(`${r[0]}.${r[1]}`),t,Buffer.from(r[2],"base64url"))}catch{return}if(!a)return;let s;try{s=b64urlJsonSafe(r[1])}catch{return}if((0,jwt_claims_js_1.checkStandardClaims)(s,{...n.issuer?{issuer:n.issuer}:{},...M?{audience:M}:{},clockToleranceS:P}))return s},"verifyWith"),R=i(r=>{const e=g(r);if(!e)return;const t=l(e.header);if(!t){p();return}return k(e,t)},"verify"),A=i(async r=>{let e;await Promise.race([r,new Promise(t=>{e=setTimeout(t,O),e.unref?.()})]),clearTimeout(e)},"waitBounded"),$=i(async r=>{const e=g(r);if(!e)return!1;if(l(e.header))return!0;const t=p();return t?(await A(t),l(e.header)!==void 0):!1},"awaitKey"),B=i(r=>{const e=g(r);if(!e)return;const t=l(e.header);if(t)return k(e,t);const a=p();if(a)return A(a).then(()=>{const s=l(e.header);return s?k(e,s):void 0})},"verifyOrAwait"),K=i(r=>new Promise(e=>{const t=setTimeout(()=>{h.delete(t),e()},r);t.unref?.(),h.set(t,e)}),"sleep");return{verify:R,refresh:y,awaitKey:$,verifyOrAwait:B,warm:i(async(r={})=>{const e=Math.max(1,r.attempts??8),t=r.baseDelayMs??1e3,a=r.maxDelayMs??3e4;for(let s=1;!v;s++)try{return await y(),!0}catch(b){if(v)return!1;const _=s<e?Math.min(a,t*2**(s-1)):void 0;try{r.onError?.(b,s,_)}catch{}if(_===void 0)return!1;await K(_)}return!1},"warm"),stop:i(()=>{v=!0,clearInterval(j);for(const[r,e]of h)clearTimeout(r),e();h.clear()},"stop")}}m(createJwksVerifier,"createJwksVerifier"),w(createJwksVerifier,"createJwksVerifier"),i(createJwksVerifier,"createJwksVerifier");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var ue=Object.defineProperty;var M=(e,t)=>ue(e,"name",{value:t,configurable:!0});var ce=Object.defineProperty,R=M((e,t)=>ce(e,"name",{value:t,configurable:!0}),"R"),ie=Object.defineProperty,a=R((e,t)=>ie(e,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),node_fs_1=require("node:fs"),node_crypto_1=require("node:crypto"),node_path_1=require("node:path"),node_os_1=require("node:os"),node_cluster_1=tslib_1.__importDefault(require("node:cluster")),prod_gateway_lib_1=require("./prod-gateway.lib"),jwt_claims_1=require("./jwt-claims"),jwks_verify_1=require("./jwks-verify"),otel_tracing_1=require("./otel-tracing"),health_check_1=require("./health-check"),rate_limit_1=require("./rate-limit"),metrics_1=require("./metrics"),edge_1=require("./prod-runtime/edge"),registry_source_1=require("./registry-source"),providers_prod_1=require("./providers-prod"),prod_config_1=require("./prod-config");function readJson(e,t){if(!e)return t;try{return JSON.parse((0,node_fs_1.readFileSync)(e,"utf8"))}catch(n){return console.error(`[gateway] could not read ${e}:`,n.message),t}}M(readJson,"readJson"),R(readJson,"readJson"),a(readJson,"readJson");function watchJsonFile(e,t,n=1e3){let u=-1;const _=setInterval(a(()=>{if(!(0,node_fs_1.existsSync)(e))return;let p;try{p=(0,node_fs_1.statSync)(e).mtimeMs}catch{return}if(p===u)return;let w;try{w=JSON.parse((0,node_fs_1.readFileSync)(e,"utf8"))}catch(g){console.error("[gateway] endpoints reload:",g.message);return}u=p,t(w)},"tick"),n);return _.unref?.(),()=>clearInterval(_)}M(watchJsonFile,"watchJsonFile"),R(watchJsonFile,"watchJsonFile"),a(watchJsonFile,"watchJsonFile");async function main(){const e=process.env,t=(0,prod_config_1.parseGatewayEnv)(e),n=t.ports;process.on("uncaughtException",s=>console.error("[gateway] uncaughtException:",s)),process.on("unhandledRejection",s=>console.error("[gateway] unhandledRejection:",s));const u=[];let _=!1;const p=a(async s=>{if(!_){_=!0,console.log(`[gateway] ${s} \u2014 draining\u2026`);for(const r of u)try{await r()}catch{}process.exit(0)}},"shutdown");for(const s of["SIGTERM","SIGINT"])process.on(s,()=>{p(s)});const w=e.LENSMCP_GW_REDIS_URL;let g,m,$,O;const A={};let f;if(w){const s=require("ioredis"),r=new s(w);f=r;const o=(0,registry_source_1.createRegistryProviders)((0,registry_source_1.redisRegistrySource)(r,e.LENSMCP_GW_REDIS_PREFIX?{prefix:e.LENSMCP_GW_REDIS_PREFIX}:{}),{onError:a(i=>console.error("[gateway] registry:",i instanceof Error?i.message:i),"onError")});await o.start(),g=o.manifests,m=o.pods,O=o,console.log(`[gateway] registry: Redis-backed, live (${g.list().length} service(s))`)}else{const s=e.LENSMCP_GW_MANIFESTS;s?g=(0,providers_prod_1.fileManifestProvider)(s,{onError:a(o=>console.error("[gateway] manifest:",o instanceof Error?o.message:o),"onError")}):(console.warn("[gateway] no LENSMCP_GW_MANIFESTS / LENSMCP_GW_REDIS_URL \u2014 starting with an EMPTY route table (all requests 404 until manifests arrive)."),g=(0,providers_prod_1.staticManifestProvider)([]));const r=e.LENSMCP_GW_ENDPOINTS;if(r){const o=a(E=>console.error("[gateway] endpoints:",E instanceof Error?E.message:E),"onEpError"),i=readJson(r,{}),c=Object.keys(i).length,l=(0,providers_prod_1.isRolloutShape)(i);m=l?(0,providers_prod_1.rolloutPodProvider)(i,{onError:o}):(0,providers_prod_1.endpointsPodProvider)(i,{onError:o}),console.log(c?`[gateway] endpoints: ${l?"ROLLOUT (weighted cohorts)":"flat"} \u2014 ${c} service(s)`:`[gateway] endpoints: ${(0,node_fs_1.existsSync)(r)?"present but empty/unreadable":"file not there yet"} \u2014 watching ${r}`),$=watchJsonFile(r,E=>{try{m?.update?.(E),A.current?.refreshUpstreams(),console.log("[gateway] endpoints reloaded")}catch(le){console.error("[gateway] endpoints reload failed:",le.message)}})}}const ee=readJson(e.LENSMCP_GW_KEYS,{}),F=e.LENSMCP_GW_TLS_KEY&&e.LENSMCP_GW_TLS_CERT?{key:(0,node_fs_1.readFileSync)(e.LENSMCP_GW_TLS_KEY),cert:(0,node_fs_1.readFileSync)(e.LENSMCP_GW_TLS_CERT)}:void 0,te=t.jwt.presenceOnly,j=t.jwt.jwksUrl,T=t.jwt.secret,S=j?(0,jwks_verify_1.createJwksVerifier)({jwksUrl:j,...t.jwt.issuer?{issuer:t.jwt.issuer}:{},...t.jwt.audience?{audience:t.jwt.audience}:{}}):void 0,re=a(s=>{if(!T)return;const r=s.split(".");if(r.length===3)try{if(JSON.parse(Buffer.from(r[0],"base64url").toString()).alg!=="HS256")return;const o=(0,node_crypto_1.createHmac)("sha256",T).update(r[0]+"."+r[1]).digest("base64url"),i=Buffer.from(r[2]),c=Buffer.from(o);if(i.length!==c.length||!(0,node_crypto_1.timingSafeEqual)(i,c))return;const l=JSON.parse(Buffer.from(r[1],"base64url").toString());return(0,jwt_claims_1.checkStandardClaims)(l,{...t.jwt.issuer?{issuer:t.jwt.issuer}:{},...t.jwt.audience?.length?{audience:t.jwt.audience}:{}})?l:void 0}catch{return}},"hs256"),D=a(s=>{const r=String(s.headers.authorization??"");if(!r.startsWith("Bearer "))return;const o=r.slice(7);return S?S.verify(o):re(o)},"bearerClaims"),q=S
|
|
1
|
+
"use strict";var ue=Object.defineProperty;var M=(e,t)=>ue(e,"name",{value:t,configurable:!0});var ce=Object.defineProperty,R=M((e,t)=>ce(e,"name",{value:t,configurable:!0}),"R"),ie=Object.defineProperty,a=R((e,t)=>ie(e,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),node_fs_1=require("node:fs"),node_crypto_1=require("node:crypto"),node_path_1=require("node:path"),node_os_1=require("node:os"),node_cluster_1=tslib_1.__importDefault(require("node:cluster")),prod_gateway_lib_1=require("./prod-gateway.lib"),jwt_claims_1=require("./jwt-claims"),jwks_verify_1=require("./jwks-verify"),otel_tracing_1=require("./otel-tracing"),health_check_1=require("./health-check"),rate_limit_1=require("./rate-limit"),metrics_1=require("./metrics"),edge_1=require("./prod-runtime/edge"),registry_source_1=require("./registry-source"),providers_prod_1=require("./providers-prod"),prod_config_1=require("./prod-config");function readJson(e,t){if(!e)return t;try{return JSON.parse((0,node_fs_1.readFileSync)(e,"utf8"))}catch(n){return console.error(`[gateway] could not read ${e}:`,n.message),t}}M(readJson,"readJson"),R(readJson,"readJson"),a(readJson,"readJson");function watchJsonFile(e,t,n=1e3){let u=-1;const _=setInterval(a(()=>{if(!(0,node_fs_1.existsSync)(e))return;let p;try{p=(0,node_fs_1.statSync)(e).mtimeMs}catch{return}if(p===u)return;let w;try{w=JSON.parse((0,node_fs_1.readFileSync)(e,"utf8"))}catch(g){console.error("[gateway] endpoints reload:",g.message);return}u=p,t(w)},"tick"),n);return _.unref?.(),()=>clearInterval(_)}M(watchJsonFile,"watchJsonFile"),R(watchJsonFile,"watchJsonFile"),a(watchJsonFile,"watchJsonFile");async function main(){const e=process.env,t=(0,prod_config_1.parseGatewayEnv)(e),n=t.ports;process.on("uncaughtException",s=>console.error("[gateway] uncaughtException:",s)),process.on("unhandledRejection",s=>console.error("[gateway] unhandledRejection:",s));const u=[];let _=!1;const p=a(async s=>{if(!_){_=!0,console.log(`[gateway] ${s} \u2014 draining\u2026`);for(const r of u)try{await r()}catch{}process.exit(0)}},"shutdown");for(const s of["SIGTERM","SIGINT"])process.on(s,()=>{p(s)});const w=e.LENSMCP_GW_REDIS_URL;let g,m,$,O;const A={};let f;if(w){const s=require("ioredis"),r=new s(w);f=r;const o=(0,registry_source_1.createRegistryProviders)((0,registry_source_1.redisRegistrySource)(r,e.LENSMCP_GW_REDIS_PREFIX?{prefix:e.LENSMCP_GW_REDIS_PREFIX}:{}),{onError:a(i=>console.error("[gateway] registry:",i instanceof Error?i.message:i),"onError")});await o.start(),g=o.manifests,m=o.pods,O=o,console.log(`[gateway] registry: Redis-backed, live (${g.list().length} service(s))`)}else{const s=e.LENSMCP_GW_MANIFESTS;s?g=(0,providers_prod_1.fileManifestProvider)(s,{onError:a(o=>console.error("[gateway] manifest:",o instanceof Error?o.message:o),"onError")}):(console.warn("[gateway] no LENSMCP_GW_MANIFESTS / LENSMCP_GW_REDIS_URL \u2014 starting with an EMPTY route table (all requests 404 until manifests arrive)."),g=(0,providers_prod_1.staticManifestProvider)([]));const r=e.LENSMCP_GW_ENDPOINTS;if(r){const o=a(E=>console.error("[gateway] endpoints:",E instanceof Error?E.message:E),"onEpError"),i=readJson(r,{}),c=Object.keys(i).length,l=(0,providers_prod_1.isRolloutShape)(i);m=l?(0,providers_prod_1.rolloutPodProvider)(i,{onError:o}):(0,providers_prod_1.endpointsPodProvider)(i,{onError:o}),console.log(c?`[gateway] endpoints: ${l?"ROLLOUT (weighted cohorts)":"flat"} \u2014 ${c} service(s)`:`[gateway] endpoints: ${(0,node_fs_1.existsSync)(r)?"present but empty/unreadable":"file not there yet"} \u2014 watching ${r}`),$=watchJsonFile(r,E=>{try{m?.update?.(E),A.current?.refreshUpstreams(),console.log("[gateway] endpoints reloaded")}catch(le){console.error("[gateway] endpoints reload failed:",le.message)}})}}const ee=readJson(e.LENSMCP_GW_KEYS,{}),F=e.LENSMCP_GW_TLS_KEY&&e.LENSMCP_GW_TLS_CERT?{key:(0,node_fs_1.readFileSync)(e.LENSMCP_GW_TLS_KEY),cert:(0,node_fs_1.readFileSync)(e.LENSMCP_GW_TLS_CERT)}:void 0,te=t.jwt.presenceOnly,j=t.jwt.jwksUrl,T=t.jwt.secret,S=j?(0,jwks_verify_1.createJwksVerifier)({jwksUrl:j,...t.jwt.issuer?{issuer:t.jwt.issuer}:{},...t.jwt.audience?{audience:t.jwt.audience}:{}}):void 0,re=a(s=>{if(!T)return;const r=s.split(".");if(r.length===3)try{if(JSON.parse(Buffer.from(r[0],"base64url").toString()).alg!=="HS256")return;const o=(0,node_crypto_1.createHmac)("sha256",T).update(r[0]+"."+r[1]).digest("base64url"),i=Buffer.from(r[2]),c=Buffer.from(o);if(i.length!==c.length||!(0,node_crypto_1.timingSafeEqual)(i,c))return;const l=JSON.parse(Buffer.from(r[1],"base64url").toString());return(0,jwt_claims_1.checkStandardClaims)(l,{...t.jwt.issuer?{issuer:t.jwt.issuer}:{},...t.jwt.audience?.length?{audience:t.jwt.audience}:{}})?l:void 0}catch{return}},"hs256"),D=a(s=>{const r=String(s.headers.authorization??"");if(!r.startsWith("Bearer "))return;const o=r.slice(7);return S?S.verify(o):re(o)},"bearerClaims"),q=S?(0,jwks_verify_1.bearerIdentify)(S):T?D:void 0,se=a((s,r)=>{if(s==="jwt"){if(S||T){if(!D(r))throw new Error("invalid or missing token");return}if(!te)throw new Error("jwt route but no verifier configured (set LENSMCP_GW_JWKS_URL or LENSMCP_GW_JWT_SECRET)");if(!String(r.headers.authorization??"").startsWith("Bearer "))throw new Error("missing bearer token")}},"authenticate"),{attributeHeaders:U,trustProxyHops:oe,trustProxyIp:x,uidHeader:J,cookie:H}=t,y=e.LENSMCP_EVENT_FILE,K=y?(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})():void 0,B=K?.createEventFloodGuard({burst:30,perSec:5});let z=!1;const C=y?s=>{try{const r=B?B.admit(String(s.fingerprint??"")):{ok:!0,suppressed:0};if(!r.ok)return;z||((0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(y),{recursive:!0}),z=!0);const o=s.raw,i=JSON.stringify(r.suppressed>0?{...s,raw:{...o,floodSuppressed:r.suppressed}}:s)+`
|
|
2
2
|
`;K?.maybeRotateEventFile(y,i.length),(0,node_fs_1.appendFileSync)(y,i)}catch{}}:void 0;if(S)try{await S.refresh(),console.log("[gateway] JWKS loaded")}catch(s){console.error("[gateway] initial JWKS load failed (will retry in background):",s.message)}const{accessLog:ae,clientIpHeader:X,clientIpTrustedProxies:Y,undici:d}=t;if(t.mtlsFromRedis&&f){const s=e.LENSMCP_GW_REDIS_PREFIX??"lensmcp:gw",[r,o,i]=await f.mget(`${s}:mtls:cert`,`${s}:mtls:key`,`${s}:mtls:ca`);r&&(d.cert=r),o&&(d.key=o),i&&(d.ca=i),(r||o)&&console.log("[gateway] mTLS materials loaded from Redis")}else e.LENSMCP_GW_MTLS_CERT&&(d.cert=(0,node_fs_1.readFileSync)(e.LENSMCP_GW_MTLS_CERT)),e.LENSMCP_GW_MTLS_KEY&&(d.key=(0,node_fs_1.readFileSync)(e.LENSMCP_GW_MTLS_KEY)),e.LENSMCP_GW_MTLS_CA&&(d.ca=(0,node_fs_1.readFileSync)(e.LENSMCP_GW_MTLS_CA));(0,prod_config_1.finalizeUpstreamTls)(d).autoEnabled&&console.log("[gateway] upstream TLS verification ENABLED (a CA is configured; set LENSMCP_GW_UPSTREAM_TLS_VERIFY=0 to opt out)"),(0,prod_config_1.finalizeUpstreamH2)(d,e)&&console.log(`[gateway] mTLS to upstreams ON over ${d.allowH2?"h2":"h1.1"} (server validation ${d.rejectUnauthorized?"enabled":"OFF"})`);const{upstreamRetries:v,server:V}=t,P=(0,otel_tracing_1.setupTracing)({...e.OTEL_SERVICE_NAME?{serviceName:e.OTEL_SERVICE_NAME}:{},...e.LENSMCP_GW_OTEL_SAMPLE?{sampleRatio:Number(e.LENSMCP_GW_OTEL_SAMPLE)}:{}});P&&console.log(`[gateway] OTLP tracing on \u2192 ${(0,edge_1.safeUrl)(e.OTEL_EXPORTER_OTLP_ENDPOINT)}`);const{statusEnabled:N,statusToken:L}=t,Q=(0,node_os_1.hostname)(),ne=t.statusTtlSec,Z=f,k=N&&Z?s=>{for(const r of s)Z.set(`status:${r.service}:${Q}`,JSON.stringify({...r,instance:Q,at:Date.now()}),"EX",ne).catch(()=>{})}:void 0;N&&!L&&console.warn("[gateway] LENSMCP_GW_STATUS=1 but no LENSMCP_GW_STATUS_TOKEN \u2014 /statusz stays DISABLED (404) until a token is set (fail-closed). The Redis status:* fan-out still runs."),N&&console.log(`[gateway] status on \u2014 /statusz${L?"":" (endpoint disabled: no token)"}${k?" + Redis status:* fan-out":""}`);const G=t.health?(0,health_check_1.createHealthChecker)(t.health):void 0;G&&console.log(`[gateway] active health-checking on (path ${t.health?.path??"/healthz"})`);let h,W;if(t.rateLimit){const{limit:s,windowMs:r,wantRedis:o,failClosed:i}=t.rateLimit;if(o){const c=t.rateLimit.dedicatedUrl;let l;if(c){const E=require("ioredis");W=new E(c),l=W}else f&&(l=f);l?(h=(0,rate_limit_1.createRedisRateLimiter)(l,{limit:s,windowMs:r,prefix:`${e.LENSMCP_GW_REDIS_PREFIX??"lensmcp:gw"}:rl:`,...t.rateLimit.timeoutMs!==void 0?{timeoutMs:t.rateLimit.timeoutMs}:{},...i?{failOpen:!1}:{},onFailure:a(E=>console.error(`[gateway] RATE LIMITER DEGRADED \u2014 Redis unusable (${E}). Requests are being ${i?"DENIED":"ALLOWED"} without a limit check. Check the ACL covers ${e.LENSMCP_GW_REDIS_PREFIX??"lensmcp:gw"}:rl:*`),"onFailure")}),console.log(`[gateway] rate limit on \u2014 ${s} req / ${r}ms GLOBAL (Redis: ${c?"dedicated":"shared registry"}, fail-${i?"closed":"open"}) per client IP`)):console.warn("[gateway] LENSMCP_GW_RATE_LIMIT_REDIS set but no Redis configured (set LENSMCP_GW_REDIS_URL or LENSMCP_GW_RATE_LIMIT_REDIS_URL) \u2014 falling back to PER-INSTANCE in-memory limiting.")}if(!h){const c=(0,prod_config_1.workerCount)(e),l=(0,prod_config_1.perWorkerLimit)(s,c);h=(0,rate_limit_1.createRateLimiter)({limit:l,windowMs:r}),console.log(`[gateway] rate limit on \u2014 ${s} req / ${r}ms per client IP (in-memory`+(c>1?`, ${l}/worker \xD7 ${c} workers`:", per-instance")+")")}}const b=t.metricsEnabled?(0,metrics_1.createProcessMetrics)():void 0;b&&console.log(`[gateway] process metrics on \u2014 /metricsz${L?" (token-gated)":" (DISABLED: needs LENSMCP_GW_STATUS_TOKEN)"}`);const I=await(0,prod_gateway_lib_1.startProdGateway)({...n.length?{ports:n}:{},manifests:g,...m?{pods:m}:{},serviceKeys:ee,authenticate:se,...F?{tls:F}:{},...C?{emit:C}:{},...q?{identify:q}:{},...U.length?{attributeHeaders:U}:{},...x?{trustProxyIp:x,trustProxyHops:oe}:{},...H?{cookie:H}:{},...J?{uidHeader:J}:{},...ae?{accessLog:!0}:{},...X?{clientIpHeader:X}:{},...Y.length?{clientIpTrustedProxies:Y}:{},...P?{tracing:P}:{},...N?{statusEndpoint:!0}:{},...L?{statusToken:L}:{},...k?{onStatus:k}:{},...G?{healthChecker:G}:{},...h?{rateLimiter:h}:{},...b?{metrics:b}:{},...t.trafficFlushMs!==void 0?{trafficFlushMs:t.trafficFlushMs}:{},...v!==void 0&&Number.isFinite(v)?{upstreamRetries:v}:{},...Object.keys(d).length?{undici:d}:{},...Object.keys(V).length?{server:V}:{}});A.current=I,console.log(`[gateway] production gateway up on ${I.ports.join(", ")} (${I.routes().length} routes${C?", lens ON":""})`),u.push(()=>g.stop?.(),()=>$?.(),()=>O?.stop(),()=>W?.quit(),()=>S?.stop(),()=>I.stop(),()=>P?.shutdown())}M(main,"main"),R(main,"main"),a(main,"main");const WORKERS=(0,prod_config_1.workerCount)(process.env);if(WORKERS>1&&node_cluster_1.default.isPrimary){if(console.log(`[gateway] primary ${process.pid}: forking ${WORKERS} workers (one per core)\u2026`),process.env.LENSMCP_GW_RATE_LIMIT&&!(0,prod_config_1.redisLimiterConfigured)(process.env)){const n=process.env.LENSMCP_GW_RATE_LIMIT_REDIS==="1",u=Number(process.env.LENSMCP_GW_RATE_LIMIT),_=(0,prod_config_1.perWorkerLimit)(u,WORKERS);console.warn(`[gateway] WARNING: ${n?"LENSMCP_GW_RATE_LIMIT_REDIS=1 is set but no Redis URL is \u2014 each worker will fall back to":"using"} the in-memory rate limiter with ${WORKERS} workers. Each worker now enforces ${_} req/window so the total stays \u2248 ${u}; a single client's burst can still land unevenly across workers. Set ${n?"LENSMCP_GW_REDIS_URL (or LENSMCP_GW_RATE_LIMIT_REDIS_URL)":"LENSMCP_GW_RATE_LIMIT_REDIS=1 and a Redis URL"} for one shared global budget.`)}let e=!1;for(let n=0;n<WORKERS;n++)node_cluster_1.default.fork();node_cluster_1.default.on("exit",(n,u,_)=>{e||(console.warn(`[gateway] worker ${n.process.pid} exited (code=${u} sig=${_??""}) \u2014 respawning.`),node_cluster_1.default.fork())});const t=a(n=>{if(!e){e=!0,console.log(`[gateway] primary: ${n} \u2014 stopping ${Object.keys(node_cluster_1.default.workers??{}).length} workers\u2026`);for(const u of Object.values(node_cluster_1.default.workers??{}))try{u?.kill(n)}catch{}setTimeout(()=>process.exit(0),12e3).unref()}},"stopAll");process.on("SIGTERM",()=>t("SIGTERM")),process.on("SIGINT",()=>t("SIGINT"))}else main().catch(e=>{console.error("[gateway] FATAL: could not start \u2014",e instanceof Error?e.stack??e.message:e),process.exit(1)});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var X=Object.defineProperty;var N=(f,o)=>X(f,"name",{value:o,configurable:!0});var W=Object.defineProperty,_=N((f,o)=>W(f,"name",{value:o,configurable:!0}),"_"),z=Object.defineProperty,v=_((f,o)=>z(f,"name",{value:o,configurable:!0}),"v");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),gateway_errors_1=require("../gateway-errors"),edge_1=require("./edge");function createHandler(f){const{rt:o,obs:d,cors:M,auth:C,upstream:$,rollout:w,hooks:p,reqState:D,spanByReq:V,ctxByReq:B}=f,j=!!o.opts.pods?.resolveVersion,q=!!o.opts.cookie?.secret,I=!!o.opts.pods?.bucket&&(q||!!o.opts.uidHeader),F=j||q||I,G=o.opts.upstreamRetries??1,K=v(u=>(0,edge_1.tokenMatches)(u.query?.token??(0,edge_1.hdr)(u.raw.headers["x-status-token"]),o.opts.statusToken),"statusTokenMatches"),J=v((u,a,e,m,y)=>{const n=u.raw;if(M.setCors(a,n),a.header("cache-control",edge_1.NO_STORE),n.method==="OPTIONS"){a.code(204).header("access-control-allow-methods","GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS").header("access-control-allow-headers",String(n.headers["access-control-request-headers"]??"*")).send();return}const s=o.table.routes.filter(i=>i.host&&(0,manifest_1.hostMatches)(i.host,e)&&i.prefix),h=y;if(s.length>0&&(h==="/health"||h==="/healthz")){a.code(200).type("application/json").send(JSON.stringify({status:"ok",host:e,services:s.map(i=>({service:i.service,prefix:i.prefix}))}));return}if(h==="/.well-known/lensmcp-routes"){if(!K(u)){(0,edge_1.sendError)(a,n,404,"not_found","No gateway route for this hostname.");return}a.code(200).type("application/json").send(JSON.stringify({host:e,routes:o.table.routes.filter(i=>!i.host||(0,manifest_1.hostMatches)(i.host,e)).map(i=>({host:i.host??"(default)",service:i.service,prefix:i.prefix,internal:!!i.internal,auth:i.auth}))}));return}(0,edge_1.sendError)(a,n,404,"not_found",s.length>0?`No service mounted at ${m} on ${e}.`:"No gateway route for this hostname.")},"handleNoRoute");return async(u,a)=>{const e=u.raw,m=(e.headers.host||"").split(":")[0],y=e.url??"/",n=d.beginTrace(e);(0,gateway_errors_1.ensureRequestId)(e),d.step(n,"received",{host:m,method:e.method,url:y});const s=p.any?p.httpContext(e,u,a,n):void 0;if(s&&B.set(u,s),s&&p.has("onRequest")&&(await p.run("onRequest",s),s.responded()))return a;if(e.httpVersionMajor<1)return d.finish(n,"gateway",400),(0,edge_1.sendError)(a,e,400,"bad_request","Bad Request");const h=(0,edge_1.canonicalize)(y);if(h===void 0)return d.step(n,"bad-path",{}),d.finish(n,"gateway",400),(0,edge_1.sendError)(a,e,400,"bad_request","Bad Request");const i=h.policy,b=h.path+h.query;b!==y&&(0,edge_1.rememberRawTarget)(e,y),e.url=b;const L=e.socket?.remoteAddress??u.ip,T=w.clientIpOf(e,L);if(o.opts.rateLimiter){const r=await o.opts.rateLimiter.check(T);if(!r.allowed)return a.header("retry-after",String(Math.ceil(r.retryAfterMs/1e3))),d.step(n,"rate-limited",{}),d.finish(n,"gateway",429),(0,edge_1.sendError)(a,e,429,"rate_limited","Too Many Requests")}const t=(0,manifest_1.matchRoute)(o.table.routes,m,i+h.query);if(!t)return J(u,a,m,b,i),a;if(s&&(s.route=t),d.step(n,"route-match",{project:t.service,...t.prefix?{prefix:t.prefix}:{},...t.internal?{internal:!0}:{}}),e.method==="OPTIONS")return M.setCors(a,e),a.code(204).header("access-control-allow-methods","GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS").header("access-control-allow-headers",String(e.headers["access-control-request-headers"]??"*")).send(),a;const k=o.opts.tracing?.startSpan(e,`gateway ${t.service}`,{"http.request.method":e.method??"GET","url.path":b.split("?")[0],"server.address":m,"lensmcp.service":t.service,...t.internal?{"lensmcp.internal":!0}:{}});if(k&&V.set(u,k),s&&p.has("beforeAuth")&&(await p.run("beforeAuth",s),s.responded()))return a;const O=t.internal?void 0:o.opts.identify?.(e),P=O instanceof Promise?await O:O;let S;if(t.internal){const r=C.internal(e,t,n);if(!r.ok)return d.finish(n,t.service,r.status),(0,edge_1.sendError)(a,e,r.status,r.reason,r.detail);S=r.caller,s&&(s.caller=S)}else{const r=C.public(e,t,i,P,T,n);if(!r.ok)return d.finish(n,t.service,r.status),(0,edge_1.sendError)(a,e,r.status,r.reason,r.detail);if(s&&(s.claims=P),s&&p.has("afterAuth")&&(await p.run("afterAuth",s),s.responded()))return a}if(e.headers["x-forwarded-for"]=w.forwardedFor(e,L),e.headers["x-forwarded-proto"]=o.opts.tls?"https":"http",e.headers["x-forwarded-host"]=e.headers.host??m,o.opts.serviceKeys?.[t.service]&&(e.headers["x-internal-token"]=o.opts.serviceKeys[t.service]),t.prependPrefix){const r=e.url??"/",c=t.prependPrefix;r!==c&&!r.startsWith(c+"/")&&(e.url=c+r)}const x=(0,edge_1.hdr)(e.headers[o.versionHeader]);let R=x,A=!1,E;if(F&&!t.internal&&!x){const r=w.resolveStickyKey(e);r.mint&&(E=r.key);const c=j?o.opts.pods.resolveVersion(t.service,w.buildAttrs(e,T,P,r.key)):void 0;if(c)R=c.version,A=!0,d.step(n,"targeting",{rule:c.rule,version:c.version});else if(r.key){const g=o.opts.pods?.bucket?.(t.service,r.key);g&&(R=g.version,A=!0,d.step(n,"targeting",{mode:"sticky",version:g.version}))}}const l=await $.resolveUpstream(t,n,R,A);if(!l)return d.finish(n,t.service,503),(0,edge_1.sendError)(a,e,503,"unavailable",`Service ${t.service} has no available upstream.`);if(!("url"in l))return d.finish(n,t.service,502),(0,edge_1.sendError)(a,e,502,"unavailable",`Service ${t.service}: socket upstreams are not supported by the prod gateway.`);const Q=new URL(l.url).origin,H=l.url,U={route:t,caller:S,startedAt:Date.now(),trace:n,version:l.version,upLabel:H};return D.set(u,U),s&&(s.version=l.version),d.step(n,l.version?"rollout":"upstream",{upstream:H,...l.version?{version:l.version,...x?{pinned:!0}:{}}:{}}),k&&o.opts.tracing.inject(k,e.headers),s&&p.has("beforeForward")&&(await p.run("beforeForward",s),s.responded())?a:a.from(void 0,{getUpstream:v(()=>Q,"getUpstream"),retriesCount:G,rewriteHeaders:v(r=>{if(U.proxied=!0,n&&(r["x-lensmcp-upstream"]=H),r["x-request-id"]||(r["x-request-id"]=(0,gateway_errors_1.ensureRequestId)(e)),l.version&&(r["x-lensmcp-version"]=l.version),(x||l.version)&&(0,edge_1.addVary)(r,o.versionHeader),(E||I&&q)&&(0,edge_1.addVary)(r,"Cookie"),delete r["x-powered-by"],delete r.connection,delete r["keep-alive"],delete r["proxy-connection"],r.server="LensMCP",E){const c=r["set-cookie"],g=Array.isArray(c)?c:c?[c]:[];r["set-cookie"]=[...g,w.mintCookieHeader(E)]}return r},"rewriteHeaders"),onError:v((r,{error:c})=>{o.opts.pods?.drop?.(t.service,l),s&&p.has("onError")&&(s.error=c,p.run("onError",s)),(0,edge_1.sendError)(r,e,502,"unavailable",`Upstream ${t.service} unavailable.`)},"onError")})}}N(createHandler,"createHandler"),_(createHandler,"createHandler"),v(createHandler,"createHandler");
|
|
@@ -146,8 +146,12 @@ export interface ProdGatewayOptions {
|
|
|
146
146
|
* Exact-matched, case-insensitive. Omit (default) → no credentialed CORS (the Bearer-only edge). */
|
|
147
147
|
corsCredentialOrigins?: string[];
|
|
148
148
|
/** Return verified subject attributes (e.g. JWT claims) for ABAC routing.
|
|
149
|
-
* MUST verify (signature/exp) — forged claims must not reach the rules.
|
|
150
|
-
|
|
149
|
+
* MUST verify (signature/exp) — forged claims must not reach the rules.
|
|
150
|
+
* Answer synchronously whenever possible: it runs on every identity-routed request, and the
|
|
151
|
+
* gateway awaits ONLY a returned `Promise` (so a sync answer costs no microtask). Return one
|
|
152
|
+
* only to wait — e.g. a JWKS `kid` miss waiting on the key-set refresh (`bearerIdentify`,
|
|
153
|
+
* issues/closed/prod-gateway-20). A rejection fails the request, as a throw does. */
|
|
154
|
+
identify?: (req: http.IncomingMessage) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
151
155
|
/** Allowlist of request headers exposed to rules as `header.<name>` attributes. */
|
|
152
156
|
attributeHeaders?: string[];
|
|
153
157
|
/** Take the `ip` attribute from `X-Forwarded-For` (behind a trusted LB); default false →
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var X=Object.defineProperty;var U=(d,n)=>X(d,"name",{value:n,configurable:!0});var W=Object.defineProperty,E=U((d,n)=>W(d,"name",{value:n,configurable:!0}),"E"),G=Object.defineProperty,u=E((d,n)=>G(d,"name",{value:n,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),tls=tslib_1.__importStar(require("node:tls")),manifest_1=require("../manifest"),edge_1=require("./edge"),trust_1=require("./trust"),validHeaderValue=u(d=>/^[\t\x20-\x7e\x80-\xff]*$/.test(d),"validHeaderValue"),validToken=u(d=>/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(d),"validToken"),FORWARDED_HEADERS=["x-forwarded-for","x-forwarded-proto","x-forwarded-host"],MAX_HEAD_BYTES=64*1024,HANDSHAKE_TIMEOUT_MS=3e4;function createUpgrade(d){const{rt:n,obs:h,auth:R,upstream:K,hooks:y,rollout:q}=d,l=u((e,r,s,p)=>{h.emit("warning",`gateway upgrade rejected (${r})${s?` for ${s}`:""}`,`gateway-upgrade:${s??"(none)"}:${r}`,{kind:"gateway-upgrade-rejected",reason:r,...s?{project:s}:{},...p}),e.destroy()},"reject"),N=u((e,r,s="")=>{try{e.end(`HTTP/1.1 ${r}\r
|
|
2
2
|
${s}Connection: close\r
|
|
3
3
|
Content-Length: 0\r
|
|
4
4
|
\r
|
|
5
|
-
`)}catch{e.destroy()}},"refuse"),
|
|
6
|
-
`);return}}const i=y.any?y.upgradeContext(e,
|
|
5
|
+
`)}catch{e.destroy()}},"refuse"),B=u(async(e,r,s)=>{(0,trust_1.stripTrust)(e);const p=(e.headers.host||"").split(":")[0],m=(0,edge_1.canonicalize)(e.url??"/");if(m===void 0){l(r,"bad-path");return}const P=m.policy;e.url=m.path+m.query;const a=(0,manifest_1.matchRoute)(n.table.routes,p,P+m.query);if(!a){l(r,"no-route",void 0,{host:p});return}const O=e.socket?.remoteAddress??"",C=q.clientIpOf(e,O);if(n.opts.rateLimiter){const t=await n.opts.rateLimiter.check(C);if(!t.allowed){h.emit("warning",`gateway upgrade rate-limited for ${a.service}`,`gateway-upgrade:${a.service}:rate-limited`,{kind:"gateway-upgrade-rejected",reason:"rate-limited",project:a.service}),N(r,"429 Too Many Requests",`Retry-After: ${Math.ceil(t.retryAfterMs/1e3)}\r
|
|
6
|
+
`);return}}const i=y.any?y.upgradeContext(e,r,void 0):void 0;i&&(i.route=a);const b=a.internal?void 0:n.opts.identify?.(e);let x;if(b instanceof Promise){const t=u(()=>{},"ignore");if(r.on("error",t),x=await b,r.off("error",t),r.destroyed)return}else x=b;let k;if(a.internal){const t=R.internal(e,a,void 0);if(!t.ok){l(r,"auth-failed",a.service);return}k=t.caller,i&&(i.caller=k)}else{if(!R.public(e,a,P,x,C,void 0).ok){l(r,"auth-failed",a.service);return}if(i&&(i.claims=x),i&&y.has("afterAuth")&&(await y.run("afterAuth",i),i.responded()))return}if(e.headers["x-forwarded-for"]=q.forwardedFor(e,O),e.headers["x-forwarded-proto"]=n.opts.tls?"https":"http",e.headers["x-forwarded-host"]=e.headers.host??p,n.opts.serviceKeys?.[a.service]&&(e.headers["x-internal-token"]=n.opts.serviceKeys[a.service]),a.prependPrefix){const t=e.url??"/",o=a.prependPrefix;t!==o&&!t.startsWith(o+"/")&&(e.url=o+t)}if(i&&y.has("onUpgrade")&&(await y.run("onUpgrade",i),i.responded()))return;const F=(0,edge_1.hdr)(e.headers[n.versionHeader]),T=await K.resolveUpstream(a,void 0,F);if(!T||!("url"in T)){l(r,"no-upstream",a.service);return}const f=new URL(T.url),V=new Set([...trust_1.TRUST_HEADERS,"x-api-key","connection","proxy-connection","keep-alive",...FORWARDED_HEADERS]),D=e.method??"GET",_=e.url??"/";if(!validToken(D)||!validHeaderValue(_)||_.includes(" ")){l(r,"bad-request-line",a.service);return}const j=[`${D} ${_} HTTP/1.1`,"Connection: Upgrade"];for(let t=0;t<e.rawHeaders.length;t+=2){const o=e.rawHeaders[t],$=e.rawHeaders[t+1]??"";if(!V.has(o.toLowerCase())){if(!validToken(o)||!validHeaderValue($)){l(r,"bad-header",a.service,{header:o.toLowerCase()});return}j.push(`${o}: ${$}`)}}for(const t of[...trust_1.TRUST_HEADERS,...FORWARDED_HEADERS]){const o=e.headers[t];if(!(typeof o!="string"||o==="")){if(!validHeaderValue(o)){l(r,"bad-identity-value",a.service,{header:t});return}j.push(`${t}: ${o}`)}}const H=f.protocol==="https:"||f.protocol==="wss:",M=Number(f.port)||(H?443:80),g=n.opts.undici,c=H?tls.connect({port:M,host:f.hostname,...net.isIP(f.hostname)?{}:{servername:f.hostname},rejectUnauthorized:g?.rejectUnauthorized??!1,...g?.cert?{cert:g.cert}:{},...g?.key?{key:g.key}:{},...g?.ca?{ca:g.ca}:{}}):net.connect(M,f.hostname);let v=!1;const w=u(t=>{v||(v=!0,clearTimeout(A),h.emit("warning",`gateway upgrade failed (${t}) for ${a.service}`,`gateway-upgrade:${a.service}:${t}`,{kind:"gateway-upgrade-rejected",reason:t,project:a.service}),c.destroy(),r.destroy())},"abort"),A=setTimeout(()=>w("handshake-timeout"),HANDSHAKE_TIMEOUT_MS);A.unref?.(),c.on("error",()=>w("upstream-error")),r.on("error",()=>w("client-error")),c.on(H?"secureConnect":"connect",()=>{c.write(j.join(`\r
|
|
7
7
|
`)+`\r
|
|
8
8
|
\r
|
|
9
|
-
`),s?.length&&c.write(s)});const
|
|
9
|
+
`),s?.length&&c.write(s)});const L=[];let z=0;const I=u(t=>{if(v)return;L.push(t),z+=t.length;const o=Buffer.concat(L),$=o.indexOf(`\r
|
|
10
10
|
\r
|
|
11
|
-
`);if($===-1){
|
|
12
|
-
`)).toString("latin1"))?.[1]);if(
|
|
11
|
+
`);if($===-1){z>MAX_HEAD_BYTES&&w("oversized-response-head");return}v=!0,clearTimeout(A),c.off("data",I);const S=Number(/^HTTP\/1\.[01] (\d{3})/.exec(o.subarray(0,o.indexOf(`\r
|
|
12
|
+
`)).toString("latin1"))?.[1]);if(S!==101){h.emit("warning",`gateway upgrade refused upstream (${S||"malformed"}) for ${a.service}`,`gateway-upgrade:${a.service}:upstream-refused`,{kind:"gateway-upgrade-rejected",reason:"upstream-refused",project:a.service,status:S||0}),r.end(o.subarray(0,$+4)),c.destroy();return}h.recordEdge(a,0,101,k,T.version),r.write(o),c.pipe(r),r.pipe(c)},"onHead");c.on("data",I),c.on("close",()=>{v||w("upstream-closed")})},"run");return(e,r,s)=>{B(e,r,s).catch(p=>{h.emit("error",`gateway upgrade error: ${p?.message??String(p)}`,"gateway-upgrade:(none):exception",{kind:"gateway-upgrade-rejected",reason:"exception",error:p?.message??String(p)});try{r.destroy()}catch{}})}}U(createUpgrade,"createUpgrade"),E(createUpgrade,"createUpgrade"),u(createUpgrade,"createUpgrade");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ve=Object.defineProperty;var i=(e,r)=>ve(e,"name",{value:r,configurable:!0});var de=Object.defineProperty,n=i((e,r)=>de(e,"name",{value:r,configurable:!0}),"n"),ae=Object.defineProperty,s=n((e,r)=>ae(e,"name",{value:r,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_host_1=require("../../devserver-host"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry");function definedEnv(e){const r={};for(const[j,c]of Object.entries(e))typeof c=="string"&&(r[j]=c);return r}i(definedEnv,"definedEnv"),n(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const j=r.projectsConfigurations.projects[r.projectName],c=r.root,N=path.join(c,j.root),J=e.outputPath?path.join(c,e.outputPath):path.join(c,"dist",j.root),H=e.configEnv||"development",x=path.join(c,"config",`config.${H}.yaml`),A=3050;let p=A,a=r.projectName,$={};fs.existsSync(x)?($=yaml.load(fs.readFileSync(x,"utf8")),p=Number($.port)||p,a=$.serviceName||a):console.warn(`[serve] ${r.projectName}: no config file at ${x} \u2014 public devserver port defaults to ${A}. If another service also lacks its config, both bind ${A} \u2192 EADDRINUSE. Add config/config.${H}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(a=e.serviceName);const M=e.servePrefix??"",_=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),re=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),h=e.logFile?path.resolve(c,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(h){fs.mkdirSync(path.dirname(h),{recursive:!0}),fs.writeFileSync(h,"",{mode:384});try{fs.chmodSync(h,384)}catch{}}let I,F;e.gateway?.middleware&&(I=path.resolve(N,e.gateway.middleware),F=JSON.stringify($));const oe=e.entryFile||"./src/deployments/service/main.ts",te=e.tsConfigFile||"./tsconfig.app.json",se=path.resolve(N,oe),X=path.resolve(N,te),ne=(e.workers||[]).map(o=>({entryName:o.name,entryPath:path.resolve(N,o.entryPath)})),z={appName:r.projectName,appRoot:N,outputDir:J,main:se,tsConfig:X,assets:e.assets||[],additionalEntryPoints:ne,port:p,serviceName:a,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:c,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,q=!1,U=!1,G=!1,f=!1;const K=path.join(__dirname,"../../main.devserver.js"),Z=path.join(__dirname,"../../main.devserver.ts"),B=!fs.existsSync(K)&&fs.existsSync(Z),k=B?Z:K,ie=path.join(J,"main.js");function C(){u&&!u.killed&&(u.kill("SIGTERM"),u=null)}i(C,"q"),n(C,"J"),s(C,"cleanup");function w(){f=!0,C()}i(w,"$"),n(w,"M"),s(w,"stop");const ce=(0,stop_on_signal_1.stopOnSignal)({stop:w,close:s(()=>S(),"close")});process.on("exit",w);function y(){if(f||q)return;if(!fs.existsSync(k)){console.error(`[serve] devserver script not found at ${k}`);return}const o=(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(p),SERVICE_NAME:a,SERVE_PREFIX:M,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||a,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(c,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:ie,...I&&{GATEWAY_MIDDLEWARE:I},...F&&{GATEWAY_CONFIG:F},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},..._&&{LENSMCP_DEBUG_PORT:String(_)},...h&&{LENSMCP_LOG_FILE:h},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:G?"1":"0"});if((0,devserver_host_1.devserverInProcessOffered)()&&(!B||require.extensions[".ts"])){q=!0;for(const[g,d]of Object.entries(o))d!==void 0&&(process.env[g]=d);(0,devserver_host_1.markDevserverInProcess)(),require(k),R("in this process");return}const v=["--enable-source-maps"];B&&v.unshift("--require","@swc-node/register");const l=(0,node_child_process_1.fork)(k,{cwd:c,env:o,stdio:["inherit","inherit","inherit","ipc"],execArgv:v,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(c),a,"dev"))});u=l,l.on("exit",(g,d)=>{u===l&&(u=null),!(d||g===0||f)&&(console.error(`[serve] devserver exited unexpectedly (code=${g}, signal=${d}) \u2014 respawning in 1.5s`),setTimeout(()=>{!f&&!u&&U&&y()},1500).unref?.())}),R()}i(y,"y"),n(y,"P"),s(y,"startDevServer");function R(o){console.log(`[serve] Devserver started on port ${p} (service: ${a}${M?`, prefix: /${M}`:""}${o?`, ${o}`:""})`),_?(console.warn(`[serve] DEBUGGER ENABLED for ${a}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),re?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${_}+slot (workers ${_}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${_}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${a}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${a}\` opens pod inspectors without a restart`),h&&console.log(`[serve] Logs teed to ${h} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}i(R,"U"),n(R,"V"),s(R,"announceDevServer");function Q(){if(!u||u.killed){console.log("[serve] Devserver not running, restarting..."),y();return}fetch(`http://localhost:${p}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(o=>{console.warn("[serve] Failed to notify devserver, restarting...",o.message),C(),y()})}i(Q,"ie"),n(Q,"le"),s(Q,"_notifyDevServerReload");let D=null;const W=[];function m(o){if(D){const v=D;D=null,v(o)}else W.push(o)}i(m,"m"),n(m,"g"),s(m,"pushResult");function V(){return W.length>0?Promise.resolve(W.shift()):new Promise(o=>{D=o})}i(V,"z"),n(V,"se"),s(V,"waitForNext");let S=s(()=>{},"closeBuild");function O(){return!U||!u&&!q?(U=!0,y(),!0):!1}i(O,"B"),n(O,"Y"),s(O,"builtOk");function b(){if(f)return;const{createDevWebpackConfig:o}=require("../../create-webpack-dev"),v=require("webpack"),l=o(z),{watch:g,...d}=l,t=v(d);G=(0,dev_devtool_1.bundleCarriesSourceMaps)(t.options.devtool);let E;const P=(0,compile_retry_1.createFailureRetry)(()=>{f||!E||(console.log(`[serve] ${r.projectName}: compiling again after the failure`),E.invalidate())}),ee=s((L,T)=>{if(L){if(console.error("[serve] Webpack error:",L),E&&!f){const{delayMs:ue,retry:pe}=P.failed();console.log(`[serve] ${r.projectName}: compiling again in ${Math.round(ue/1e3)} s (retry ${pe}) \u2014 webpack does not after a failure like this`)}m({success:!1});return}P.succeeded();const le=T.hasErrors();if(console.info(T.toString({colors:!0,chunks:!1,assets:!0})),le){m({success:!1});return}O(),m({success:!0,baseUrl:`http://localhost:${p}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),t.run((L,T)=>{ee(L,T),t.close(()=>{})});return}E=t.watch(l.watchOptions||{},ee),S=s(()=>{P.dispose(),E.close(()=>{})},"closeBuild")}i(b,"k"),n(b,"U"),s(b,"compileOnItsOwn");function Y(o){const v=o.accepted.projects.filter(t=>t!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${o.accepted.group.slice(0,8)}${v.length?` with ${v.join(", ")}`:""} \u2014 no webpack in this process`);const l=new devserver_notifier_1.DevserverNotifier(p,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(a)),g=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:X,workspaceRoot:c,memoryLimit:e.memoryLimit||8192});let d;S=s(()=>{o.close(),g.dispose()},"closeBuild"),o.listen(t=>{if(!f)switch(t.type){case"watch-run":l.watchRun(t.paths);return;case"watch-closed":l.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${t.message}`),l.failed(`the shared compiler failed: ${t.message}`),m({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${t.reason}`),S(),b();return;case"built":{if(console.info(t.text),G=t.sourceMaps,g.built(t),!t.ok){l.failed(),m({success:!1});return}const E=t.hash!==d;d=t.hash;const P=O();l.built(E&&!P),m({success:!0,baseUrl:`http://localhost:${p}`});return}default:return}})}if(i(Y,"K"),n(Y,"oe"),s(Y,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const o=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:z,targetOptions:{...e},env:definedEnv(process.env)});o.kind==="attached"?Y(o.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${o.reason}`),b())}else b();try{for(;;)yield await V()}finally{w(),S(),ce()}}i(serveExecutor,"serveExecutor"),n(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
|
1
|
+
"use strict";var Ee=Object.defineProperty;var c=(e,r)=>Ee(e,"name",{value:r,configurable:!0});var me=Object.defineProperty,i=c((e,r)=>me(e,"name",{value:r,configurable:!0}),"i"),pe=Object.defineProperty,s=i((e,r)=>pe(e,"name",{value:r,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_host_1=require("../../devserver-host"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry"),config_files_1=require("../../config-files"),signature_1=require("../../shared-build/signature");function definedEnv(e){const r={};for(const[C,n]of Object.entries(e))typeof n=="string"&&(r[C]=n);return r}c(definedEnv,"definedEnv"),i(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const C=r.projectsConfigurations.projects[r.projectName],n=r.root,P=path.join(n,C.root),ee=e.outputPath?path.join(n,e.outputPath):path.join(n,"dist",C.root),re=e.configEnv||"development",q=path.join(n,"config",`config.${re}.yaml`),F=3050;let p=F,l=r.projectName,O={};fs.existsSync(q)?(O=yaml.load(fs.readFileSync(q,"utf8")),p=Number(O.port)||p,l=O.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${q} \u2014 public devserver port defaults to ${F}. If another service also lacks its config, both bind ${F} \u2192 EADDRINUSE. Add config/config.${re}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const M=e.servePrefix??"",N=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),ce=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),g=e.logFile?path.resolve(n,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(g){fs.mkdirSync(path.dirname(g),{recursive:!0}),fs.writeFileSync(g,"",{mode:384});try{fs.chmodSync(g,384)}catch{}}let U,G;e.gateway?.middleware&&(U=path.resolve(P,e.gateway.middleware),G=JSON.stringify(O));const ae=e.entryFile||"./src/deployments/service/main.ts",le=e.tsConfigFile||"./tsconfig.app.json",ue=path.resolve(P,ae),oe=path.resolve(P,le),de=(e.workers||[]).map(o=>({entryName:o.name,entryPath:path.resolve(P,o.entryPath)})),y={appName:r.projectName,appRoot:P,outputDir:ee,main:ue,tsConfig:oe,assets:e.assets||[],additionalEntryPoints:de,port:p,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:n,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let u=null,B=!1,W=!1,J=!1,v=!1;const te=path.join(__dirname,"../../main.devserver.js"),se=path.join(__dirname,"../../main.devserver.ts"),V=!fs.existsSync(te)&&fs.existsSync(se),k=V?se:te,ve=path.join(ee,"main.js");function R(){u&&!u.killed&&(u.kill("SIGTERM"),u=null)}c(R,"B"),i(R,"K"),s(R,"cleanup");function $(){v=!0,R()}c($,"O"),i($,"U"),s($,"stop");const fe=(0,stop_on_signal_1.stopOnSignal)({stop:$,close:s(()=>j(),"close")});process.on("exit",$);function S(){if(v||B)return;if(!fs.existsSync(k)){console.error(`[serve] devserver script not found at ${k}`);return}const o=(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(p),SERVICE_NAME:l,SERVE_PREFIX:M,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(n,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:ve,...U&&{GATEWAY_MIDDLEWARE:U},...G&&{GATEWAY_CONFIG:G},...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)},...N&&{LENSMCP_DEBUG_PORT:String(N)},...g&&{LENSMCP_LOG_FILE:g},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:J?"1":"0"});if((0,devserver_host_1.devserverInProcessOffered)()&&(!V||require.extensions[".ts"])){B=!0;for(const[d,h]of Object.entries(o))h!==void 0&&(process.env[d]=h);(0,devserver_host_1.markDevserverInProcess)(),require(k),L("in this process");return}const f=["--enable-source-maps"];V&&f.unshift("--require","@swc-node/register");const a=(0,node_child_process_1.fork)(k,{cwd:n,env:o,stdio:["inherit","inherit","inherit","ipc"],execArgv:f,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(n),l,"dev"))});u=a,a.on("exit",(d,h)=>{u===a&&(u=null),!(h||d===0||v)&&(console.error(`[serve] devserver exited unexpectedly (code=${d}, signal=${h}) \u2014 respawning in 1.5s`),setTimeout(()=>{!v&&!u&&W&&S()},1500).unref?.())}),L()}c(S,"S"),i(S,"C"),s(S,"startDevServer");function L(o){console.log(`[serve] Devserver started on port ${p} (service: ${l}${M?`, prefix: /${M}`:""}${o?`, ${o}`:""})`),N?(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.`),ce?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${N}+slot (workers ${N}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${N}. 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`),g&&console.log(`[serve] Logs teed to ${g} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(L,"G"),i(L,"Z"),s(L,"announceDevServer");function ne(){if(!u||u.killed){console.log("[serve] Devserver not running, restarting..."),S();return}fetch(`http://localhost:${p}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(o=>{console.warn("[serve] Failed to notify devserver, restarting...",o.message),R(),S()})}c(ne,"ve"),i(ne,"he"),s(ne,"_notifyDevServerReload");let D=null;const Y=[];function E(o){if(D){const f=D;D=null,f(o)}else Y.push(o)}c(E,"_"),i(E,"w"),s(E,"pushResult");function z(){return Y.length>0?Promise.resolve(Y.shift()):new Promise(o=>{D=o})}c(z,"re"),i(z,"le"),s(z,"waitForNext");let j=s(()=>{},"closeBuild");function T(){return!W||!u&&!B?(W=!0,S(),!0):!1}c(T,"V"),i(T,"Q"),s(T,"builtOk");function x(){try{return JSON.stringify((0,signature_1.transpileCompilerOptions)(y.tsConfig,n))}catch{return}}c(x,"H"),i(x,"ee"),s(x,"compileShape");function b(){if(v)return;const{createDevWebpackConfig:o}=require("../../create-webpack-dev"),f=require("webpack"),a=y.webpackConfigPath?path.resolve(y.appRoot,y.webpackConfigPath):void 0;if(a)try{delete require.cache[require.resolve(a)]}catch{}const d=o(y),{watch:h,...t}=d,X=(0,config_files_1.configFilesOf)(y),K=x();let Z;t.plugins=[...t.plugins??[],new config_files_1.ConfigFilesPlugin({files:s(()=>X,"files"),changed:s(w=>{if(!a||!w.includes(a)){const m=x();if(m===void 0||m===K)return}Z=w.map(m=>path.relative(n,m)).sort().join(", ")},"changed")})];const A=f(t);J=(0,dev_devtool_1.bundleCarriesSourceMaps)(A.options.devtool);let _;const I=(0,compile_retry_1.createFailureRetry)(()=>{v||!_||(console.log(`[serve] ${r.projectName}: compiling again after the failure`),_.invalidate())}),ie=s((w,m)=>{if(Z!==void 0&&_&&!v){console.log(`[serve] ${r.projectName}: ${Z} changed what it compiles with \u2014 compiling again with a new compiler`),I.dispose();const Q=_;_=void 0,setImmediate(()=>Q.close(()=>b()));return}if(w){if(console.error("[serve] Webpack error:",w),_&&!v){const{delayMs:Q,retry:ge}=I.failed();console.log(`[serve] ${r.projectName}: compiling again in ${Math.round(Q/1e3)} s (retry ${ge}) \u2014 webpack does not after a failure like this`)}E({success:!1});return}I.succeeded();const he=m.hasErrors();if(console.info(m.toString({colors:!0,chunks:!1,assets:!0})),he){E({success:!1});return}T(),E({success:!0,baseUrl:`http://localhost:${p}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),A.run((w,m)=>{ie(w,m),A.close(()=>{})});return}_=A.watch(d.watchOptions||{},ie),j=s(()=>{I.dispose(),_?.close(()=>{})},"closeBuild")}c(b,"P"),i(b,"$"),s(b,"compileOnItsOwn");function H(o){const f=o.accepted.projects.filter(t=>t!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${o.accepted.group.slice(0,8)}${f.length?` with ${f.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(p,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),d=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:oe,workspaceRoot:n,memoryLimit:e.memoryLimit||8192});let h;j=s(()=>{o.close(),d.dispose()},"closeBuild"),o.listen(t=>{if(!v)switch(t.type){case"watch-run":a.watchRun(t.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${t.message}`),a.failed(`the shared compiler failed: ${t.message}`),E({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${t.reason}`),j(),b();return;case"built":{if(console.info(t.text),J=t.sourceMaps,d.built(t),!t.ok){a.failed(),E({success:!1});return}const X=t.hash!==h;h=t.hash;const K=T();a.built(X&&!K),E({success:!0,baseUrl:`http://localhost:${p}`});return}default:return}})}if(c(H,"te"),i(H,"ue"),s(H,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const o=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:y,targetOptions:{...e},env:definedEnv(process.env)});o.kind==="attached"?H(o.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${o.reason}`),b())}else b();try{for(;;)yield await z()}finally{$(),j(),fe()}}c(serveExecutor,"serveExecutor"),i(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
package/main.devserver.js
CHANGED
|
@@ -9,16 +9,16 @@
|
|
|
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"),u(importFresh,"importFresh"),a(importFresh,"importFresh");function debounce(d,_){let g=null;return(...y)=>{g&&clearTimeout(g),g=setTimeout(()=>d(...y),_)}}if(h(debounce,"debounce"),u(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const d=a(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(H){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,H.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const y=inspector.url();y&&tell(process,{type:"inspector-url",url:y})},"openInspector"),_=process.env.CHILD_DEBUG_PORT;if(_!==void 0&&_!==""){const g=Number(_);Number.isInteger(g)&&(g===0||g>1024)&&d(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,y=!1,H,ce=!1;const ze=(0,hot_swap_registries_1.registryPruningEnabled)(),we=a((r,c)=>{const l=(0,hot_swap_registries_1.describePrune)(r,c);l&&console.log(l)},"logPrune");let Y={app:null,handler:a((r,c)=>{c.statusCode=503,c.end("starting")},"handler"),inflight:0};const me=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),C=a(r=>{const c=globalThis.__lensmcpActiveWork;if(typeof c!="function")return{count:0};try{const l=c(r);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),Ie=a(r=>{const c=globalThis.__lensmcpStopWorkIntake;if(typeof c=="function")try{return c(r)}catch{return}},"stopWorkIntake"),ye=a(r=>{const c=globalThis.__lensmcpWorkIntakeReport;if(!(r===void 0||typeof c!="function"))try{return c(r)??void 0}catch{return}},"workIntakeReport"),Re=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),q=new Set,ne=(()=>{const r=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(r)&&r>=0?r:3e4})(),X=a(r=>{if(!r.abandoned?.size)return 0;const c=Date.now();for(const[l,f]of r.abandoned)(l.writableEnded||c-f>=ne)&&r.abandoned.delete(l);return r.abandoned.size},"abandonedRunning"),le=a(r=>{let c=0;for(const l of r.open??[])l.headersSent||(c+=1);return{unanswered:c+X(r),streams:Math.max(0,r.inflight-c),work:C(r.workGen).count}},"owedBy"),de=a((r,c,l=`${c}s`)=>`${r} ${r===1?c:l}`,"plural");async function Ce(){if(ne<=0)return;const r=Date.now();let c=!1;for(;!(q.size<Re);){const l=q.values().next().value;if(!l||l.closed)break;const f=le(l);if(f.unanswered===0&&f.work===0)break;const v=(l.drainingSince??r)+ne-Date.now();if(v<=0)break;c||(c=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??r))/100)/10}s ago is still `+[f.unanswered?`answering ${de(f.unanswered,"request")}`:"",f.work?`running ${de(f.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${Re} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(v/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(S=>{setTimeout(S,Math.min(100,v)).unref?.()})}c&&console.log(`[child] swap resumed after ${Date.now()-r}ms`)}h(Ce,"We"),u(Ce,"He"),a(Ce,"holdForOwedWork");const M=(0,swap_recycle_1.swapRecyclePolicy)();let Ee,pe=0;const Qe=(()=>{const r=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(r)&&r>=0?r:3500})();let w=!1,P=!1;const k=(0,pod_memory_1.podMemoryPolicy)();let V=0;const x=new Set;let ve=!1;const j=a(()=>{if(!ve)return;let r=0;for(const c of x)c.deref()===void 0?x.delete(c):r+=1;return r},"liveContainers"),b=(0,pod_memory_1.createPodMemoryMonitor)({policy:k,state:a(()=>({gen:Y.gen??0,swaps:pe,...Ee===void 0?{}:{baseline:Ee},busy:y||ce||z.size>0,lastBusyAt:G}),"state"),send:a(r=>{tell(process,r)},"send"),containers:j,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:a(()=>ke(),"onRetained"),gc:a(()=>(0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc),"gc")}),U=a(()=>((0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc)&&b.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),B=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,c=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:a(f=>l.has(f)||f.startsWith(r)&&!f.slice(r.length).includes(c)&&!f.slice(r.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let ue=!1;function Ae(){if(!(!B||ue))try{const r=B();r.errors>0&&console.log(`[child] read the stack of ${r.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(r){console.error("[child] could not release library error frames:",r),ue=!0}}h(Ae,"Te"),u(Ae,"De"),a(Ae,"releaseLibraryErrorFrames");const z=new Map;let G=Date.now();const Be=a(()=>M.settleMs<=0||!y&&!ce&&z.size===0&&Date.now()-G>=M.settleMs,"settled");let K;function se(r){if(!(!M.enabled||w||P)){if(M.settleMs<=0){J(r);return}K&&clearTimeout(K),K=setTimeout(()=>{K=void 0,Be()?J(r):se(r)},M.settleMs),K.unref?.()}}h(se,"ae"),u(se,"he"),a(se,"recycleCheckWhenSettled");const Ne=a(()=>{if(M.stuckCloseMs<=0)return 0;const r=Date.now();let c=0;for(const l of z.values())r-l>=M.stuckCloseMs&&(c+=1);return c},"stuckCloses");function J(r){if(!M.enabled||w||P||!process.send)return;const c=v8.getHeapStatistics().heap_size_limit,l=a(v=>{const S=b.oldestRetained();return{swaps:pe,usedBytes:v,limitBytes:c,stuckCloses:Ne(),settled:Be(),...Ee===void 0?{}:{baselineBytes:Ee},...S?{retained:{generation:S.gen,ageMs:Date.now()-S.closedAt,gcs:S.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(M,l(process.memoryUsage().heapUsed)))return;const f=(0,swap_recycle_1.recycleReason)(M,l(U()));f&&(w=!0,console.log(`[child] asking for a fresh pod ${r} \u2014 ${f}. 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}))}h(J,"X"),u(J,"Q"),a(J,"considerRecycle");let Se;function ke(){if(!M.enabled||M.retainedMs<=0||w||P)return;const r=b.oldestRetained();if(!r)return;const c=r.closedAt+M.retainedMs-Date.now();if(c<=0){J("with a closed generation still in memory");return}Se||(Se=setTimeout(()=>{Se=void 0,b.measure("retention-check").catch(()=>{})},c),Se.unref?.())}h(ke,"Oe"),u(ke,"je"),a(ke,"retainedRecycleCheck");function ie(r,c){const l={};c&&(l.module=c),r&&(l.app=r);try{const f=r?.app;if(f&&typeof f=="object"&&(l.nestApp=f,f.container&&typeof f.container=="object")){l.container=f.container;const v=rootModuleOf(f.container);v&&(l.rootModule=v)}}catch{}return l}h(ie,"Ee"),u(ie,"Ne"),a(ie,"generationTargets");async function Q(r,c){if(r.closed||(r.closed=!0,q.delete(r),c&&console.error(`[child] ${c}`),!r.app))return;z.set(r,Date.now());const l=M.enabled&&M.stuckCloseMs>0?setTimeout(()=>{z.has(r)&&(console.error(`[child] a previous app has been closing for ${Math.round(M.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.`),J("with a previous app stuck in close()"))},M.stuckCloseMs):void 0;l?.unref?.();const f=r.app;try{await r.app.close()}catch(v){console.error("[child] error closing a previous app:",v)}finally{z.delete(r),G=Date.now(),l&&clearTimeout(l),r.app=null,r.abandoned=void 0,r.handler=(v,S)=>{S.statusCode=503,S.end("closed")},r.gen!==void 0&&b.closed(r.gen,pe,ie(f,r.module)),r.module=void 0}if(r.superseded){try{we((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(v){console.error("[child] could not release the previous app's class registrations:",v)}r.superseded=void 0}se("after closing the previous app")}h(Q,"ce"),u(Q,"me"),a(Q,"closeGeneration");async function Z(r){const c=Date.now();r.drainingSince=c;const l=a(()=>({inflight:r.inflight+X(r),work:C(r.workGen).count}),"busy"),f=l();if(f.inflight>0||f.work>0){for(;q.size>=Re;){const D=q.values().next().value;if(!D)break;const O=le(D),te=Math.round((Date.now()-(D.drainingSince??c))/1e3);if(O.unanswered===0&&O.streams===0&&O.work===0){await Q(D);continue}await Q(D,`closing a still-draining generation early \u2014 ${q.size} were pending and the cap is ${Re} (each holds a whole app: DI container, DB pool, queue workers). `+(O.unanswered>0||O.work>0?`It was replaced ${te}s ago and had still not answered ${de(O.unanswered,"request")}`+(O.work?` or finished ${de(O.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${ne}ms) to wait longer for them. `:`It held only ${de(O.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}q.add(r)}for(;me>0&&Date.now()-c<me;){if(r.closed)return;const D=l();if(D.inflight===0&&D.work===0)break;await new Promise(O=>{setTimeout(O,250).unref?.()})}if(r.closed)return;const v=l(),S=ye(r.workGen);v.inflight>0||v.work>0?console.error(`[child] swap drain expired after ${Date.now()-c}ms \u2014 closing the previous app with ${v.inflight} in-flight request(s) and ${v.work} background job(s) still running. `+(S?`Previous app still processing ${S.running} queue job(s); ${S.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${me}ms) if this is legitimate work.`):(Date.now()-c>250||S?.runningAtStop||S?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-c}ms \u2014 closing it`+(S?` (it finished ${S.finishedAfterStop} queue job(s) after the swap; ${S.takenAfterStop} were taken after the swap)`:"")),await Q(r)}h(Z,"le"),u(Z,"ye"),a(Z,"drainThenClose");async function ee(){if(y)return ce=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;y=!0,G=Date.now(),await Ce(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(R,re)=>{console.error(`[child] uncaughtException (${re}):`,R)}),process.on("unhandledRejection",R=>{const re=R instanceof Error?R.stack||R.message:String(R);re.includes("FronteggContextScope")||re.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",R)});let r;if(ze)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=void 0}const c=global.__lensmcpBeginWorkGeneration,l=typeof c=="function"?c():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(R){if(!(R instanceof SyntaxError))throw R;console.warn(`[child] bundle was unreadable mid-write (${R.message}) \u2014 retrying the swap once`),await new Promise(re=>setTimeout(re,400)),await importFresh(BUNDLE_PATH)}}catch(R){throw y=!1,G=Date.now(),R}if(typeof global.createChildApp!="function")throw y=!1,G=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 f;if(r)try{f=(0,hot_swap_registries_1.supersededSince)(r),we((0,hot_swap_registries_1.pruneRegistries)(f,"build"),"at the swap")}catch(R){console.error("[child] could not release superseded class registrations:",R)}let v;try{v=await global.createChildApp()}catch(R){throw y=!1,G=Date.now(),takeImportedModule(),R}const S=Y;f?.size&&(S.app?S.superseded=f:we((0,hot_swap_registries_1.pruneRegistries)(f,"serve"),"at the swap")),g=v;const D=takeImportedModule();Y={app:v,handler:v.handler,inflight:0,gen:++V,...D?{module:D}:{},...l===void 0?{}:{workGen:l}};const O=ie(v,void 0).container;O&&(x.add(new WeakRef(O)),ve=!0);const te=S.app&&l!==void 0?Ie(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(te?.workers?`; its ${te.workers} queue worker(s) stopped taking new jobs, previous app still processing ${te.runningAtStop} job(s)`:"")),Ae(),S.app&&(pe+=1,Z(S).catch(R=>console.error("[child] background close error:",R)),setTimeout(()=>J("30s after a hot swap"),3e4).unref?.(),b.swapped(S.gen,()=>S.closed===!0&&!z.has(S))),y=!1,G=Date.now(),ce?(ce=!1,ee()):v}h(ee,"de"),u(ee,"Ee"),a(ee,"swapNow");const $e=debounce(async()=>{try{console.log("[child] /webpack/reload"),await ee()}catch(r){console.error("[child] reload failed:",r)}},250),he=http.createServer((r,c)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){c.writeHead(200,{"content-type":"application/json"}),c.end(JSON.stringify({ok:!0})),$e();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const f=r.headers["x-internal-token"],v=process.env.LENSMCP_SERVICE_KEY;if(v&&f!==v){c.writeHead(404,{"content-type":"text/plain"}),c.end("not found");return}const S=C();c.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),c.end(JSON.stringify({ok:!0,pid:process.pid,swapping:y,inflight:Y.inflight,oldestInflightAt:Y.oldestAt,activeWork:S.count,oldestWorkAt:S.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...H?{watcher:H}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=Y;l.inflight+=1,(l.open??=new Set).add(c),l.oldestAt===void 0&&(l.oldestAt=Date.now()),c.on("close",()=>{l.inflight-=1,l.open?.delete(c),ne>0&&!l.closed&&!c.headersSent&&!c.writableEnded&&((l.abandoned??=new Map).set(c,Date.now()),l.abandoned.size>256&&X(l)),l.inflight===0&&(l.oldestAt=void 0),b.activity()}),l.handler(r,c)});he.keepAliveTimeout=0;const $=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock($),process.exit(0)});try{const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const l=(0,schema_first_instance_1.prewarmZodFor)(r);l?.created&&console.log(`[child] zod${l.version?` ${l.version}`:""}: built ${l.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 l=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(r);l?.patched&&console.log(`[child] ioredis${l.versions.length?` ${l.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 ee(),Ee=(await b.booted()).live,console.log(`[child] Using bundle: ${r}`),claimSock($);let c=!1;he.on("error",l=>{if(c){console.error("[child] server error:",l);return}console.error(`[child] could not listen on ${$} (${l.code??l.message}) \u2014 exiting; the devserver starts another pod`),tell(process,{type:"boot-error",error:`could not listen on ${$}: ${String(l?.stack||l)}`}),cleanupSock($),process.exitCode=1,setTimeout(()=>process.exit(1),100)}),he.listen($,()=>{c=!0,console.log(`[child] Listening on ${$}`),tell(process,{type:"ready",socketPath:$})})}catch(r){console.error("[child] Startup error:",r),tell(process,{type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const c=Number(r.port);d(Number.isInteger(c)&&c>1024?c:0);return}if(r.type==="watcher"){const c=r.testimony;c&&typeof c=="object"&&(H=c);return}if(r.type==="heap-snapshot"){be(r);return}if(r.type==="recycle-declined"){w=!1;return}if(r.type==="retire"){L();return}if(r.type==="reload"){if(P)return;try{const c=Date.now();await ee(),tell(process,{type:"reloaded",socketPath:$,swap:{generation:V,swaps:pe,swapMs:Date.now()-c,draining:q.size}})}catch(c){console.error("[child] reload error:",c),tell(process,{type:"reload-error",error:String(c?.stack||c)})}}}});let fe=!1;async function be(r){const c=a(l=>{tell(process,{type:"heap-snapshot-result",id:r.id,...l})},"reply");if(fe)return c({ok:!1,refused:"a snapshot of this pod is already being written"});fe=!0;try{const l=U(),f=b.tracker.views().map(O=>O.gen),v=(0,heap_snapshot_1.heapSnapshotPolicy)();r.maxHeapBytes&&(v.maxHeapBytes=r.maxHeapBytes);const S=(0,heap_snapshot_1.heapSnapshotRefusal)(v,{live:l,...r.available?{available:r.available}:{}});if(S)return c({ok:!1,refused:S,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const D=await(0,heap_snapshot_1.writeHeapSnapshot)(r.file);c({ok:!0,file:r.file,bytes:D.bytes,ms:D.ms,live:l,gen:Y.gen??0,closedAlive:f})}catch(l){c({ok:!1,error:l?.message??String(l)})}finally{fe=!1}}h(be,"Me"),u(be,"Ye"),a(be,"takeHeapSnapshot");async function L(r="a fresh pod has taken over"){if(P)return;P=!0,b.stop(),console.log(`[child] retiring \u2014 ${r}; ${path.basename($,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const c=Ie(Number.POSITIVE_INFINITY);c?.workers&&console.log(`[child] its ${c.workers} queue worker(s) stopped taking new jobs, still processing ${c.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath($),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(f=>{setTimeout(f,Qe).unref?.()}),cleanupSock($),he.close();const l=Date.now();for(;Date.now()-l<me;){he.closeIdleConnections?.();let f=0;for(const v of q)f+=le(v).unanswered;if(Y.inflight+X(Y)===0&&f===0&&C().count===0)break;await new Promise(v=>{setTimeout(v,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(f){console.error("[child] close error while retiring:",f)}process.exit(0)}h(L,"q"),u(L,"B"),a(L,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const _e=a(async r=>{console.log(`[child ${r}] shutting down\u2026`);const c=g?.close().catch(f=>console.error("[child] close error:",f)),l=new Promise(f=>he.close(()=>f()));cleanupSock($),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([c,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{if(r==="SIGTERM"&&!P&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){L("this generation was handed over to a newer one");return}inspector?.close?.(),_e(r)})})})()}else{let d=u(function(w){X.push(w),X.length>ne&&X.splice(0,X.length-ne),Re?.write((0,inspector_secret_1.redactInspectorUrls)(w.replace(q,"")));for(const P of le)try{const k=P.plain?w.replace(q,""):w;P.res.write(P.redact?(0,inspector_secret_1.redactInspectorUrls)(k):k)}catch{le.delete(P)}},"we");a(d,"emitLogLine");const _=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",y=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),H=a(w=>({project:g,service:SERVICE_NAME||g,pod:w.id,pid:w.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),ce=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],ze="\x1B[0m",we=Math.max(1,Number(process.env.CHILD_COUNT||1)),Y=LENSMCP_WS_KEY||path.basename(process.cwd()),me=a(w=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(Y,SERVICE_NAME||"service",w)),"execPathFor");let C=(()=>{const w=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(w)&&w>1024?w:0})();const Ie=20;C&&(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 (${C}+slot, workers ${C}+${Ie}+i). Any account on this host can connect to 127.0.0.1:${C} 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 ye=process.env.LENSMCP_LOG_FILE||"",Re=ye?(()=>{try{fs.mkdirSync(path.dirname(ye),{recursive:!0});const w=fs.createWriteStream(ye,{flags:"a",mode:384});try{fs.chmodSync(ye,384)}catch{}return w}catch(w){return console.warn(`[parent] cannot open log file ${ye}:`,w.message),null}})():null,q=/\x1b\[[0-9;]*m/g,ne=2e3,X=[],le=new Set,de=(0,devserver_transport_1.gatewayManaged)();for(const w of["log","info","warn","error"]){const P=console[w].bind(console),k=w==="log"||w==="info"?process.stdout:process.stderr;console[w]=(...V)=>{P(...V.map(x=>typeof x=="string"?(0,inspector_secret_1.forStream)(k,de,x):x)),d(V.map(x=>typeof x=="string"?x:util.inspect(x)).join(" ")+`
|
|
13
|
-
`)}}const Ce=a(w=>ce[(w-1)%ce.length],"colorFor"),M=a(w=>{if(
|
|
14
|
-
`+String(j),"stack")}}),x=a((j,b)=>{const U=j==="stdout"?process.stdout:process.stderr;try{k?.(b)}catch{}const B=
|
|
15
|
-
`;U.write((0,inspector_secret_1.forStream)(U,de,`${P}${B}`)),d(`${P}${B}`)},"write"),ve=a((j,b)=>{if(!j)return;const U=readline.createInterface({input:j});U.on("line",B=>x(b,B)),U.on("close",()=>{})},"attach");ve(w.stdout,"stdout"),ve(w.stderr,"stderr")},"wireProcLogging"),Qe=a(w=>pe(w.proc,M(w.id),P=>w.tail.push(P)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const w=httpProxy.createProxyServer({});let P=Number(process.env.PORT)||0;P||(P=9090);const k=[],V=new Set;function x(){let e=0;for(;V.has(e);)e++;return V.add(e),e}h(x,"x"),u(x,"D"),a(x,"allocDebugSlot");let ve=1,j=0,b=!1,U=we,B=[],ue=0;const Ae=(0,swap_mode_1.swapMode)(),z=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),G=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Be=a(e=>e.booting&&!G(e)&&Date.now()-e.spawnedAt<z,"stillBooting");function K(){return k.filter(e=>e.healthy&&typeof e.socketPath=="string")}h(K,"Y"),u(K,"J"),a(K,"healthyChildren");let se,Ne=0,J;const Se={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function ke(e){const t=(0,service_build_events_1.buildAccountOf)(e.building);t&&t.startedAt!==Ne&&(Ne=t.startedAt,y.publish((0,service_build_events_1.buildServiceBuildEvent)(Se,"started",t,{root:process.cwd()})));const s=(0,service_build_events_1.buildAccountOf)(e.failed);s&&(Ne=0,y.publish((0,service_build_events_1.buildServiceBuildEvent)(Se,"failed",s,{root:process.cwd()})))}h(ke,"Oe"),u(ke,"je"),a(ke,"recordBuildProgress");function ie(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){ke(t),"failed"in t&&delete t.failed,se=t;for(const s of k)tell(s.proc,{type:"watcher",testimony:t})}}h(ie,"Ee"),u(ie,"Ne"),a(ie,"recordWatcherTestimony");function Q(e,t=256*1024){return new Promise(s=>{let n="",i=!1;e.on("data",o=>{i||(n+=o,n.length>t&&(i=!0))}),e.on("end",()=>{if(i||!n)return s(void 0);try{s(JSON.parse(n))}catch{s(void 0)}}),e.on("error",()=>s(void 0))})}h(Q,"ce"),u(Q,"me"),a(Q,"readJsonBody");function Z(){const e=K();if(e.length===0)return null;const t=j%e.length;return j=(j+1)%e.length,e[t]}h(Z,"le"),u(Z,"ye"),a(Z,"pickChild");function ee(e,t){const s=`Child#${e.id}`,n=SERVICE_NAME||g,i=Date.now();if(t.type==="pod-memory"){const o=t.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(s,o)}`),y.publish((0,pod_memory_events_1.buildPodMemoryEvent)(H(e),o))}else if(t.type==="pod-memory-retained"){const o=t.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:i}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(s,n,o)}`),y.publish((0,pod_memory_events_1.buildPodRetainedEvent)(H(e),o,i))}else{const o=t.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(s,n,o)}`),y.publish((0,pod_memory_events_1.buildPodReleasedEvent)(H(e),o,i))}}h(ee,"de"),u(ee,"Ee"),a(ee,"recordPodMemory");const $e=new Map;let he=0;async function $(){const e=ve++,t=childSockPath(e);cleanupSock(t);const s=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&s.unshift("--require","@swc-node/register"),s.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const n=C?x():-1,i=(0,node_child_process_1.fork)(__filename,{env:(0,child_process_env_1.childProcessEnv)({...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...n>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(C,n))}}),stdio:["inherit","pipe","pipe","ipc"],execArgv:s,execPath:me("pod")}),o={id:e,proc:i,healthy:!1,lastError:null,debugSlot:n,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ue,memory:{history:[],retained:new Map}};return i.on("error",E=>console.error(`[parent] Child#${e}: ${E.message}`)),i.on("message",E=>{if(!E||typeof E!="object")return;const p=E;if(p.type==="ready")o.socketPath=String(p.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),se&&tell(i,{type:"watcher",testimony:se}),ue!==o.bootReloadSeq&&(Ae==="process"?R():tell(i,{type:"reload"}));else if(p.type==="recycle-request")S(o,String(p.reason||"it has outgrown its bound"));else if(p.type==="inspector-url")o.inspectorUrl=String(p.url),console.log(`[parent] Child#${o.id} Debugger listening on ${p.url}`);else if(p.type==="reloaded"){if(L.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(p.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const m=p.swap;m&&typeof m.generation=="number"&&typeof m.swaps=="number"&&y.publish((0,service_build_events_1.buildServiceSwapEvent)(H(o),{generation:m.generation,swaps:m.swaps,swapMs:typeof m.swapMs=="number"?m.swapMs:0,draining:typeof m.draining=="number"?m.draining:0},J,{root:process.cwd()}))}else p.type==="reload-error"?(o.lastError=String(p.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):p.type==="boot-error"?(o.lastError=String(p.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):p.type==="pod-memory"||p.type==="pod-memory-retained"||p.type==="pod-memory-released"?ee(o,p):p.type==="heap-snapshot-result"&&$e.get(Number(p.id))?.(p)}),i.on("exit",(E,p)=>{o.booting=!1;for(const A of o.memory.retained.values()){const I={gen:A.gen,closedAt:A.closedAt,ageMs:Date.now()-A.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,I)}`),y.publish((0,pod_memory_events_1.buildPodReleasedEvent)(H(o),I,Date.now()))}o.memory.retained.clear(),o.memory.latest&&y.publish((0,pod_memory_events_1.buildPodExitedEvent)(H(o),{code:E,signal:p},Date.now())),o.debugSlot>=0&&(V.delete(o.debugSlot),o.debugSlot=-1);const m=p==="SIGINT"||p==="SIGTERM"||E===0;if(m?console.log(`[parent] Child#${o.id} exited cleanly (code=${E}, signal=${p??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${E}, signal=${p??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,E,p,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${E}, signal=${p??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!m&&!b){const A=k.indexOf(o);A>=0&&k.splice(A,1);const I=Date.now();if(B=B.filter(T=>I-T<3e4),B.length>=5){console.error(`[parent] crash-loop detected (${B.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}B.push(I),setTimeout(()=>{const T=k.filter(oe=>oe.proc.killed===!1).length;!b&&T<U&&(console.log(`[parent] respawning a crashed pod (${T}/${U} live)`),$().catch(()=>{}))},1500).unref?.()}}),k.push(o),Qe(o),o}h($,"N"),u($,"N"),a($,"spawnChild");async function fe(e){U=Math.max(U,e);const t=k.filter(n=>n.proc.killed===!1),s=e-t.length;for(let n=0;n<s;n++)await $()}h(fe,"ve"),u(fe,"be"),a(fe,"ensurePoolSize");async function be(){const e=k.filter(t=>(!t.healthy||typeof t.socketPath!="string")&&!Be(t));await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const s=k.indexOf(t);s>=0&&k.splice(s,1),await $()}))}h(be,"Me"),u(be,"Ye"),a(be,"respawnUnhealthy");const L=new Set;let _e=!1,r=0;const c=3e4,l=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function f(e,t={}){const s=Date.now(),n=await $(),i=Date.now()+z;for(;!n.healthy&&!n.lastError&&!G(n)&&Date.now()<i;){if(t.superseded?.()){const o=k.indexOf(n);o>=0&&k.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`a newer build landed while Child#${n.id} booted`,superseded:!0}}await new Promise(o=>{setTimeout(o,100).unref?.()})}if(b)return{why:"shutting down"};if(!n.healthy){const o=k.indexOf(n);o>=0&&k.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`its successor Child#${n.id} did not come up (${n.lastError??"no answer in time"})`}}return v(e),{fresh:n,readyMs:Date.now()-s}}h(f,"h"),u(f,"_"),a(f,"replacePod");function v(e){const t=k.indexOf(e);if(t>=0&&k.splice(t,1),e.healthy=!1,G(e))return;L.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const s=setTimeout(()=>{if(!G(e)){console.error(`[parent] Child#${e.id} was still running ${l}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},l);if(s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),L.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}}h(v,"E"),u(v,"m"),a(v,"retirePod");async function S(e,t){const s=a(i=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${t}) \u2014 not now: ${i}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(b||!k.includes(e))return;if(_e)return s("another pod is already being replaced");const n=Date.now()-r;if(n<c)return s(`a pod was replaced ${Math.round(n/1e3)}s ago`);_e=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${t}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const i=await f(e);if("why"in i){i.why!=="shutting down"&&s(i.why);return}r=Date.now(),console.log(`[parent] Child#${i.fresh.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${i.fresh.proc.pid})`)}catch(i){s(`could not start a successor: ${i?.message??String(i)}`)}finally{_e=!1}}h(S,"v"),u(S,"y"),a(S,"recyclePod");let D,O=!1,te=0;function R(){if(D){O=!0;return}D=(async()=>{do{O=!1;const e=ue;for(;_e&&!b;)await new Promise(s=>{setTimeout(s,100).unref?.()});let t=!1;for(const s of K().filter(n=>n.bootReloadSeq<e)){if(b)break;if(!(!k.includes(s)||!s.healthy)){_e=!0;try{re(),console.log(`[parent] A new build \u2014 starting a fresh pod for it; Child#${s.id} keeps serving the previous build until it is ready.`);const n=await f(s,{superseded:a(()=>ue!==e&&(0,swap_mode_1.abandonSuperseded)(te),"superseded")});if("why"in n){if(n.superseded){te+=1,console.log(`[parent] ${n.why} \u2014 abandoned; one fresh pod for the newest build instead (${te} in a row). Child#${s.id} keeps serving.`),t=!0;break}n.why!=="shutting down"&&console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 ${n.why}`);continue}te=0,console.log(`[parent] Child#${n.fresh.id} took over from Child#${s.id} with the new build (pid ${s.proc.pid} \u2192 ${n.fresh.proc.pid}, ready in ${(n.readyMs/1e3).toFixed(1)}s while Child#${s.id} served)`),y.publish((0,service_build_events_1.buildServiceSwapEvent)(H(n.fresh),{generation:1,swaps:0,swapMs:n.readyMs,draining:L.size,handoff:{fromPid:s.proc.pid??0}},J,{root:process.cwd()}))}catch(n){console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 could not start a successor: ${n?.message??String(n)}`)}finally{_e=!1}}}t&&(O=!0)}while(O&&!b)})().finally(()=>{D=void 0})}h(R,"P"),u(R,"A"),a(R,"handoffForReload");function re(){const e=(0,swap_mode_1.maxRetiring)(),t=[...L].filter(s=>!G(s));for(;t.length>=e;){const s=t.shift();console.error(`[parent] Child#${s.id} (pid ${s.proc.pid}) was still finishing its work when a newer build replaced its successor too \u2014 stopping it (LENSMCP_SWAP_MAX_RETIRING=${e})`);try{s.proc.kill("SIGTERM")}catch{}}}h(re,"ee"),u(re,"q"),a(re,"capRetiring");const W=[],je=new Map,Ke=a(e=>{let t=je.get(e);return t||je.set(e,t={crashes:0,restarts:0}),t},"watchOf"),Ze=(0,worker_supervisor_1.workerBackoff)(),Oe=a((e,t)=>({project:g,service:SERVICE_NAME||g,worker:e,pid:t,podGeneration:POD_GENERATION}),"workerWho"),it=[path.join(__dirname,"worker-guard.js"),path.join(__dirname,"worker-guard.ts")].find(e=>fs.existsSync(e));for(const e of WORKER_PROBLEMS)console.error(`[parent] ${e}`);function Ye(e,t){const s=e.name,n=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),i=path.join(n,`${s}.js`),o=Ke(s);if(!fs.existsSync(i))return console.error(`[parent] Worker "${s}" bundle not found: ${i}`),o.missing=i,y.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Oe(s,0),{ranMs:0,reason:`its bundle was not built (${path.basename(i)} is not in ${n})`})),null;o.missing=void 0;const E=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&E.unshift("--require","@swc-node/register"),it&&E.push("--require",it);try{E.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const p=inspectorPortFor(C||9229,Ie+Math.max(0,WORKER_NAMES.indexOf(s)));E.push(C?`--inspect=127.0.0.1:${p}`:`--inspect-port=127.0.0.1:${p}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const m=(0,node_child_process_1.fork)(i,{env:(0,worker_supervisor_1.workerEnv)(process.env,e),stdio:["inherit","pipe","pipe","ipc"],execArgv:E,execPath:me(`w-${s}`)}),A={name:s,spec:e,proc:m,debugPort:p,startedAt:Date.now(),tail:new exit_reason_1.TailBuffer,stopping:!1};W.push(A),m.on("error",T=>console.error(`[parent] Worker "${s}": ${T.message}`)),pe(m,`\x1B[35m[worker:${s}] \x1B[0m`,T=>{A.tail.push(T);const oe=/Debugger listening on (wss?:\/\/\S+)/.exec(T);oe&&(A.inspectorUrl=oe[1])});const I=setTimeout(()=>{if(!(Te(A)||A.stopping)){if(o.crashes>0){const T=Date.now()-A.startedAt;console.log(`[parent] Worker "${s}" (pid=${m.pid}) is up again \u2014 ${(0,worker_supervisor_1.spanOf)(T)} without a crash, after ${o.crashes} in a row`),y.publish((0,worker_supervisor_1.buildWorkerUpEvent)(Oe(s,m.pid??0),{ranMs:T,afterCrashes:o.crashes}))}o.crashes=0}},Ze.stableMs);return I.unref?.(),m.on("exit",(T,oe)=>{clearTimeout(I);const Pe=W.indexOf(A);Pe>=0&&W.splice(Pe,1),et(A,T,oe)}),console.log(`[parent] Worker "${s}" started (pid=${m.pid}, PORT=${(0,worker_supervisor_1.workerEnv)(process.env,e).PORT}${t==="respawn"?`, restart ${o.restarts}`:""})`),y.publish((0,worker_supervisor_1.buildWorkerStartedEvent)(Oe(s,m.pid??0),{why:t,port:e.port,restarts:o.restarts})),A}h(Ye,"ze"),u(Ye,"Ze"),a(Ye,"spawnWorkerProcess");function et(e,t,s){const{name:n}=e,i=e.proc.pid??0,o=Date.now()-e.startedAt,E=(0,worker_supervisor_1.workerExitVerdict)(t,s,e.stopping||b);if(E.kind==="stopped")return;const p=Ke(n);if(E.kind==="exited"){p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"exited"},console.warn(`[parent] Worker "${n}" (pid=${i}) exited on its own (code 0) after ${(0,worker_supervisor_1.spanOf)(o)} \u2014 not started again until the next build: a worker that ends is taken to have finished its work`),y.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Oe(n,i),{ranMs:o}));return}p.crashes+=1,p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"crashed"};const m=(0,worker_supervisor_1.respawnDelay)(p.crashes,Ze),A=p.crashes>=worker_supervisor_1.CRASH_LOOP_AFTER;console.error((0,exit_reason_1.crashReport)(`[parent] Worker "${n}" crashed (code=${t}, signal=${s??"none"}, pid=${i}, after ${(0,worker_supervisor_1.spanOf)(o)})${E.external?" \u2014 stopped by a signal from outside its devserver":""}; starting it again in ${(0,worker_supervisor_1.spanOf)(m)}${A?` \u2014 ${p.crashes} crashes in a row: it is crash-looping (the wait doubles, up to ${(0,worker_supervisor_1.spanOf)(Ze.maxMs)})`:""}`,t,s,e.tail.snapshot())),y.publish((0,worker_supervisor_1.buildWorkerCrashedEvent)(Oe(n,i),{code:t,signal:s,ranMs:o,crashes:p.crashes,restarts:p.restarts,nextInMs:m,external:E.external,lines:e.tail.snapshot()})),clearTimeout(p.pending),p.nextAt=Date.now()+m,p.pending=setTimeout(()=>{p.pending=void 0,p.nextAt=void 0,!(b||W.some(I=>I.name===n))&&(p.restarts+=1,xe(e.spec,"respawn"))},m),p.pending.unref?.()}h(et,"rt"),u(et,"ct"),a(et,"workerEnded");function xe(e,t){const s=Ke(e.name);s.waiting=void 0;const n=e.port>0?[...ae].find(o=>o.name===e.name&&!Te(o)):void 0;if(!n){Ye(e,t);return}console.log(`[parent] Worker "${e.name}" starts once its predecessor (pid=${n.proc.pid}) has finished its work \u2014 it holds port ${e.port}`);const i=a(()=>{s.waiting===i&&(s.waiting=void 0),!b&&!W.some(o=>o.name===e.name)&&Ye(e,t)},"go");s.waiting=i,n.proc.once("exit",i)}h(xe,"qe"),u(xe,"Ve"),a(xe,"startWorker");const at=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),ct=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),ae=new Set,Te=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function De(e,t){if(ae.delete(e),!Te(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${t} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}h(De,"Be"),u(De,"Je"),a(De,"killDrainingWorker");function Ve(e){e.stopping=!0,tell(e.proc,{type:worker_retire_1.RETIRE_MESSAGE});try{e.proc.kill("SIGTERM")}catch{}if(Te(e))return;const t=[...ae].filter(n=>n.name===e.name);for(;t.length>=ct;)De(t.shift(),`was still running when a later reload replaced its successor (cap ${ct})`);ae.add(e);const s=setTimeout(()=>De(e,`was still running ${at}ms after SIGTERM`),at);s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),ae.delete(e)})}h(Ve,"Qe"),u(Ve,"rt"),a(Ve,"retireWorker");function Ge(){for(const e of je.values())clearTimeout(e.pending),e.pending=void 0,e.nextAt=void 0,e.waiting=void 0}h(Ge,"je"),u(Ge,"qe"),a(Ge,"cancelWorkerStarts");function tt(){for(const e of[...W])Ve(e);W.length=0,Ge();for(const e of WORKER_SPECS)Ke(e.name).crashes=0,xe(e,"reload")}h(tt,"st"),u(tt,"lt"),a(tt,"restartWorkers");function rt(){const e=Date.now();return{workers:WORKER_SPECS.map(t=>{const s=W.find(o=>o.name===t.name),n=je.get(t.name),i=s?"running":n?.pending?(n.crashes??0)>=worker_supervisor_1.CRASH_LOOP_AFTER?"crash-looping":"restarting":n?.waiting?"waiting":n?.missing?"missing":n?.lastEnd?.verdict==="exited"?"exited":"stopped";return{name:t.name,state:i,port:t.port,env:Object.keys(t.env),...s?{pid:s.proc.pid,upMs:e-s.startedAt}:{},crashes:n?.crashes??0,restarts:n?.restarts??0,...n?.nextAt?{nextInMs:Math.max(0,n.nextAt-e)}:{},...n?.lastEnd?{lastEnd:n.lastEnd}:{},...n?.missing?{missing:n.missing}:{}}}),draining:[...ae].filter(t=>!Te(t)).map(t=>({name:t.name,pid:t.proc.pid}))}}h(rt,"it"),u(rt,"dt"),a(rt,"workersView");const ft=debounce(async()=>{ue+=1;const e=K();if(e.length>0)if(Ae==="process")R();else{console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)tell(t.proc,{type:"reload"})}await be(),WORKER_NAMES.length>0&&tt()},200),lt=a((e,t)=>{const s=(0,notice_generation_1.noticeGeneration)(e.headers);return s===void 0||s===POD_GENERATION?!1:(e.resume(),t.writeHead(409,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:s})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${s} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Le=a(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(lt(e,t))return;t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),Q(e).then(i=>{const o=i;J=(0,service_build_events_1.buildAccountOf)(o?.build),Ne=0,ie(o?.watcher)}),ft();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(lt(e,t))return;const i=await Q(e);ie(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:se!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);let i="";e.on("data",o=>{i+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(i||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await fe(o),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const i=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(i.get("tail")??"100")||0,0),ne),E=i.get("follow")!=="0",p=i.get("plain")==="1",m=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const A=o===0?"":X.slice(-o).join(""),I=p?A.replace(q,""):A;if(t.write(m?(0,inspector_secret_1.redactInspectorUrls)(I):I),!E){t.end();return}const T={res:t,plain:p,redact:m};le.add(T),e.on("close",()=>le.delete(T));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(i)&&i>1024&&(C=i),C||(C=9229);const o=k.filter(p=>p.proc.exitCode===null&&p.proc.signalCode===null);for(const p of o)p.debugSlot<0&&(p.debugSlot=x()),tell(p.proc,{type:"debug-open",port:inspectorPortFor(C,p.debugSlot)});for(const p of W)try{p.proc.kill("SIGUSR1")}catch{}const E=Date.now()+2e3;for(;Date.now()<E&&o.some(p=>!p.inspectorUrl);)await new Promise(p=>setTimeout(p,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:C,fixedPorts:usesFixedInspectorPorts(),pods:o.map(p=>({id:p.id,inspectorUrl:p.inspectorUrl??null})),workers:W.map(p=>({name:p.name,port:p.debugPort||(0,inspector_secret_1.inspectorPortOf)(p.inspectorUrl),inspectorUrl:p.inspectorUrl??null}))},null,2)+`
|
|
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"),u(importFresh,"importFresh"),a(importFresh,"importFresh");function debounce(d,_){let g=null;return(...y)=>{g&&clearTimeout(g),g=setTimeout(()=>d(...y),_)}}if(h(debounce,"debounce"),u(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const d=a(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(H){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,H.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const y=inspector.url();y&&tell(process,{type:"inspector-url",url:y})},"openInspector"),_=process.env.CHILD_DEBUG_PORT;if(_!==void 0&&_!==""){const g=Number(_);Number.isInteger(g)&&(g===0||g>1024)&&d(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,y=!1,H,ce=!1;const ze=(0,hot_swap_registries_1.registryPruningEnabled)(),ye=a((r,c)=>{const l=(0,hot_swap_registries_1.describePrune)(r,c);l&&console.log(l)},"logPrune");let Y={app:null,handler:a((r,c)=>{c.statusCode=503,c.end("starting")},"handler"),inflight:0};const Ee=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),C=a(r=>{const c=globalThis.__lensmcpActiveWork;if(typeof c!="function")return{count:0};try{const l=c(r);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),Ie=a(r=>{const c=globalThis.__lensmcpStopWorkIntake;if(typeof c=="function")try{return c(r)}catch{return}},"stopWorkIntake"),ve=a(r=>{const c=globalThis.__lensmcpWorkIntakeReport;if(!(r===void 0||typeof c!="function"))try{return c(r)??void 0}catch{return}},"workIntakeReport"),Re=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),q=new Set,ne=(()=>{const r=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(r)&&r>=0?r:3e4})(),X=a(r=>{if(!r.abandoned?.size)return 0;const c=Date.now();for(const[l,f]of r.abandoned)(l.writableEnded||c-f>=ne)&&r.abandoned.delete(l);return r.abandoned.size},"abandonedRunning"),le=a(r=>{let c=0;for(const l of r.open??[])l.headersSent||(c+=1);return{unanswered:c+X(r),streams:Math.max(0,r.inflight-c),work:C(r.workGen).count}},"owedBy"),de=a((r,c,l=`${c}s`)=>`${r} ${r===1?c:l}`,"plural");async function Ce(){if(ne<=0)return;const r=Date.now();let c=!1;for(;!(q.size<Re);){const l=q.values().next().value;if(!l||l.closed)break;const f=le(l);if(f.unanswered===0&&f.work===0)break;const v=(l.drainingSince??r)+ne-Date.now();if(v<=0)break;c||(c=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??r))/100)/10}s ago is still `+[f.unanswered?`answering ${de(f.unanswered,"request")}`:"",f.work?`running ${de(f.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${Re} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(v/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(S=>{setTimeout(S,Math.min(100,v)).unref?.()})}c&&console.log(`[child] swap resumed after ${Date.now()-r}ms`)}h(Ce,"We"),u(Ce,"He"),a(Ce,"holdForOwedWork");const M=(0,swap_recycle_1.swapRecyclePolicy)();let Se,pe=0;const Qe=(()=>{const r=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(r)&&r>=0?r:3500})();let w=!1,P=!1;const k=(0,pod_memory_1.podMemoryPolicy)();let V=0;const x=new Set;let ke=!1;const j=a(()=>{if(!ke)return;let r=0;for(const c of x)c.deref()===void 0?x.delete(c):r+=1;return r},"liveContainers"),b=(0,pod_memory_1.createPodMemoryMonitor)({policy:k,state:a(()=>({gen:Y.gen??0,swaps:pe,...Se===void 0?{}:{baseline:Se},busy:y||ce||z.size>0,lastBusyAt:G}),"state"),send:a(r=>{tell(process,r)},"send"),containers:j,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:a(()=>be(),"onRetained"),gc:a(()=>(0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc),"gc")}),U=a(()=>((0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc)&&b.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),B=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,c=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:a(f=>l.has(f)||f.startsWith(r)&&!f.slice(r.length).includes(c)&&!f.slice(r.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let ue=!1;function Ne(){if(!(!B||ue))try{const r=B();r.errors>0&&console.log(`[child] read the stack of ${r.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(r){console.error("[child] could not release library error frames:",r),ue=!0}}h(Ne,"Te"),u(Ne,"De"),a(Ne,"releaseLibraryErrorFrames");const z=new Map;let G=Date.now();const Be=a(()=>M.settleMs<=0||!y&&!ce&&z.size===0&&Date.now()-G>=M.settleMs,"settled");let K;function se(r){if(!(!M.enabled||w||P)){if(M.settleMs<=0){J(r);return}K&&clearTimeout(K),K=setTimeout(()=>{K=void 0,Be()?J(r):se(r)},M.settleMs),K.unref?.()}}h(se,"ae"),u(se,"_e"),a(se,"recycleCheckWhenSettled");const he=a(()=>{if(M.stuckCloseMs<=0)return 0;const r=Date.now();let c=0;for(const l of z.values())r-l>=M.stuckCloseMs&&(c+=1);return c},"stuckCloses");function J(r){if(!M.enabled||w||P||!process.send)return;const c=v8.getHeapStatistics().heap_size_limit,l=a(v=>{const S=b.oldestRetained();return{swaps:pe,usedBytes:v,limitBytes:c,stuckCloses:he(),settled:Be(),...Se===void 0?{}:{baselineBytes:Se},...S?{retained:{generation:S.gen,ageMs:Date.now()-S.closedAt,gcs:S.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(M,l(process.memoryUsage().heapUsed)))return;const f=(0,swap_recycle_1.recycleReason)(M,l(U()));f&&(w=!0,console.log(`[child] asking for a fresh pod ${r} \u2014 ${f}. 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}))}h(J,"X"),u(J,"Q"),a(J,"considerRecycle");let fe;function be(){if(!M.enabled||M.retainedMs<=0||w||P)return;const r=b.oldestRetained();if(!r)return;const c=r.closedAt+M.retainedMs-Date.now();if(c<=0){J("with a closed generation still in memory");return}fe||(fe=setTimeout(()=>{fe=void 0,b.measure("retention-check").catch(()=>{})},c),fe.unref?.())}h(be,"Oe"),u(be,"je"),a(be,"retainedRecycleCheck");function ie(r,c){const l={};c&&(l.module=c),r&&(l.app=r);try{const f=r?.app;if(f&&typeof f=="object"&&(l.nestApp=f,f.container&&typeof f.container=="object")){l.container=f.container;const v=rootModuleOf(f.container);v&&(l.rootModule=v)}}catch{}return l}h(ie,"ve"),u(ie,"Ne"),a(ie,"generationTargets");async function Q(r,c){if(r.closed||(r.closed=!0,q.delete(r),c&&console.error(`[child] ${c}`),!r.app))return;z.set(r,Date.now());const l=M.enabled&&M.stuckCloseMs>0?setTimeout(()=>{z.has(r)&&(console.error(`[child] a previous app has been closing for ${Math.round(M.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.`),J("with a previous app stuck in close()"))},M.stuckCloseMs):void 0;l?.unref?.();const f=r.app;try{await r.app.close()}catch(v){console.error("[child] error closing a previous app:",v)}finally{z.delete(r),G=Date.now(),l&&clearTimeout(l),r.app=null,r.abandoned=void 0,r.handler=(v,S)=>{S.statusCode=503,S.end("closed")},r.gen!==void 0&&b.closed(r.gen,pe,ie(f,r.module)),r.module=void 0}if(r.superseded){try{ye((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(v){console.error("[child] could not release the previous app's class registrations:",v)}r.superseded=void 0}se("after closing the previous app")}h(Q,"de"),u(Q,"me"),a(Q,"closeGeneration");async function Z(r){const c=Date.now();r.drainingSince=c;const l=a(()=>({inflight:r.inflight+X(r),work:C(r.workGen).count}),"busy"),f=l();if(f.inflight>0||f.work>0){for(;q.size>=Re;){const D=q.values().next().value;if(!D)break;const O=le(D),te=Math.round((Date.now()-(D.drainingSince??c))/1e3);if(O.unanswered===0&&O.streams===0&&O.work===0){await Q(D);continue}await Q(D,`closing a still-draining generation early \u2014 ${q.size} were pending and the cap is ${Re} (each holds a whole app: DI container, DB pool, queue workers). `+(O.unanswered>0||O.work>0?`It was replaced ${te}s ago and had still not answered ${de(O.unanswered,"request")}`+(O.work?` or finished ${de(O.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${ne}ms) to wait longer for them. `:`It held only ${de(O.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}q.add(r)}for(;Ee>0&&Date.now()-c<Ee;){if(r.closed)return;const D=l();if(D.inflight===0&&D.work===0)break;await new Promise(O=>{setTimeout(O,250).unref?.()})}if(r.closed)return;const v=l(),S=ve(r.workGen);v.inflight>0||v.work>0?console.error(`[child] swap drain expired after ${Date.now()-c}ms \u2014 closing the previous app with ${v.inflight} in-flight request(s) and ${v.work} background job(s) still running. `+(S?`Previous app still processing ${S.running} queue job(s); ${S.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${Ee}ms) if this is legitimate work.`):(Date.now()-c>250||S?.runningAtStop||S?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-c}ms \u2014 closing it`+(S?` (it finished ${S.finishedAfterStop} queue job(s) after the swap; ${S.takenAfterStop} were taken after the swap)`:"")),await Q(r)}h(Z,"pe"),u(Z,"ye"),a(Z,"drainThenClose");async function ee(){if(y)return ce=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;y=!0,G=Date.now(),await Ce(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(A,re)=>{console.error(`[child] uncaughtException (${re}):`,A)}),process.on("unhandledRejection",A=>{const re=A instanceof Error?A.stack||A.message:String(A);re.includes("FronteggContextScope")||re.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",A)});let r;if(ze)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=void 0}const c=global.__lensmcpBeginWorkGeneration,l=typeof c=="function"?c():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(A){if(!(A instanceof SyntaxError))throw A;console.warn(`[child] bundle was unreadable mid-write (${A.message}) \u2014 retrying the swap once`),await new Promise(re=>setTimeout(re,400)),await importFresh(BUNDLE_PATH)}}catch(A){throw y=!1,G=Date.now(),A}if(typeof global.createChildApp!="function")throw y=!1,G=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 f;if(r)try{f=(0,hot_swap_registries_1.supersededSince)(r),ye((0,hot_swap_registries_1.pruneRegistries)(f,"build"),"at the swap")}catch(A){console.error("[child] could not release superseded class registrations:",A)}let v;try{v=await global.createChildApp()}catch(A){throw y=!1,G=Date.now(),takeImportedModule(),A}const S=Y;f?.size&&(S.app?S.superseded=f:ye((0,hot_swap_registries_1.pruneRegistries)(f,"serve"),"at the swap")),g=v;const D=takeImportedModule();Y={app:v,handler:v.handler,inflight:0,gen:++V,...D?{module:D}:{},...l===void 0?{}:{workGen:l}};const O=ie(v,void 0).container;O&&(x.add(new WeakRef(O)),ke=!0);const te=S.app&&l!==void 0?Ie(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(te?.workers?`; its ${te.workers} queue worker(s) stopped taking new jobs, previous app still processing ${te.runningAtStop} job(s)`:"")),Ne(),S.app&&(pe+=1,Z(S).catch(A=>console.error("[child] background close error:",A)),setTimeout(()=>J("30s after a hot swap"),3e4).unref?.(),b.swapped(S.gen,()=>S.closed===!0&&!z.has(S))),y=!1,G=Date.now(),ce?(ce=!1,ee()):v}h(ee,"ue"),u(ee,"Ee"),a(ee,"swapNow");const $e=debounce(async()=>{try{console.log("[child] /webpack/reload"),await ee()}catch(r){console.error("[child] reload failed:",r)}},250),_e=http.createServer((r,c)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){c.writeHead(200,{"content-type":"application/json"}),c.end(JSON.stringify({ok:!0})),$e();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const f=r.headers["x-internal-token"],v=process.env.LENSMCP_SERVICE_KEY;if(v&&f!==v){c.writeHead(404,{"content-type":"text/plain"}),c.end("not found");return}const S=C();c.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),c.end(JSON.stringify({ok:!0,pid:process.pid,swapping:y,inflight:Y.inflight,oldestInflightAt:Y.oldestAt,activeWork:S.count,oldestWorkAt:S.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...H?{watcher:H}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=Y;l.inflight+=1,(l.open??=new Set).add(c),l.oldestAt===void 0&&(l.oldestAt=Date.now()),c.on("close",()=>{l.inflight-=1,l.open?.delete(c),ne>0&&!l.closed&&!c.headersSent&&!c.writableEnded&&((l.abandoned??=new Map).set(c,Date.now()),l.abandoned.size>256&&X(l)),l.inflight===0&&(l.oldestAt=void 0),b.activity()}),l.handler(r,c)});_e.keepAliveTimeout=0;const $=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock($),process.exit(0)});try{const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const l=(0,schema_first_instance_1.prewarmZodFor)(r);l?.created&&console.log(`[child] zod${l.version?` ${l.version}`:""}: built ${l.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 l=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(r);l?.patched&&console.log(`[child] ioredis${l.versions.length?` ${l.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 ee(),Se=(await b.booted()).live,console.log(`[child] Using bundle: ${r}`),claimSock($);let c=!1;_e.on("error",l=>{if(c){console.error("[child] server error:",l);return}console.error(`[child] could not listen on ${$} (${l.code??l.message}) \u2014 exiting; the devserver starts another pod`),tell(process,{type:"boot-error",error:`could not listen on ${$}: ${String(l?.stack||l)}`}),cleanupSock($),process.exitCode=1,setTimeout(()=>process.exit(1),100)}),_e.listen($,()=>{c=!0,console.log(`[child] Listening on ${$}`),tell(process,{type:"ready",socketPath:$})})}catch(r){console.error("[child] Startup error:",r),tell(process,{type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const c=Number(r.port);d(Number.isInteger(c)&&c>1024?c:0);return}if(r.type==="watcher"){const c=r.testimony;c&&typeof c=="object"&&(H=c);return}if(r.type==="heap-snapshot"){Pe(r);return}if(r.type==="recycle-declined"){w=!1;return}if(r.type==="retire"){L();return}if(r.type==="reload"){if(P)return;try{const c=Date.now();await ee(),tell(process,{type:"reloaded",socketPath:$,swap:{generation:V,swaps:pe,swapMs:Date.now()-c,draining:q.size}})}catch(c){console.error("[child] reload error:",c),tell(process,{type:"reload-error",error:String(c?.stack||c)})}}}});let ge=!1;async function Pe(r){const c=a(l=>{tell(process,{type:"heap-snapshot-result",id:r.id,...l})},"reply");if(ge)return c({ok:!1,refused:"a snapshot of this pod is already being written"});ge=!0;try{const l=U(),f=b.tracker.views().map(O=>O.gen),v=(0,heap_snapshot_1.heapSnapshotPolicy)();r.maxHeapBytes&&(v.maxHeapBytes=r.maxHeapBytes);const S=(0,heap_snapshot_1.heapSnapshotRefusal)(v,{live:l,...r.available?{available:r.available}:{}});if(S)return c({ok:!1,refused:S,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const D=await(0,heap_snapshot_1.writeHeapSnapshot)(r.file);c({ok:!0,file:r.file,bytes:D.bytes,ms:D.ms,live:l,gen:Y.gen??0,closedAlive:f})}catch(l){c({ok:!1,error:l?.message??String(l)})}finally{ge=!1}}h(Pe,"Me"),u(Pe,"Ye"),a(Pe,"takeHeapSnapshot");async function L(r="a fresh pod has taken over"){if(P)return;P=!0,b.stop(),console.log(`[child] retiring \u2014 ${r}; ${path.basename($,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const c=Ie(Number.POSITIVE_INFINITY);c?.workers&&console.log(`[child] its ${c.workers} queue worker(s) stopped taking new jobs, still processing ${c.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath($),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(f=>{setTimeout(f,Qe).unref?.()}),cleanupSock($),_e.close();const l=Date.now();for(;Date.now()-l<Ee;){_e.closeIdleConnections?.();let f=0;for(const v of q)f+=le(v).unanswered;if(Y.inflight+X(Y)===0&&f===0&&C().count===0)break;await new Promise(v=>{setTimeout(v,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(f){console.error("[child] close error while retiring:",f)}process.exit(0)}h(L,"q"),u(L,"B"),a(L,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const we=a(async r=>{console.log(`[child ${r}] shutting down\u2026`);const c=g?.close().catch(f=>console.error("[child] close error:",f)),l=new Promise(f=>_e.close(()=>f()));cleanupSock($),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([c,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{if(r==="SIGTERM"&&!P&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){L("this generation was handed over to a newer one");return}inspector?.close?.(),we(r)})})})()}else{let d=u(function(w){X.push(w),X.length>ne&&X.splice(0,X.length-ne),Re?.write((0,inspector_secret_1.redactInspectorUrls)(w.replace(q,"")));for(const P of le)try{const k=P.plain?w.replace(q,""):w;P.res.write(P.redact?(0,inspector_secret_1.redactInspectorUrls)(k):k)}catch{le.delete(P)}},"ge");a(d,"emitLogLine");const _=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",y=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),H=a(w=>({project:g,service:SERVICE_NAME||g,pod:w.id,pid:w.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),ce=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],ze="\x1B[0m",ye=Math.max(1,Number(process.env.CHILD_COUNT||1)),Y=LENSMCP_WS_KEY||path.basename(process.cwd()),Ee=a(w=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(Y,SERVICE_NAME||"service",w)),"execPathFor");let C=(()=>{const w=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(w)&&w>1024?w:0})();const Ie=20;C&&(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 (${C}+slot, workers ${C}+${Ie}+i). Any account on this host can connect to 127.0.0.1:${C} 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 ve=process.env.LENSMCP_LOG_FILE||"",Re=ve?(()=>{try{fs.mkdirSync(path.dirname(ve),{recursive:!0});const w=fs.createWriteStream(ve,{flags:"a",mode:384});try{fs.chmodSync(ve,384)}catch{}return w}catch(w){return console.warn(`[parent] cannot open log file ${ve}:`,w.message),null}})():null,q=/\x1b\[[0-9;]*m/g,ne=2e3,X=[],le=new Set,de=(0,devserver_transport_1.gatewayManaged)();for(const w of["log","info","warn","error"]){const P=console[w].bind(console),k=w==="log"||w==="info"?process.stdout:process.stderr;console[w]=(...V)=>{P(...V.map(x=>typeof x=="string"?(0,inspector_secret_1.forStream)(k,de,x):x)),d(V.map(x=>typeof x=="string"?x:util.inspect(x)).join(" ")+`
|
|
13
|
+
`)}}const Ce=a(w=>ce[(w-1)%ce.length],"colorFor"),M=a(w=>{if(ye===1)return"";const P=`[child#${w}] `;return`${Ce(w)}${P}${ze}`},"tagFor"),Se=a(w=>w.startsWith("{")&&w.endsWith("}"),"isJsonLog"),pe=a((w,P,k)=>{const V=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:a(j=>`
|
|
14
|
+
`+String(j),"stack")}}),x=a((j,b)=>{const U=j==="stdout"?process.stdout:process.stderr;try{k?.(b)}catch{}const B=Se(b)?V(b):`${b}
|
|
15
|
+
`;U.write((0,inspector_secret_1.forStream)(U,de,`${P}${B}`)),d(`${P}${B}`)},"write"),ke=a((j,b)=>{if(!j)return;const U=readline.createInterface({input:j});U.on("line",B=>x(b,B)),U.on("close",()=>{})},"attach");ke(w.stdout,"stdout"),ke(w.stderr,"stderr")},"wireProcLogging"),Qe=a(w=>pe(w.proc,M(w.id),P=>w.tail.push(P)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const w=httpProxy.createProxyServer({});let P=Number(process.env.PORT)||0;P||(P=9090);const k=[],V=new Set;function x(){let e=0;for(;V.has(e);)e++;return V.add(e),e}h(x,"x"),u(x,"D"),a(x,"allocDebugSlot");let ke=1,j=0,b=!1,U=ye,B=[],ue=0;const Ne=(0,swap_mode_1.swapMode)(),z=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),G=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Be=a(e=>e.booting&&!G(e)&&Date.now()-e.spawnedAt<z,"stillBooting");function K(){return k.filter(e=>e.healthy&&typeof e.socketPath=="string")}h(K,"Y"),u(K,"J"),a(K,"healthyChildren");let se,he=0,J;const fe={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function be(e){const t=(0,service_build_events_1.buildAccountOf)(e.building);t&&t.startedAt!==he&&(he=t.startedAt,y.publish((0,service_build_events_1.buildServiceBuildEvent)(fe,"started",t,{root:process.cwd()})));const s=(0,service_build_events_1.buildAccountOf)(e.failed);s&&(he=0,y.publish((0,service_build_events_1.buildServiceBuildEvent)(fe,"failed",s,{root:process.cwd()})));const n=(0,service_build_events_1.buildAccountOf)(e.unchanged);n&&n.startedAt===he&&(he=0,y.publish((0,service_build_events_1.buildServiceBuildEvent)(fe,"unchanged",n,{root:process.cwd()})))}h(be,"Oe"),u(be,"je"),a(be,"recordBuildProgress");function ie(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){be(t),"failed"in t&&delete t.failed,"unchanged"in t&&delete t.unchanged,se=t;for(const s of k)tell(s.proc,{type:"watcher",testimony:t})}}h(ie,"ve"),u(ie,"Ne"),a(ie,"recordWatcherTestimony");function Q(e,t=256*1024){return new Promise(s=>{let n="",i=!1;e.on("data",o=>{i||(n+=o,n.length>t&&(i=!0))}),e.on("end",()=>{if(i||!n)return s(void 0);try{s(JSON.parse(n))}catch{s(void 0)}}),e.on("error",()=>s(void 0))})}h(Q,"de"),u(Q,"me"),a(Q,"readJsonBody");function Z(){const e=K();if(e.length===0)return null;const t=j%e.length;return j=(j+1)%e.length,e[t]}h(Z,"pe"),u(Z,"ye"),a(Z,"pickChild");function ee(e,t){const s=`Child#${e.id}`,n=SERVICE_NAME||g,i=Date.now();if(t.type==="pod-memory"){const o=t.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(s,o)}`),y.publish((0,pod_memory_events_1.buildPodMemoryEvent)(H(e),o))}else if(t.type==="pod-memory-retained"){const o=t.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:i}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(s,n,o)}`),y.publish((0,pod_memory_events_1.buildPodRetainedEvent)(H(e),o,i))}else{const o=t.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(s,n,o)}`),y.publish((0,pod_memory_events_1.buildPodReleasedEvent)(H(e),o,i))}}h(ee,"ue"),u(ee,"Ee"),a(ee,"recordPodMemory");const $e=new Map;let _e=0;async function $(){const e=ke++,t=childSockPath(e);cleanupSock(t);const s=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&s.unshift("--require","@swc-node/register"),s.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const n=C?x():-1,i=(0,node_child_process_1.fork)(__filename,{env:(0,child_process_env_1.childProcessEnv)({...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...n>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(C,n))}}),stdio:["inherit","pipe","pipe","ipc"],execArgv:s,execPath:Ee("pod")}),o={id:e,proc:i,healthy:!1,lastError:null,debugSlot:n,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ue,memory:{history:[],retained:new Map}};return i.on("error",E=>console.error(`[parent] Child#${e}: ${E.message}`)),i.on("message",E=>{if(!E||typeof E!="object")return;const p=E;if(p.type==="ready")o.socketPath=String(p.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),se&&tell(i,{type:"watcher",testimony:se}),ue!==o.bootReloadSeq&&(Ne==="process"?A():tell(i,{type:"reload"}));else if(p.type==="recycle-request")S(o,String(p.reason||"it has outgrown its bound"));else if(p.type==="inspector-url")o.inspectorUrl=String(p.url),console.log(`[parent] Child#${o.id} Debugger listening on ${p.url}`);else if(p.type==="reloaded"){if(L.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(p.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const m=p.swap;m&&typeof m.generation=="number"&&typeof m.swaps=="number"&&y.publish((0,service_build_events_1.buildServiceSwapEvent)(H(o),{generation:m.generation,swaps:m.swaps,swapMs:typeof m.swapMs=="number"?m.swapMs:0,draining:typeof m.draining=="number"?m.draining:0},J,{root:process.cwd()}))}else p.type==="reload-error"?(o.lastError=String(p.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):p.type==="boot-error"?(o.lastError=String(p.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):p.type==="pod-memory"||p.type==="pod-memory-retained"||p.type==="pod-memory-released"?ee(o,p):p.type==="heap-snapshot-result"&&$e.get(Number(p.id))?.(p)}),i.on("exit",(E,p)=>{o.booting=!1;for(const R of o.memory.retained.values()){const I={gen:R.gen,closedAt:R.closedAt,ageMs:Date.now()-R.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,I)}`),y.publish((0,pod_memory_events_1.buildPodReleasedEvent)(H(o),I,Date.now()))}o.memory.retained.clear(),o.memory.latest&&y.publish((0,pod_memory_events_1.buildPodExitedEvent)(H(o),{code:E,signal:p},Date.now())),o.debugSlot>=0&&(V.delete(o.debugSlot),o.debugSlot=-1);const m=p==="SIGINT"||p==="SIGTERM"||E===0;if(m?console.log(`[parent] Child#${o.id} exited cleanly (code=${E}, signal=${p??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${E}, signal=${p??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,E,p,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${E}, signal=${p??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!m&&!b){const R=k.indexOf(o);R>=0&&k.splice(R,1);const I=Date.now();if(B=B.filter(T=>I-T<3e4),B.length>=5){console.error(`[parent] crash-loop detected (${B.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}B.push(I),setTimeout(()=>{const T=k.filter(oe=>oe.proc.killed===!1).length;!b&&T<U&&(console.log(`[parent] respawning a crashed pod (${T}/${U} live)`),$().catch(()=>{}))},1500).unref?.()}}),k.push(o),Qe(o),o}h($,"N"),u($,"N"),a($,"spawnChild");async function ge(e){U=Math.max(U,e);const t=k.filter(n=>n.proc.killed===!1),s=e-t.length;for(let n=0;n<s;n++)await $()}h(ge,"Se"),u(ge,"be"),a(ge,"ensurePoolSize");async function Pe(){const e=k.filter(t=>(!t.healthy||typeof t.socketPath!="string")&&!Be(t));await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const s=k.indexOf(t);s>=0&&k.splice(s,1),await $()}))}h(Pe,"Me"),u(Pe,"Ye"),a(Pe,"respawnUnhealthy");const L=new Set;let we=!1,r=0;const c=3e4,l=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function f(e,t={}){const s=Date.now(),n=await $(),i=Date.now()+z;for(;!n.healthy&&!n.lastError&&!G(n)&&Date.now()<i;){if(t.superseded?.()){const o=k.indexOf(n);o>=0&&k.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`a newer build landed while Child#${n.id} booted`,superseded:!0}}await new Promise(o=>{setTimeout(o,100).unref?.()})}if(b)return{why:"shutting down"};if(!n.healthy){const o=k.indexOf(n);o>=0&&k.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`its successor Child#${n.id} did not come up (${n.lastError??"no answer in time"})`}}return v(e),{fresh:n,readyMs:Date.now()-s}}h(f,"h"),u(f,"_"),a(f,"replacePod");function v(e){const t=k.indexOf(e);if(t>=0&&k.splice(t,1),e.healthy=!1,G(e))return;L.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const s=setTimeout(()=>{if(!G(e)){console.error(`[parent] Child#${e.id} was still running ${l}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},l);if(s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),L.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}}h(v,"E"),u(v,"m"),a(v,"retirePod");async function S(e,t){const s=a(i=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${t}) \u2014 not now: ${i}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(b||!k.includes(e))return;if(we)return s("another pod is already being replaced");const n=Date.now()-r;if(n<c)return s(`a pod was replaced ${Math.round(n/1e3)}s ago`);we=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${t}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const i=await f(e);if("why"in i){i.why!=="shutting down"&&s(i.why);return}r=Date.now(),console.log(`[parent] Child#${i.fresh.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${i.fresh.proc.pid})`)}catch(i){s(`could not start a successor: ${i?.message??String(i)}`)}finally{we=!1}}h(S,"v"),u(S,"y"),a(S,"recyclePod");let D,O=!1,te=0;function A(){if(D){O=!0;return}D=(async()=>{do{O=!1;const e=ue;for(;we&&!b;)await new Promise(s=>{setTimeout(s,100).unref?.()});let t=!1;for(const s of K().filter(n=>n.bootReloadSeq<e)){if(b)break;if(!(!k.includes(s)||!s.healthy)){we=!0;try{re(),console.log(`[parent] A new build \u2014 starting a fresh pod for it; Child#${s.id} keeps serving the previous build until it is ready.`);const n=await f(s,{superseded:a(()=>ue!==e&&(0,swap_mode_1.abandonSuperseded)(te),"superseded")});if("why"in n){if(n.superseded){te+=1,console.log(`[parent] ${n.why} \u2014 abandoned; one fresh pod for the newest build instead (${te} in a row). Child#${s.id} keeps serving.`),t=!0;break}n.why!=="shutting down"&&console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 ${n.why}`);continue}te=0,console.log(`[parent] Child#${n.fresh.id} took over from Child#${s.id} with the new build (pid ${s.proc.pid} \u2192 ${n.fresh.proc.pid}, ready in ${(n.readyMs/1e3).toFixed(1)}s while Child#${s.id} served)`),y.publish((0,service_build_events_1.buildServiceSwapEvent)(H(n.fresh),{generation:1,swaps:0,swapMs:n.readyMs,draining:L.size,handoff:{fromPid:s.proc.pid??0}},J,{root:process.cwd()}))}catch(n){console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 could not start a successor: ${n?.message??String(n)}`)}finally{we=!1}}}t&&(O=!0)}while(O&&!b)})().finally(()=>{D=void 0})}h(A,"P"),u(A,"A"),a(A,"handoffForReload");function re(){const e=(0,swap_mode_1.maxRetiring)(),t=[...L].filter(s=>!G(s));for(;t.length>=e;){const s=t.shift();console.error(`[parent] Child#${s.id} (pid ${s.proc.pid}) was still finishing its work when a newer build replaced its successor too \u2014 stopping it (LENSMCP_SWAP_MAX_RETIRING=${e})`);try{s.proc.kill("SIGTERM")}catch{}}}h(re,"ee"),u(re,"q"),a(re,"capRetiring");const W=[],je=new Map,Ke=a(e=>{let t=je.get(e);return t||je.set(e,t={crashes:0,restarts:0}),t},"watchOf"),Ze=(0,worker_supervisor_1.workerBackoff)(),Oe=a((e,t)=>({project:g,service:SERVICE_NAME||g,worker:e,pid:t,podGeneration:POD_GENERATION}),"workerWho"),it=[path.join(__dirname,"worker-guard.js"),path.join(__dirname,"worker-guard.ts")].find(e=>fs.existsSync(e));for(const e of WORKER_PROBLEMS)console.error(`[parent] ${e}`);function Ye(e,t){const s=e.name,n=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),i=path.join(n,`${s}.js`),o=Ke(s);if(!fs.existsSync(i))return console.error(`[parent] Worker "${s}" bundle not found: ${i}`),o.missing=i,y.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Oe(s,0),{ranMs:0,reason:`its bundle was not built (${path.basename(i)} is not in ${n})`})),null;o.missing=void 0;const E=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&E.unshift("--require","@swc-node/register"),it&&E.push("--require",it);try{E.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const p=inspectorPortFor(C||9229,Ie+Math.max(0,WORKER_NAMES.indexOf(s)));E.push(C?`--inspect=127.0.0.1:${p}`:`--inspect-port=127.0.0.1:${p}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const m=(0,node_child_process_1.fork)(i,{env:(0,worker_supervisor_1.workerEnv)(process.env,e),stdio:["inherit","pipe","pipe","ipc"],execArgv:E,execPath:Ee(`w-${s}`)}),R={name:s,spec:e,proc:m,debugPort:p,startedAt:Date.now(),tail:new exit_reason_1.TailBuffer,stopping:!1};W.push(R),m.on("error",T=>console.error(`[parent] Worker "${s}": ${T.message}`)),pe(m,`\x1B[35m[worker:${s}] \x1B[0m`,T=>{R.tail.push(T);const oe=/Debugger listening on (wss?:\/\/\S+)/.exec(T);oe&&(R.inspectorUrl=oe[1])});const I=setTimeout(()=>{if(!(Te(R)||R.stopping)){if(o.crashes>0){const T=Date.now()-R.startedAt;console.log(`[parent] Worker "${s}" (pid=${m.pid}) is up again \u2014 ${(0,worker_supervisor_1.spanOf)(T)} without a crash, after ${o.crashes} in a row`),y.publish((0,worker_supervisor_1.buildWorkerUpEvent)(Oe(s,m.pid??0),{ranMs:T,afterCrashes:o.crashes}))}o.crashes=0}},Ze.stableMs);return I.unref?.(),m.on("exit",(T,oe)=>{clearTimeout(I);const Ae=W.indexOf(R);Ae>=0&&W.splice(Ae,1),et(R,T,oe)}),console.log(`[parent] Worker "${s}" started (pid=${m.pid}, PORT=${(0,worker_supervisor_1.workerEnv)(process.env,e).PORT}${t==="respawn"?`, restart ${o.restarts}`:""})`),y.publish((0,worker_supervisor_1.buildWorkerStartedEvent)(Oe(s,m.pid??0),{why:t,port:e.port,restarts:o.restarts})),R}h(Ye,"ze"),u(Ye,"Ze"),a(Ye,"spawnWorkerProcess");function et(e,t,s){const{name:n}=e,i=e.proc.pid??0,o=Date.now()-e.startedAt,E=(0,worker_supervisor_1.workerExitVerdict)(t,s,e.stopping||b);if(E.kind==="stopped")return;const p=Ke(n);if(E.kind==="exited"){p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"exited"},console.warn(`[parent] Worker "${n}" (pid=${i}) exited on its own (code 0) after ${(0,worker_supervisor_1.spanOf)(o)} \u2014 not started again until the next build: a worker that ends is taken to have finished its work`),y.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Oe(n,i),{ranMs:o}));return}p.crashes+=1,p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"crashed"};const m=(0,worker_supervisor_1.respawnDelay)(p.crashes,Ze),R=p.crashes>=worker_supervisor_1.CRASH_LOOP_AFTER;console.error((0,exit_reason_1.crashReport)(`[parent] Worker "${n}" crashed (code=${t}, signal=${s??"none"}, pid=${i}, after ${(0,worker_supervisor_1.spanOf)(o)})${E.external?" \u2014 stopped by a signal from outside its devserver":""}; starting it again in ${(0,worker_supervisor_1.spanOf)(m)}${R?` \u2014 ${p.crashes} crashes in a row: it is crash-looping (the wait doubles, up to ${(0,worker_supervisor_1.spanOf)(Ze.maxMs)})`:""}`,t,s,e.tail.snapshot())),y.publish((0,worker_supervisor_1.buildWorkerCrashedEvent)(Oe(n,i),{code:t,signal:s,ranMs:o,crashes:p.crashes,restarts:p.restarts,nextInMs:m,external:E.external,lines:e.tail.snapshot()})),clearTimeout(p.pending),p.nextAt=Date.now()+m,p.pending=setTimeout(()=>{p.pending=void 0,p.nextAt=void 0,!(b||W.some(I=>I.name===n))&&(p.restarts+=1,xe(e.spec,"respawn"))},m),p.pending.unref?.()}h(et,"rt"),u(et,"ct"),a(et,"workerEnded");function xe(e,t){const s=Ke(e.name);s.waiting=void 0;const n=e.port>0?[...ae].find(o=>o.name===e.name&&!Te(o)):void 0;if(!n){Ye(e,t);return}console.log(`[parent] Worker "${e.name}" starts once its predecessor (pid=${n.proc.pid}) has finished its work \u2014 it holds port ${e.port}`);const i=a(()=>{s.waiting===i&&(s.waiting=void 0),!b&&!W.some(o=>o.name===e.name)&&Ye(e,t)},"go");s.waiting=i,n.proc.once("exit",i)}h(xe,"qe"),u(xe,"Ve"),a(xe,"startWorker");const at=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),ct=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),ae=new Set,Te=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function De(e,t){if(ae.delete(e),!Te(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${t} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}h(De,"Be"),u(De,"Je"),a(De,"killDrainingWorker");function Ve(e){e.stopping=!0,tell(e.proc,{type:worker_retire_1.RETIRE_MESSAGE});try{e.proc.kill("SIGTERM")}catch{}if(Te(e))return;const t=[...ae].filter(n=>n.name===e.name);for(;t.length>=ct;)De(t.shift(),`was still running when a later reload replaced its successor (cap ${ct})`);ae.add(e);const s=setTimeout(()=>De(e,`was still running ${at}ms after SIGTERM`),at);s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),ae.delete(e)})}h(Ve,"Qe"),u(Ve,"rt"),a(Ve,"retireWorker");function Ge(){for(const e of je.values())clearTimeout(e.pending),e.pending=void 0,e.nextAt=void 0,e.waiting=void 0}h(Ge,"je"),u(Ge,"qe"),a(Ge,"cancelWorkerStarts");function tt(){for(const e of[...W])Ve(e);W.length=0,Ge();for(const e of WORKER_SPECS)Ke(e.name).crashes=0,xe(e,"reload")}h(tt,"st"),u(tt,"lt"),a(tt,"restartWorkers");function rt(){const e=Date.now();return{workers:WORKER_SPECS.map(t=>{const s=W.find(o=>o.name===t.name),n=je.get(t.name),i=s?"running":n?.pending?(n.crashes??0)>=worker_supervisor_1.CRASH_LOOP_AFTER?"crash-looping":"restarting":n?.waiting?"waiting":n?.missing?"missing":n?.lastEnd?.verdict==="exited"?"exited":"stopped";return{name:t.name,state:i,port:t.port,env:Object.keys(t.env),...s?{pid:s.proc.pid,upMs:e-s.startedAt}:{},crashes:n?.crashes??0,restarts:n?.restarts??0,...n?.nextAt?{nextInMs:Math.max(0,n.nextAt-e)}:{},...n?.lastEnd?{lastEnd:n.lastEnd}:{},...n?.missing?{missing:n.missing}:{}}}),draining:[...ae].filter(t=>!Te(t)).map(t=>({name:t.name,pid:t.proc.pid}))}}h(rt,"it"),u(rt,"dt"),a(rt,"workersView");const ft=debounce(async()=>{ue+=1;const e=K();if(e.length>0)if(Ne==="process")A();else{console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)tell(t.proc,{type:"reload"})}await Pe(),WORKER_NAMES.length>0&&tt()},200),lt=a((e,t)=>{const s=(0,notice_generation_1.noticeGeneration)(e.headers);return s===void 0||s===POD_GENERATION?!1:(e.resume(),t.writeHead(409,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:s})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${s} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Le=a(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(lt(e,t))return;t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),Q(e).then(i=>{const o=i;J=(0,service_build_events_1.buildAccountOf)(o?.build),he=0,ie(o?.watcher)}),ft();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(lt(e,t))return;const i=await Q(e);ie(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:se!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);let i="";e.on("data",o=>{i+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(i||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await ge(o),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const i=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(i.get("tail")??"100")||0,0),ne),E=i.get("follow")!=="0",p=i.get("plain")==="1",m=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const R=o===0?"":X.slice(-o).join(""),I=p?R.replace(q,""):R;if(t.write(m?(0,inspector_secret_1.redactInspectorUrls)(I):I),!E){t.end();return}const T={res:t,plain:p,redact:m};le.add(T),e.on("close",()=>le.delete(T));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(i)&&i>1024&&(C=i),C||(C=9229);const o=k.filter(p=>p.proc.exitCode===null&&p.proc.signalCode===null);for(const p of o)p.debugSlot<0&&(p.debugSlot=x()),tell(p.proc,{type:"debug-open",port:inspectorPortFor(C,p.debugSlot)});for(const p of W)try{p.proc.kill("SIGUSR1")}catch{}const E=Date.now()+2e3;for(;Date.now()<E&&o.some(p=>!p.inspectorUrl);)await new Promise(p=>setTimeout(p,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:C,fixedPorts:usesFixedInspectorPorts(),pods:o.map(p=>({id:p.id,inspectorUrl:p.inspectorUrl??null})),workers:W.map(p=>({name:p.name,port:p.debugPort||(0,inspector_secret_1.inspectorPortOf)(p.inspectorUrl),inspectorUrl:p.inspectorUrl??null}))},null,2)+`
|
|
16
16
|
`);return}if(e.method==="GET"&&e.url==="/webpack/workers"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(rt(),null,2)+`
|
|
17
17
|
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...k,...L].map(E=>({id:E.id,pid:E.proc.pid??0,podGeneration:POD_GENERATION,healthy:E.healthy,retiring:L.has(E),latest:E.memory.latest??null,history:E.memory.history,retained:[...E.memory.retained.values()]}));t.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),t.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:g,bundle:{path:i},pods:o},null,2)+`
|
|
18
18
|
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=a((F,Ue)=>{t.writeHead(F,{"content-type":"application/json"}),t.end(JSON.stringify(Ue,null,2)+`
|
|
19
|
-
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,E=Number(o.get("pod")||""),p=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(p!==void 0&&!(Number.isFinite(p)&&p>0)){i(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const m=Number.isInteger(E)&&E>0?k.find(F=>F.id===E):K()[0];if(!m||G(m)){i(404,{ok:!1,error:E?`no pod #${E} \u2014 running: ${k.map(F=>`#${F.id}`).join(", ")||"none"}`:"no pod is serving"});return}if($e.size>0){i(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const
|
|
19
|
+
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,E=Number(o.get("pod")||""),p=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(p!==void 0&&!(Number.isFinite(p)&&p>0)){i(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const m=Number.isInteger(E)&&E>0?k.find(F=>F.id===E):K()[0];if(!m||G(m)){i(404,{ok:!1,error:E?`no pod #${E} \u2014 running: ${k.map(F=>`#${F.id}`).join(", ")||"none"}`:"no pod is serving"});return}if($e.size>0){i(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const R=(0,heap_snapshot_1.heapSnapshotPolicy)(),I=(0,heap_snapshot_1.heapSnapshotDir)();let T;try{(0,heap_snapshot_1.ensureHeapDir)(I);const F=(0,heap_snapshot_1.pruneHeapSnapshots)(I,R);F.length&&console.log(`[parent] removed ${F.length} older heap snapshot(s) from ${I}`),T=path.join(I,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||g,m.id,m.proc.pid??0,m.memory.latest?.gen??0,Date.now()))}catch(F){i(500,{ok:!1,error:`cannot prepare ${I}: ${F.message}`});return}const oe=++_e,Ae=(0,heap_snapshot_1.availableSystemMemory)();let qe;const N=await new Promise(F=>{const Ue=setTimeout(()=>F(void 0),6e5);Ue.unref?.(),$e.set(oe,Et=>{clearTimeout(Ue),F(Et)}),qe=a(()=>{clearTimeout(Ue),F(void 0)},"onExit"),m.proc.once("exit",qe);const mt={type:"heap-snapshot",id:oe,file:T,...Ae?{available:Ae}:{},...p!==void 0?{maxHeapBytes:Math.floor(p*1048576)}:{}};tell(m.proc,mt)||qe()}).finally(()=>{$e.delete(oe),qe&&m.proc.off("exit",qe)});if(!N){i(504,{ok:!1,pod:m.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}N.ok?console.log(`[parent] Child#${m.id} wrote a ${Math.round((N.bytes??0)/1048576)} MB heap snapshot in ${N.ms} ms \u2192 ${N.file}`):console.warn(`[parent] Child#${m.id} heap snapshot not taken: ${N.refused??N.error}`),i(N.ok?200:N.refused?409:500,{ok:N.ok,pod:m.id,pid:m.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...Ae?{available:Ae}:{},...N.file?{file:N.file,bytes:N.bytes,ms:N.ms}:{},...N.live!==void 0?{live:N.live}:{},...N.gen!==void 0?{gen:N.gen,closedAlive:N.closedAlive??[]}:{},...N.refused?{refused:N.refused}:{},...N.error?{error:N.error}:{}});return}const s=matchGatewayRoute(e);if(s?.prependPrefix&&!e.url?.startsWith(s.prependPrefix)&&(e.url=s.prependPrefix+(e.url??"/")),s?.target){applyGatewayMiddleware(e),w.web(e,t,{target:s.target,autoRewrite:typeof s.target=="string",changeOrigin:!0},i=>{console.error(`[parent] proxy error to ${s.target}:`,i),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${s.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const n=Z();if(!n){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const i=k.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
|
|
20
20
|
`);t.end(`No healthy children available. POST /webpack/reload to recover.
|
|
21
21
|
|
|
22
22
|
Last errors:
|
|
23
23
|
${i}`);return}w.web(e,t,{target:{socketPath:n.socketPath},headers:{"x-proxy-child-id":String(n.id)}},i=>{console.error(`[parent] proxy error to Child#${n.id}:`,i),n.healthy=!1,n.lastError=String(i?.stack||i),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from Child#${n.id}. Try /webpack/reload.
|
|
24
|
-
${n.lastError}`)})},"requestHandler");let
|
|
24
|
+
${n.lastError}`)})},"requestHandler");let me,We,ot;const Fe=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(n=>n.host).filter(n=>!!n),s=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");We=e.getCertificateSync(s,SERVICE_NAME||"lensmcp.dev",t),ot=e.caCertPath(),me=require("node:https").createServer({key:We,cert:We},Le)}else me=http.createServer(Le);const dt=a((e,t,s)=>{const n=matchGatewayRoute(e);if(n?.prependPrefix&&!e.url?.startsWith(n.prependPrefix)&&(e.url=n.prependPrefix+(e.url??"/")),n?.target){w.ws(e,t,s,{target:n.target,changeOrigin:!0},()=>t.destroy());return}const i=Z();if(!i){t.destroy();return}w.ws(e,t,s,{target:{socketPath:i.socketPath}},()=>t.destroy())},"upgradeHandler");me.on("upgrade",dt);const pt=2e3;let ut=!1;me.on("error",e=>{if(e.code==="EADDRINUSE"){ut||(ut=!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 ${pt/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!b&&!me.listening&&me.listen(P)},pt).unref?.();return}console.error("[parent] public server error:",e)}),await ge(ye);for(const e of WORKER_SPECS)xe(e,"boot");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)`):me.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(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),ot&&(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 ${ot}`)),console.log(`POST ${e}://localhost:${P}/webpack/reload to trigger rolling swap/respawn`)});const Je=Number(process.env.INTERNAL_PORT||0);if(Je>0&&!SOCKET_ONLY){const e=a((s,n)=>{const i=Z();if(!i){n.statusCode=503,n.setHeader("content-type","text/plain; charset=utf-8"),n.end("No healthy children available.");return}w.web(s,n,{target:{socketPath:i.socketPath}},o=>{i.healthy=!1,i.lastError=String(o?.stack||o),n.statusCode=502,n.end(`Upstream error from Child#${i.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(s,n,i)=>{const o=Z();if(!o){n.destroy();return}w.ws(s,n,i,{target:{socketPath:o.socketPath}},()=>n.destroy())}),t.on("error",s=>{console.warn(`[parent] internal port ${Je} unavailable (${s.code}).`)}),t.listen(Je,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${Je}`)}),Fe.push(t)}const _t=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of _t){if(!Number.isInteger(e)||e===Number(P))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:We,cert:We},Le):http.createServer(Le);t.on("upgrade",dt),t.on("error",s=>{console.warn(`[parent] extra port ${e} unavailable (${s.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),Fe.push(t)}const Me=path.join(SOCK_DIR,"parent.sock");claimSock(Me),(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);const He=http.createServer(Le);He.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),He.listen(Me),Fe.push(He);function nt(){b=!0;const e=[...k,...L];console.log(`[SIGTERM] [parent] generation g${POD_GENERATION} was handed over to a newer one \u2014 ${e.length} pod(s) finish their in-flight work, then it exits`);const t=sockOwnedByAnother(Me);cleanupSock(Me);for(const i of Fe)if(!(i===He&&t))try{i.close()}catch{}try{me.close()}catch{}Ge();for(const i of[...W])Ve(i);W.length=0;for(const i of k)if(!tell(i.proc,{type:"retire"}))try{i.proc.kill("SIGTERM")}catch{}const s=setTimeout(()=>{for(const i of e)if(!G(i)){console.error(`[parent] Child#${i.id} was still running ${l}ms after its generation was retired \u2014 killing it`);try{i.proc.kill("SIGKILL")}catch{}}},l);s.unref?.();const n=a(()=>{if(!(e.some(i=>!G(i))||[...ae].some(i=>!Te(i)))){clearTimeout(s);for(const i of e)i.socketPath&&cleanupSock(i.socketPath,i.proc.pid);(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);try{fs.rmdirSync(SOCK_DIR)}catch{}console.log(`[parent] generation g${POD_GENERATION} retired`),process.exit(0)}},"finish");for(const i of e)i.proc.once("exit",n);for(const i of ae)i.proc.once("exit",n);n()}h(nt,"pt"),u(nt,"ht"),a(nt,"retireGeneration");const ht=a(async e=>{b=!0,console.log(`[${e}] [parent] shutting down\u2026`);const t=sockOwnedByAnother(Me);cleanupSock(Me);for(const n of Fe)if(!(n===He&&t))try{n.close()}catch{}Ge();for(const n of W){n.stopping=!0;try{n.proc.kill("SIGTERM")}catch{}}for(const n of WORKER_SPECS){const i=W.find(o=>o.name===n.name)?.proc.pid??0;y.publish((0,worker_supervisor_1.buildWorkerStoppedEvent)(Oe(n.name,i),{reason:`its devserver stopped (${e})`}))}for(const n of[...ae])De(n,"was still draining when its devserver shut down");for(const n of L)try{n.proc.kill("SIGTERM")}catch{}for(const n of k){try{n.proc.kill("SIGTERM")}catch{}n.socketPath&&cleanupSock(n.socketPath,n.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}const s=(0,devserver_host_1.devserverStopExitCode)(e);setTimeout(()=>process.exit(s),700).unref()},"shutdown");let st=0;const wt=2e3;["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{if(e==="SIGTERM"){if(!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){st=Date.now(),nt();return}if(st>0&&Date.now()-st<wt)return}ht(e)}));const Xe=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(Xe)&&Xe>0){const e=setInterval(()=>{try{process.kill(Xe,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${Xe}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),ht("gateway-gone")}},5e3);e.unref()}})()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.36",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@lensmcp/core": "1.25.
|
|
62
|
-
"@lensmcp/node-instrumentation": "1.25.
|
|
63
|
-
"@lensmcp/nx-plugin": "1.25.
|
|
61
|
+
"@lensmcp/core": "1.25.36",
|
|
62
|
+
"@lensmcp/node-instrumentation": "1.25.36",
|
|
63
|
+
"@lensmcp/nx-plugin": "1.25.36",
|
|
64
64
|
"fastify": "^5.8.5",
|
|
65
65
|
"@fastify/reply-from": "^12.6.2",
|
|
66
66
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
@@ -45,8 +45,8 @@ export interface SwapReport {
|
|
|
45
45
|
export declare function workspaceRelative(files: readonly string[], root: string | undefined): string[];
|
|
46
46
|
/** Parse what arrived over HTTP into a build account, or nothing: never trust a body's shape. */
|
|
47
47
|
export declare function buildAccountOf(raw: unknown): BuildAccount | undefined;
|
|
48
|
-
/** A build of this service started (`phase: 'started'`)
|
|
49
|
-
export declare function buildServiceBuildEvent(who: Pick<PodIdentity, 'project' | 'service' | 'podGeneration'>, phase: 'started' | 'failed', build: BuildAccount, opts?: {
|
|
48
|
+
/** A build of this service started (`phase: 'started'`), ended with errors (`'failed'`), or ended leaving its bundle as it was (`'unchanged'`). */
|
|
49
|
+
export declare function buildServiceBuildEvent(who: Pick<PodIdentity, 'project' | 'service' | 'podGeneration'>, phase: 'started' | 'failed' | 'unchanged', build: BuildAccount, opts?: {
|
|
50
50
|
root?: string;
|
|
51
51
|
at?: number;
|
|
52
52
|
id?: () => string;
|
package/service-build-events.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var g=Object.defineProperty;var l=(
|
|
2
|
-
`)[0].slice(0,160):void 0,v=e==="started"?`build started: ${
|
|
1
|
+
"use strict";var g=Object.defineProperty;var l=(n,e)=>g(n,"name",{value:e,configurable:!0});var f=Object.defineProperty,c=l((n,e)=>f(n,"name",{value:e,configurable:!0}),"c"),p=Object.defineProperty,s=c((n,e)=>p(n,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.workspaceRelative=workspaceRelative,exports.buildAccountOf=buildAccountOf,exports.buildServiceBuildEvent=buildServiceBuildEvent,exports.buildServiceSwapEvent=buildServiceSwapEvent;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),MAX_FILES=50,MAX_ERROR_CHARS=500;let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}l(makeId,"makeId"),c(makeId,"makeId"),s(makeId,"makeId");function workspaceRelative(n,e){return n.slice(0,MAX_FILES).map(t=>{if(!e||!path.isAbsolute(t))return t;const r=path.relative(e,t);return r&&!r.startsWith("..")&&!path.isAbsolute(r)?r.split(path.sep).join("/"):t})}l(workspaceRelative,"workspaceRelative"),c(workspaceRelative,"workspaceRelative"),s(workspaceRelative,"workspaceRelative");function buildAccountOf(n){if(!n||typeof n!="object")return;const e=n;if(typeof e.startedAt!="number"||!Number.isFinite(e.startedAt)||e.startedAt<=0)return;const t=Array.isArray(e.changedFiles)?e.changedFiles.filter(r=>typeof r=="string"&&r.length>0&&r.length<1024).slice(0,MAX_FILES):[];return{startedAt:e.startedAt,...typeof e.endedAt=="number"&&Number.isFinite(e.endedAt)?{endedAt:e.endedAt}:{},changedFiles:t,...typeof e.moreChanged=="number"&&e.moreChanged>0?{moreChanged:Math.floor(e.moreChanged)}:{},...typeof e.error=="string"&&e.error.trim()?{error:e.error.trim().slice(0,MAX_ERROR_CHARS)}:{}}}l(buildAccountOf,"buildAccountOf"),c(buildAccountOf,"buildAccountOf"),s(buildAccountOf,"buildAccountOf");function envelope(n,e,t,r,o,d,a){const i="pending";return{id:a(),sessionId:i,timestamp:e,source:"gateway",category:"cluster",severity:t,context:{sessionId:i,projectName:n.project},fingerprint:`cluster:${n.project}:${r}`,title:o,raw:{kind:r,...d,project:n.project,service:n.service,podGeneration:n.podGeneration}}}l(envelope,"envelope"),c(envelope,"envelope"),s(envelope,"envelope");const filesText=s((n,e)=>{const t=n+e;return t===0?"no source change":`${t} file${t===1?"":"s"}`},"filesText"),seconds=s(n=>n<1e3?`${n}ms`:`${(n/1e3).toFixed(1)}s`,"seconds");function buildServiceBuildEvent(n,e,t,r={}){const o=workspaceRelative(t.changedFiles,r.root),d=t.moreChanged??0,a=r.at??(e==="started"?t.startedAt:t.endedAt??Date.now()),i=t.endedAt!==void 0?Math.max(0,t.endedAt-t.startedAt):void 0,u=e==="failed"&&t.error?t.error.split(`
|
|
2
|
+
`)[0].slice(0,160):void 0,v=e==="started"?`build started: ${n.project} \u2014 ${filesText(o.length,d)}`:e==="unchanged"?`build unchanged: ${n.project} \u2014 ${filesText(o.length,d)}${i!==void 0?` \xB7 ${seconds(i)}`:""} \xB7 its bundle came out as it was, nothing to swap`:`build failed: ${n.project} \u2014 ${u??filesText(o.length,d)}${i!==void 0?` \xB7 ${seconds(i)}`:""} \xB7 nothing was swapped, the pod serves the last good build`;return envelope(n,a,e==="failed"?"warning":"info","service-build",v,{phase:e,startedAt:t.startedAt,...t.endedAt!==void 0?{endedAt:t.endedAt,compileMs:i}:{},changedFiles:o,...d>0?{moreChanged:d}:{},...e==="failed"&&t.error?{error:t.error}:{}},r.id??makeId)}l(buildServiceBuildEvent,"buildServiceBuildEvent"),c(buildServiceBuildEvent,"buildServiceBuildEvent"),s(buildServiceBuildEvent,"buildServiceBuildEvent");function buildServiceSwapEvent(n,e,t,r={}){const o=r.at??Date.now(),d=workspaceRelative(t?.changedFiles??[],r.root),a=t?.moreChanged??0,i=t?.endedAt!==void 0?Math.max(0,t.endedAt-t.startedAt):void 0,u=e.handoff?`swap: ${n.project} \u2014 a fresh pod on the new build`+(t?` \xB7 ${filesText(d.length,a)}`:"")+(i!==void 0?` \xB7 compiled in ${seconds(i)}`:"")+` \xB7 ready in ${seconds(e.swapMs)} while the previous pod served`:`swap: ${n.project} \u2014 generation ${e.generation} (${e.swaps} swap${e.swaps===1?"":"s"})`+(t?` \xB7 ${filesText(d.length,a)}`:"")+(i!==void 0?` \xB7 compiled in ${seconds(i)}`:"")+` \xB7 swapped in ${seconds(e.swapMs)}`;return envelope(n,o,"info","service-swap",u,{pod:n.pod,pid:n.pid,generation:e.generation,swaps:e.swaps,swappedAt:o,swapMs:e.swapMs,draining:e.draining,...e.handoff?{handoff:{fromPid:e.handoff.fromPid}}:{},...t?{compile:{startedAt:t.startedAt,...t.endedAt!==void 0?{endedAt:t.endedAt,ms:i}:{}},changedFiles:d,...a>0?{moreChanged:a}:{}}:{}},r.id??makeId)}l(buildServiceSwapEvent,"buildServiceSwapEvent"),c(buildServiceSwapEvent,"buildServiceSwapEvent"),s(buildServiceSwapEvent,"buildServiceSwapEvent");
|
package/shared-build/host.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function buildTargetString(join: Pick<MemberJoinRequest, 'project
|
|
|
21
21
|
export declare class BuildHost {
|
|
22
22
|
private readonly opts;
|
|
23
23
|
private readonly groups;
|
|
24
|
-
/** member → its project and
|
|
24
|
+
/** member → its project, its group, and what it joined with (evaluated again when a config file changes). */
|
|
25
25
|
private readonly members;
|
|
26
26
|
private readonly pauseMs;
|
|
27
27
|
private readonly webpack;
|
|
@@ -36,6 +36,8 @@ export declare class BuildHost {
|
|
|
36
36
|
/** Build a member's config the way the shared compilation will, and its signature. Throws what the config throws. */
|
|
37
37
|
private evaluate;
|
|
38
38
|
join(member: string, join: MemberJoinRequest): void;
|
|
39
|
+
/** Put an evaluated member into the group of its signature — a new group when none has it. */
|
|
40
|
+
private admit;
|
|
39
41
|
leave(member: string, opts?: {
|
|
40
42
|
quiet?: boolean;
|
|
41
43
|
}): void;
|
|
@@ -51,6 +53,17 @@ export declare class BuildHost {
|
|
|
51
53
|
*/
|
|
52
54
|
private compileAgain;
|
|
53
55
|
private onBuild;
|
|
56
|
+
/**
|
|
57
|
+
* A file the group's configs were read from changed (config-files.ts): every member is evaluated again, and one
|
|
58
|
+
* whose signature changed — a `paths` alias added or retargeted, an emit option, its override — leaves for the group
|
|
59
|
+
* of its new signature, a compiler made from the files as they are now. One whose signature did not change stays: a
|
|
60
|
+
* type-check-only edit compiles nothing again. One whose config cannot be built from the files now (a tsconfig saved
|
|
61
|
+
* half-written) stays too, compiling as before, and the next save evaluates it again. Returns whether this group still
|
|
62
|
+
* has a member.
|
|
63
|
+
*/
|
|
64
|
+
private reconfigure;
|
|
65
|
+
/** Take a member out of its group at once — its project's entries too, when it was the last to hold them. */
|
|
66
|
+
private detach;
|
|
54
67
|
private tellBuilt;
|
|
55
68
|
private closeGroup;
|
|
56
69
|
/** Stop every group. Resolves once each watcher has closed and stored its cache, or after `timeoutMs`. */
|
package/shared-build/host.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)[0]})
|
|
3
|
-
`)
|
|
4
|
-
`)),p=e.toldFiles.get(t)!==n;e.toldFiles.set(t,n),this.tell(t,{type:"built",ok:o.errorCount===0,changed:s,hash:o.hash,text:(0,shared_compilation_1.describeBuild)(o,{webpackVersion:e.webpackVersion,durationMs:r.durationMs,changed:s}),errors:o.errorCount,warnings:o.warningCount,durationMs:r.durationMs,sourceMaps:e.sourceMaps,...p?{files:o.files}:{}})}closeGroup(e){e.retry.dispose();for(const s of e.pausing.values())clearTimeout(s);e.pausing.clear();const t=e.compiler,r=e.watching;e.watching=void 0;const o=h(()=>{try{t?.close?.(()=>{})}catch{}},"closeCompiler");r?r.close(o):o()}stop(e=5e3){this.stopped=!0;const t=[...this.groups.values()];return this.groups.clear(),this.members.clear(),new Promise(r=>{let o=t.length;if(o===0)return r();const s=setTimeout(r,e);s.unref?.();for(const n of t){n.retry.dispose();for(const i of n.pausing.values())clearTimeout(i);const p=h(()=>{--o===0&&(clearTimeout(s),r())},"done"),l=n.compiler,c=h(()=>{try{l.close(()=>p())}catch{p()}},"closeCompiler");n.watching?n.watching.close(c):c()}})}}exports.BuildHost=BuildHost;
|
|
1
|
+
"use strict";var $=Object.defineProperty;var b=(l,e)=>$(l,"name",{value:e,configurable:!0});var j=Object.defineProperty,y=b((l,e)=>j(l,"name",{value:e,configurable:!0}),"y"),k=Object.defineProperty,h=y((l,e)=>k(l,"name",{value:e,configurable:!0}),"h");Object.defineProperty(exports,"__esModule",{value:!0}),exports.BuildHost=exports.DEFAULT_PAUSE_MS=void 0,exports.withEnv=withEnv,exports.buildTargetString=buildTargetString;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),path=tslib_1.__importStar(require("node:path")),checkout_cache_1=require("../checkout-cache"),config_files_1=require("../config-files"),compile_retry_1=require("../compile-retry"),dev_devtool_1=require("../dev-devtool"),create_webpack_dev_1=require("../create-webpack-dev"),perf_rules_plugin_1=require("../perf/perf-rules-plugin"),signature_1=require("./signature"),shared_compilation_1=require("./shared-compilation");exports.DEFAULT_PAUSE_MS=5e3;function withEnv(l,e){const t={...process.env};for(const o of Object.keys(process.env))delete process.env[o];for(const[o,s]of Object.entries(l))s!==void 0&&(process.env[o]=s);try{return e()}finally{for(const o of Object.keys(process.env))delete process.env[o];Object.assign(process.env,t)}}b(withEnv,"withEnv"),y(withEnv,"withEnv"),h(withEnv,"withEnv");function buildTargetString(l){return`${l.project}:${l.target}${l.configuration?`:${l.configuration}`:""}`}b(buildTargetString,"buildTargetString"),y(buildTargetString,"buildTargetString"),h(buildTargetString,"buildTargetString");const shortHash=h(l=>(0,node_crypto_1.createHash)("sha256").update(l).digest("hex").slice(0,16),"shortHash"),firstLine=h(l=>(l?.message??String(l)).split(`
|
|
2
|
+
`)[0],"firstLine");class BuildHost{static{b(this,"BuildHost")}static{y(this,"BuildHost")}static{h(this,"BuildHost")}constructor(e){this.opts=e,this.groups=new Map,this.members=new Map,this.stopped=!1,this.pauseMs=e.pauseMs??exports.DEFAULT_PAUSE_MS,this.webpack=e.webpack??require("webpack")}tell(e,t){this.opts.send({type:"member",member:e,event:t})}describe(){return[...this.groups.values()].map(e=>({group:e.key,projects:e.registry.projects()}))}evaluate(e){const t=e.options;if(t.webpackConfigPath)try{delete require.cache[require.resolve(path.resolve(t.appRoot,t.webpackConfigPath))]}catch{}const o={...e.env,NODE_ENV:"development",NX_BUILD_TARGET:buildTargetString(e),NX_WORKSPACE_ROOT:this.opts.root,LENSMCP_TYPECHECK:"off"},s=withEnv(o,()=>(0,create_webpack_dev_1.createDevWebpackConfig)(t)),i=(0,signature_1.transpileCompilerOptions)(t.tsConfig,this.opts.root),r=(0,signature_1.memberSignature)({join:e,devtool:s.devtool,config:s,compilerOptions:i});return{config:s,signature:r,configFiles:(0,config_files_1.configFilesOf)({...t,workspaceRoot:this.opts.root})}}join(e,t){if(this.stopped)return this.tell(e,{type:"refused",reason:"the build host is stopping"});this.leave(e,{quiet:!0});const o=(0,signature_1.refusalOf)(t,this.opts.root);if(o)return this.opts.log(`${t.project}: compiles on its own \u2014 ${o}`),this.tell(e,{type:"refused",reason:o});let s;try{s=this.evaluate(t)}catch(i){const r=`its webpack config could not be built here (${firstLine(i)})`;return this.opts.log(`${t.project}: compiles on its own \u2014 ${r}`),this.tell(e,{type:"refused",reason:r})}this.admit(e,t,s)}admit(e,t,o){let s=this.groups.get(o.signature.key);if(!s){for(const a of this.groups.values()){const p=(0,signature_1.describeDifference)(o.signature,a.signature);this.opts.log(`${t.project}: compiled apart from ${a.registry.projects().sort().join(", ")||"the other group"} \u2014 ${p??"its compile signature differs"}`);break}try{s=this.createGroup(o,t)}catch(a){const p=`the shared compiler could not be created (${firstLine(a)})`;return this.opts.log(`${t.project}: compiles on its own \u2014 ${p}`),this.tell(e,{type:"refused",reason:p})}this.groups.set(s.key,s)}this.members.set(e,{project:t.project,group:s,join:t});for(const a of o.configFiles)s.configFiles.add(a);const i=s.projects.get(t.project)??new Set;i.add(e);const r=!s.projects.has(t.project);s.projects.set(t.project,i);const c=s.pausing.get(t.project);if(c&&(clearTimeout(c),s.pausing.delete(t.project)),this.tell(e,{type:"accepted",group:s.key,projects:[...s.projects.keys()]}),r)s.registry.set(t.project,(0,shared_compilation_1.memberEntries)(t.project,t.options)),this.opts.log(`shared compilation ${s.key.slice(0,8)}: ${s.registry.projects().sort().join(", ")}`),this.kick(s);else{const a=s.last.get(t.project);a&&this.tellBuilt(s,e,a)}}leave(e,t={}){const o=this.members.get(e);if(!o)return;this.members.delete(e);const{project:s,group:i}=o;i.told.delete(e),i.toldFiles.delete(e);const r=i.projects.get(s);if(r?.delete(e),r&&r.size>0)return;t.quiet||this.opts.log(`${s}: no member left \u2014 its entries pause in ${Math.round(this.pauseMs/1e3)} s`);const c=setTimeout(()=>this.pause(i,s),this.pauseMs);c.unref?.(),i.pausing.set(s,c)}pause(e,t){if(e.pausing.delete(t),!((e.projects.get(t)?.size??0)>0)){if(e.projects.delete(t),e.registry.delete(t),e.last.delete(t),e.projects.size===0){this.opts.log(`shared compilation ${e.key.slice(0,8)}: no service left \u2014 closed`),this.groups.delete(e.key),this.closeGroup(e);return}this.opts.log(`shared compilation ${e.key.slice(0,8)}: ${t} paused \u2014 ${e.registry.projects().sort().join(", ")}`),this.kick(e)}}kick(e){if(e.watching){e.watching.invalidate();return}e.watching=e.compiler.watch(e.watchOptions,(t,o)=>this.onBuild(e,t,o))}createGroup(e,t){const o=e.signature.key,s=new shared_compilation_1.EntryRegistry,i=t.options,r=e.config,c=r.devtool,a=path.join((0,checkout_cache_1.checkoutCacheDir)(this.opts.root,"lensmcp-build"),o,"out"),p=(0,create_webpack_dev_1.devCacheOptions)(i,c),n={key:o,signature:e.signature,registry:s,projects:new Map,pausing:new Map,last:new Map,told:new Map,toldFiles:new Map,compiler:void 0,watching:void 0,watchOptions:{},sourceMaps:!1,webpackVersion:"",retry:(0,compile_retry_1.createFailureRetry)(()=>this.compileAgain(n),this.opts.retryDelaysMs??compile_retry_1.RETRY_AFTER_FAILURE_MS),configFiles:new Set(e.configFiles),configChanged:new Set},u=r.plugins.filter(d=>!signature_1.PER_SERVICE_PLUGINS.has(d?.constructor?.name));u.push(new perf_rules_plugin_1.PerfRulesPlugin({project:t.project,workspaceRoot:this.opts.root,tsConfig:i.tsConfig,entries:h(()=>s.entryProjects(),"entries")}),new shared_compilation_1.SharedEntriesPlugin({registry:s,workspaceRoot:this.opts.root,outputPath:a,cache:{...p,name:`shared-${o}`,version:`${p.version}-${o}`}}),new config_files_1.ConfigFilesPlugin({files:h(()=>n.configFiles,"files"),changed:h(d=>{for(const f of d)n.configChanged.add(f)},"changed")}),{apply:h(d=>{d.hooks.watchRun.tap("LensmcpSharedBuild",f=>{const v=[...f.modifiedFiles??[],...f.removedFiles??[]].filter(w=>typeof w=="string").slice(-32);for(const w of this.membersOf(n))this.tell(w,{type:"watch-run",paths:v})}),d.hooks.watchClose.tap("LensmcpSharedBuild",()=>{for(const f of this.membersOf(n))this.tell(f,{type:"watch-closed"})})},"apply")});const g={...r,plugins:u};delete g.watch,n.watchOptions=r.watchOptions??{};const m={...t.env,NODE_ENV:"development",NX_BUILD_TARGET:buildTargetString(t),NX_WORKSPACE_ROOT:this.opts.root,LENSMCP_TYPECHECK:"off"};return n.compiler=withEnv(m,()=>this.webpack(g)),n.sourceMaps=(0,dev_devtool_1.bundleCarriesSourceMaps)(n.compiler.options.devtool),n.webpackVersion=String(n.compiler.webpack?.version??"5"),n}membersOf(e){const t=[];for(const o of e.projects.values())t.push(...o);return t}compileAgain(e){this.stopped||this.groups.get(e.key)!==e||!e.watching||(this.opts.log(`shared compilation ${e.key.slice(0,8)}: compiling again after the failure`),e.watching.invalidate())}onBuild(e,t,o){if(this.stopped||e.configChanged.size>0&&!this.reconfigure(e))return;if(t){const n=t?.message??String(t),{delayMs:u,retry:g}=e.retry.failed();this.opts.log(`shared compilation ${e.key.slice(0,8)} failed: ${n} \u2014 compiling again in ${Math.round(u/1e3)} s (retry ${g})`);for(const m of this.membersOf(e))this.tell(m,{type:"failed",message:n});return}const s=e.retry.succeeded();s>0&&this.opts.log(`shared compilation ${e.key.slice(0,8)}: compiled again after ${s} failure${s===1?"":"s"} \u2014 watching again`);const i=o,r=Math.max(0,(i.endTime??0)-(i.startTime??0)),c=(0,shared_compilation_1.attributeCompilation)(i.compilation,e.registry),a=[];for(const[n,u]of c){e.last.get(n)?.attribution.hash!==u.hash&&a.push(n);const g={attribution:u,durationMs:r};e.last.set(n,g);for(const m of e.projects.get(n)??[])this.tellBuilt(e,m,g)}const p=[...c.values()].filter(n=>n.errorCount>0).map(n=>n.project);this.opts.log(`built ${[...c.keys()].sort().join(", ")||"nothing"} in ${r} ms \u2014 ${a.length?`changed: ${a.sort().join(", ")}`:"no bundle changed"}${p.length?`; errors in: ${p.sort().join(", ")}`:""}`)}reconfigure(e){const t=[...e.configChanged].map(c=>path.relative(this.opts.root,c)).sort().join(", ");e.configChanged.clear();const o=[];for(const[c,a]of this.members){if(a.group!==e)continue;let p;try{p=this.evaluate(a.join)}catch(n){this.opts.log(`${a.project}: ${t} changed, but its webpack config could not be built from it (${firstLine(n)}) \u2014 it compiles as before`);continue}for(const n of p.configFiles)e.configFiles.add(n);p.signature.key!==e.key&&o.push({member:c,join:a.join,evaluated:p})}const s=`shared compilation ${e.key.slice(0,8)}`;if(o.length===0)return this.opts.log(`${s}: ${t} changed \u2014 nothing it compiles with did`),!0;const i=[...new Set(o.map(c=>c.join.project))].sort();for(const c of o)this.detach(c.member);const r=e.projects.size>0;this.opts.log(`${s}: ${t} changed what ${i.join(", ")} compile${i.length===1?"s":""} with \u2014 a new compiler reads it${r?"":"; this one closes"}`),r||(this.groups.delete(e.key),setImmediate(()=>this.closeGroup(e)));for(const c of o)this.admit(c.member,c.join,c.evaluated);return r}detach(e){const t=this.members.get(e);if(!t)return;this.members.delete(e);const{project:o,group:s}=t;s.told.delete(e),s.toldFiles.delete(e);const i=s.projects.get(o);if(i?.delete(e),i&&i.size>0)return;s.projects.delete(o),s.registry.delete(o),s.last.delete(o);const r=s.pausing.get(o);r&&(clearTimeout(r),s.pausing.delete(o))}tellBuilt(e,t,o){const s=o.attribution,i=e.told.get(t)!==s.hash;e.told.set(t,s.hash);const r=shortHash([...s.files].sort().join(`
|
|
3
|
+
`)),c=e.toldFiles.get(t)!==r;e.toldFiles.set(t,r),this.tell(t,{type:"built",ok:s.errorCount===0,changed:i,hash:s.hash,text:(0,shared_compilation_1.describeBuild)(s,{webpackVersion:e.webpackVersion,durationMs:o.durationMs,changed:i}),errors:s.errorCount,warnings:s.warningCount,durationMs:o.durationMs,sourceMaps:e.sourceMaps,...c?{files:s.files}:{}})}closeGroup(e){e.retry.dispose();for(const i of e.pausing.values())clearTimeout(i);e.pausing.clear();const t=e.compiler,o=e.watching;e.watching=void 0;const s=h(()=>{try{t?.close?.(()=>{})}catch{}},"closeCompiler");o?o.close(s):s()}stop(e=5e3){this.stopped=!0;const t=[...this.groups.values()];return this.groups.clear(),this.members.clear(),new Promise(o=>{let s=t.length;if(s===0)return o();const i=setTimeout(o,e);i.unref?.();for(const r of t){r.retry.dispose();for(const n of r.pausing.values())clearTimeout(n);const c=h(()=>{--s===0&&(clearTimeout(i),o())},"done"),a=r.compiler,p=h(()=>{try{a.close(()=>c())}catch{c()}},"closeCompiler");r.watching?r.watching.close(p):p()}})}}exports.BuildHost=BuildHost;
|