@lensmcp/cluster 1.25.24 → 1.25.26

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.
Files changed (36) hide show
  1. package/compile-retry.d.ts +26 -0
  2. package/compile-retry.js +1 -0
  3. package/create-webpack-dev.js +1 -1
  4. package/devserver-notifier.d.ts +13 -2
  5. package/devserver-notifier.js +1 -1
  6. package/executors/gateway/gateway.impl.js +1 -1
  7. package/executors/gateway/runtime/control.d.ts +1 -0
  8. package/executors/gateway/runtime/control.js +2 -2
  9. package/executors/gateway/runtime/lens-children.js +1 -1
  10. package/executors/gateway/runtime/lifecycle.d.ts +18 -3
  11. package/executors/gateway/runtime/lifecycle.js +3 -3
  12. package/executors/gateway/runtime/observability.js +3 -2
  13. package/executors/gateway/runtime/orphan-reaper.d.ts +44 -0
  14. package/executors/gateway/runtime/orphan-reaper.js +2 -0
  15. package/executors/gateway/runtime/own-log.d.ts +71 -0
  16. package/executors/gateway/runtime/own-log.js +6 -4
  17. package/executors/gateway/runtime/server.js +2 -2
  18. package/executors/gateway/runtime/types.d.ts +16 -0
  19. package/executors/gateway/runtime/types.js +1 -1
  20. package/executors/serve/serve.impl.js +1 -1
  21. package/executors/serve/stop-on-signal.d.ts +19 -0
  22. package/executors/serve/stop-on-signal.js +1 -0
  23. package/main.devserver.js +7 -7
  24. package/package.json +4 -4
  25. package/process-name.d.ts +11 -2
  26. package/process-name.js +1 -1
  27. package/service-build-events.d.ts +2 -0
  28. package/service-build-events.js +2 -1
  29. package/shared-build/host.d.ts +7 -0
  30. package/shared-build/host.js +4 -4
  31. package/tsgo-check-plugin.d.ts +23 -0
  32. package/tsgo-check-plugin.js +5 -5
  33. package/typecheck-bus.d.ts +24 -0
  34. package/typecheck-bus.js +3 -3
  35. package/typecheck-queue.d.ts +24 -1
  36. package/typecheck-queue.js +1 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Compiling again after webpack failed outright (issues/closed/foodguard-91).
3
+ *
4
+ * A compile that throws — not a module with errors, but the compiler itself: ENOSPC writing the bundle or the cache on
5
+ * a full disk — ends webpack's watch for good. `Watching._done` hands the error to the callback and returns before the
6
+ * `this.watch(...)` that would watch the files again (webpack/lib/Watching.js), so nothing it builds ever changes after
7
+ * that: FoodGuard's shared compilation sat failed from 21:01:53Z, though space was free again minutes later. Its
8
+ * paused watcher still collects what changes meanwhile, so `invalidate()` — this module's `again` — compiles with those
9
+ * changes, and a compile that succeeds watches again.
10
+ *
11
+ * So: after a failure, compile again on a backoff — 2 s, 5 s, 15 s, 30 s, then every minute — until one succeeds.
12
+ */
13
+ /** The waits before each attempt after a failure; the last repeats. */
14
+ export declare const RETRY_AFTER_FAILURE_MS: readonly number[];
15
+ export interface FailureRetry {
16
+ /** A compile failed: the next attempt is scheduled (one at a time). Returns the wait, and which retry in a row it is. */
17
+ failed(): {
18
+ delayMs: number;
19
+ retry: number;
20
+ };
21
+ /** A compile succeeded. Returns how many failures in a row it ends (0: there were none). */
22
+ succeeded(): number;
23
+ /** Nothing more is attempted — the watcher is closing. */
24
+ dispose(): void;
25
+ }
26
+ export declare function createFailureRetry(again: () => void, delays?: readonly number[]): FailureRetry;
@@ -0,0 +1 @@
1
+ "use strict";var c=Object.defineProperty;var u=(t,r)=>c(t,"name",{value:r,configurable:!0});var d=Object.defineProperty,s=u((t,r)=>d(t,"name",{value:r,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.RETRY_AFTER_FAILURE_MS=void 0,exports.createFailureRetry=createFailureRetry,exports.RETRY_AFTER_FAILURE_MS=[2e3,5e3,15e3,3e4,6e4];function createFailureRetry(t,r=exports.RETRY_AFTER_FAILURE_MS){let i=0,e,a=!1;return{failed(){i+=1;const o=r[Math.min(i,r.length)-1]??6e4;return e&&clearTimeout(e),a||(e=setTimeout(()=>{e=void 0,a||t()},o),e.unref?.()),{delayMs:o,retry:i}},succeeded(){const o=i;return i=0,e&&(clearTimeout(e),e=void 0),o},dispose(){a=!0,e&&(clearTimeout(e),e=void 0)}}}u(createFailureRetry,"createFailureRetry"),s(createFailureRetry,"createFailureRetry");
@@ -1 +1 @@
1
- "use strict";var T=Object.defineProperty;var p=(r,e)=>T(r,"name",{value:e,configurable:!0});var O=Object.defineProperty,c=p((r,e)=>O(r,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DevBuildFinalizePlugin=exports.DevServerReloadPlugin=exports.devserverControlSock=exports.DevserverNotifier=void 0,exports.devCacheOptions=devCacheOptions,exports.devModuleFilename=devModuleFilename,exports.createDevWebpackConfig=createDevWebpackConfig;const tslib_1=require("tslib"),app_plugin_1=require("@nx/webpack/app-plugin"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),webpack_node_externals_1=tslib_1.__importDefault(require("webpack-node-externals")),build_scope_patterns_1=require("./build-scope-patterns"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),tsgo_check_plugin_1=require("./tsgo-check-plugin"),fork_ts_check_reporter_1=require("./fork-ts-check-reporter"),perf_rules_plugin_1=require("./perf/perf-rules-plugin"),devserver_notifier_1=require("./devserver-notifier"),workspace_paths_1=require("./workspace-paths");var devserver_notifier_2=require("./devserver-notifier");Object.defineProperty(exports,"DevserverNotifier",{enumerable:!0,get:c(function(){return devserver_notifier_2.DevserverNotifier},"get")}),Object.defineProperty(exports,"devserverControlSock",{enumerable:!0,get:c(function(){return devserver_notifier_2.devserverControlSock},"get")});const webpack=require("webpack");class DevServerReloadPlugin{static{p(this,"DevServerReloadPlugin")}static{c(this,"DevServerReloadPlugin")}constructor(e,n=!1,a,o=Number(process.env.LENSMCP_GATEWAY_PID??0)>0){this.notifier=new devserver_notifier_1.DevserverNotifier(e,n,a,o)}snapshot(){return this.notifier.snapshot()}apply(e){const n="DevServerReloadPlugin";let a=!1;e.hooks.watchRun?.tap(n,o=>{const s=[...o?.modifiedFiles??[],...o?.removedFiles??[]].filter(i=>typeof i=="string");if(!a){const i=o?.watchFileSystem?.watcher;i&&typeof i.on=="function"&&(a=!0,i.on("error",l=>this.notifier.degrade(`watcher error: ${l?.message??String(l)}`)))}this.notifier.watchRun(s)}),e.hooks.watchClose?.tap(n,()=>this.notifier.degrade("watch closed")),e.hooks.failed?.tap(n,o=>this.notifier.degrade(`compiler failed: ${o?.message??String(o)}`)),e.hooks.done.tap(n,async o=>{if(o.hasErrors()){this.notifier.failed();return}await this.notifier.built()})}}exports.DevServerReloadPlugin=DevServerReloadPlugin;const DEV_CACHE_MEMORY_GENERATIONS=5,DEV_CACHE_MAX_AGE_MS=10080*60*1e3,LOCKFILES=["yarn.lock","package-lock.json","pnpm-lock.yaml","bun.lock"];function clusterVersion(){for(const r of[path.join(__dirname,"package.json"),path.join(__dirname,"..","package.json")])try{const e=JSON.parse(fs.readFileSync(r,"utf8"));if(e.name==="@lensmcp/cluster"&&e.version)return e.version}catch{}return"unknown"}p(clusterVersion,"clusterVersion"),c(clusterVersion,"clusterVersion");function devCacheOptions(r,e){const{appName:n,appRoot:a,workspaceRoot:o,tsConfig:s,webpackConfigPath:i}=r,l=clusterVersion(),_=JSON.stringify({lensmcp:l,devtool:e,buildLibsFromSource:r.buildLibsFromSource??!0,orgScopes:r.orgScopes??[],bundlePackages:r.bundlePackages??[],nodeExternalsConfig:r.nodeExternalsConfig??null}),f=c(g=>g.filter(d=>fs.existsSync(d)),"existing"),k=(0,workspace_paths_1.readWorkspacePaths)(s,o)?.configFiles??[path.resolve(o,s)],v={lensmcp:[__filename],lockfile:f(LOCKFILES.map(g=>path.join(o,g))),tsconfig:f([...new Set([...k,path.join(o,"tsconfig.base.json")])])};return i&&(v.overrides=[path.resolve(a,i)]),{type:"filesystem",cacheDirectory:(0,checkout_cache_1.checkoutCacheDir)(o,"lensmcp-webpack"),name:n.replace(/[^\w.-]+/g,"_"),version:`${l}-${(0,node_crypto_1.createHash)("sha256").update(_).digest("hex").slice(0,16)}`,buildDependencies:v,maxMemoryGenerations:DEV_CACHE_MEMORY_GENERATIONS,maxAge:DEV_CACHE_MAX_AGE_MS}}p(devCacheOptions,"devCacheOptions"),c(devCacheOptions,"devCacheOptions");function devModuleFilename(r){const e=r.absoluteResourcePath;return path.isAbsolute(e)?`file://${e}`:e}p(devModuleFilename,"devModuleFilename"),c(devModuleFilename,"devModuleFilename");class DevBuildFinalizePlugin{static{p(this,"DevBuildFinalizePlugin")}static{c(this,"DevBuildFinalizePlugin")}constructor(e){this.options=e}apply(e){const{cache:n}=this.options;e.options.cache={...n,buildDependencies:{...n.buildDependencies}},e.options.context&&(e.context=e.options.context);const a=e.options.resolve?.plugins??[];if(a.some(o=>o instanceof workspace_paths_1.WorkspacePathsResolverPlugin)){const o=new Set(a);e.hooks.afterPlugins.tap("DevBuildFinalizePlugin",()=>{const s=e.options.resolve;s.plugins=s.plugins.filter(i=>o.has(i)||i?.constructor?.name!=="TsconfigPathsPlugin")})}if(this.options.tsSourceMaps)for(const o of e.options.module?.rules??[])typeof o?.loader!="string"||!/[\\/]ts-loader[\\/]/.test(o.loader)||(o.options={...o.options,compilerOptions:{...o.options?.compilerOptions,sourceMap:!0,inlineSourceMap:!1}})}}exports.DevBuildFinalizePlugin=DevBuildFinalizePlugin;function createDevWebpackConfig(r){const{appRoot:e,outputDir:n,main:a,tsConfig:o,workspaceRoot:s,assets:i=[],port:l,memoryLimit:_=8192,buildLibsFromSource:f=!0,orgScopes:k=[],bundlePackages:v=[],additionalEntryPoints:g=[],nodeExternalsConfig:d,webpackConfigPath:b}=r,{allowlistPatterns:y,scopePrefixes:x,scopePatterns:M}=(0,build_scope_patterns_1.buildScopePatterns)(k),C=process.env.LENSMCP_TYPECHECK,S=C==="tsgo"?(0,tsgo_check_plugin_1.resolveTsgoBin)(s,process.env.LENSMCP_TSGO_BIN):null;if(C==="tsgo"&&!S){const t=`${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only \u2014 so typecheck:// has no producer for this project until it is fixed`;console.warn(`[serve] ${r.appName}: LENSMCP_TYPECHECK=tsgo but ${t}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:r.appName,tsConfig:o,workspaceRoot:s,detail:t})}const w=!!S,P=(0,dev_devtool_1.resolveDevDevtool)(r.devtool);P.rejected&&console.warn(`[serve] ${r.appName}: '${P.rejected}' (devtool / ${dev_devtool_1.DEV_DEVTOOL_ENV}) is not a webpack devtool \u2014 building with '${dev_devtool_1.DEFAULT_DEV_DEVTOOL}'.`);const h=P.devtool,E=f?(0,workspace_paths_1.readWorkspacePaths)(o,s):null,D=v.map(t=>new RegExp(`^${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(/|$)`)),R=[/webpack\/hot\/poll\?100/,...y,...D,...d?.allowlist||[]],N={output:{path:n,...process.env.NODE_ENV!=="production"&&{devtoolModuleFilenameTemplate:devModuleFilename},clean:!0},externals:[(0,webpack_node_externals_1.default)({allowlist:R,additionalModuleDirs:d?.additionalModuleDirs||[],...d?.importType&&{importType:d.importType}}),({request:t},m)=>{if(t&&path.isAbsolute(t)&&t.startsWith(s)&&!t.includes("node_modules")||t&&(t.startsWith(path.join(s,"apps"))||t.startsWith(path.join(s,"libs")))||t&&x.some(u=>t.startsWith(u))||t&&M.some(u=>u.test(t))||t&&D.some(u=>u.test(t)))return m();if(t&&!(t.startsWith("./")||t.startsWith(".."))){const u=t.startsWith("@")?t.split("/").slice(0,2).join("/"):t.split("/")[0];return fs.existsSync(path.join(s,"node_modules",u))?m(null,`commonjs ${t}`):m()}return m()}],mode:"development",devtool:h,...(0,workspace_paths_1.hasPathMappings)(E)&&{resolve:{plugins:[new workspace_paths_1.WorkspacePathsResolverPlugin(E)]}},plugins:[new webpack.HotModuleReplacementPlugin,new app_plugin_1.NxAppWebpackPlugin({target:"node22",compiler:"tsc",main:a,additionalEntryPoints:g,verbose:!0,sourceMap:h,mergeExternals:!0,externalDependencies:[],memoryLimit:_,tsConfig:o,assets:i,optimization:!1,progress:!1,outputHashing:"none",generatePackageJson:!1,watchDependencies:!0,typeCheckOptions:process.env.LENSMCP_TYPECHECK==="off"||w?!1:{async:!0},buildLibsFromSource:f}),new DevBuildFinalizePlugin({cache:devCacheOptions(r,h),tsSourceMaps:(0,dev_devtool_1.bundleCarriesSourceMaps)(h)}),new DevServerReloadPlugin(l,r.httpsReload===!0,(0,devserver_notifier_1.devserverControlSock)(r.serviceName)),new perf_rules_plugin_1.PerfRulesPlugin({project:process.env.LENSMCP_PROJECT||r.appName,workspaceRoot:s,tsConfig:o}),...w?[new tsgo_check_plugin_1.TsgoCheckPlugin({appName:r.appName,tsConfig:o,workspaceRoot:s,...process.env.LENSMCP_TSGO_BIN?{binPath:process.env.LENSMCP_TSGO_BIN}:{}})]:process.env.LENSMCP_TYPECHECK==="off"?[]:[new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:r.appName,tsConfig:o,workspaceRoot:s})]],snapshot:{managedPaths:[/^(.+?[\\/])?node_modules[\\/]/]},watch:!0,watchOptions:{ignored:["**/*.env.template","**/config.template.json","**/*.md","**/dist/**","**/migrations/**"]}};if(b){const t=require(path.resolve(e,b));return(t.default||t)(N)}return N}p(createDevWebpackConfig,"createDevWebpackConfig"),c(createDevWebpackConfig,"createDevWebpackConfig");
1
+ "use strict";var T=Object.defineProperty;var p=(r,e)=>T(r,"name",{value:e,configurable:!0});var O=Object.defineProperty,c=p((r,e)=>O(r,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DevBuildFinalizePlugin=exports.DevServerReloadPlugin=exports.devserverControlSock=exports.DevserverNotifier=void 0,exports.devCacheOptions=devCacheOptions,exports.devModuleFilename=devModuleFilename,exports.createDevWebpackConfig=createDevWebpackConfig;const tslib_1=require("tslib"),app_plugin_1=require("@nx/webpack/app-plugin"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),webpack_node_externals_1=tslib_1.__importDefault(require("webpack-node-externals")),build_scope_patterns_1=require("./build-scope-patterns"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),tsgo_check_plugin_1=require("./tsgo-check-plugin"),fork_ts_check_reporter_1=require("./fork-ts-check-reporter"),perf_rules_plugin_1=require("./perf/perf-rules-plugin"),devserver_notifier_1=require("./devserver-notifier"),workspace_paths_1=require("./workspace-paths");var devserver_notifier_2=require("./devserver-notifier");Object.defineProperty(exports,"DevserverNotifier",{enumerable:!0,get:c(function(){return devserver_notifier_2.DevserverNotifier},"get")}),Object.defineProperty(exports,"devserverControlSock",{enumerable:!0,get:c(function(){return devserver_notifier_2.devserverControlSock},"get")});const webpack=require("webpack");class DevServerReloadPlugin{static{p(this,"DevServerReloadPlugin")}static{c(this,"DevServerReloadPlugin")}constructor(e,i=!1,a,o=Number(process.env.LENSMCP_GATEWAY_PID??0)>0){this.notifier=new devserver_notifier_1.DevserverNotifier(e,i,a,o)}snapshot(){return this.notifier.snapshot()}apply(e){const i="DevServerReloadPlugin";let a=!1;e.hooks.watchRun?.tap(i,o=>{const s=[...o?.modifiedFiles??[],...o?.removedFiles??[]].filter(n=>typeof n=="string");if(!a){const n=o?.watchFileSystem?.watcher;n&&typeof n.on=="function"&&(a=!0,n.on("error",l=>this.notifier.degrade(`watcher error: ${l?.message??String(l)}`)))}this.notifier.watchRun(s)}),e.hooks.watchClose?.tap(i,()=>this.notifier.degrade("watch closed")),e.hooks.failed?.tap(i,o=>this.notifier.failed(`compiler failed: ${o?.message??String(o)}`)),e.hooks.done.tap(i,async o=>{if(o.hasErrors()){this.notifier.failed();return}await this.notifier.built()})}}exports.DevServerReloadPlugin=DevServerReloadPlugin;const DEV_CACHE_MEMORY_GENERATIONS=5,DEV_CACHE_MAX_AGE_MS=10080*60*1e3,LOCKFILES=["yarn.lock","package-lock.json","pnpm-lock.yaml","bun.lock"];function clusterVersion(){for(const r of[path.join(__dirname,"package.json"),path.join(__dirname,"..","package.json")])try{const e=JSON.parse(fs.readFileSync(r,"utf8"));if(e.name==="@lensmcp/cluster"&&e.version)return e.version}catch{}return"unknown"}p(clusterVersion,"clusterVersion"),c(clusterVersion,"clusterVersion");function devCacheOptions(r,e){const{appName:i,appRoot:a,workspaceRoot:o,tsConfig:s,webpackConfigPath:n}=r,l=clusterVersion(),_=JSON.stringify({lensmcp:l,devtool:e,buildLibsFromSource:r.buildLibsFromSource??!0,orgScopes:r.orgScopes??[],bundlePackages:r.bundlePackages??[],nodeExternalsConfig:r.nodeExternalsConfig??null}),f=c(g=>g.filter(d=>fs.existsSync(d)),"existing"),k=(0,workspace_paths_1.readWorkspacePaths)(s,o)?.configFiles??[path.resolve(o,s)],v={lensmcp:[__filename],lockfile:f(LOCKFILES.map(g=>path.join(o,g))),tsconfig:f([...new Set([...k,path.join(o,"tsconfig.base.json")])])};return n&&(v.overrides=[path.resolve(a,n)]),{type:"filesystem",cacheDirectory:(0,checkout_cache_1.checkoutCacheDir)(o,"lensmcp-webpack"),name:i.replace(/[^\w.-]+/g,"_"),version:`${l}-${(0,node_crypto_1.createHash)("sha256").update(_).digest("hex").slice(0,16)}`,buildDependencies:v,maxMemoryGenerations:DEV_CACHE_MEMORY_GENERATIONS,maxAge:DEV_CACHE_MAX_AGE_MS}}p(devCacheOptions,"devCacheOptions"),c(devCacheOptions,"devCacheOptions");function devModuleFilename(r){const e=r.absoluteResourcePath;return path.isAbsolute(e)?`file://${e}`:e}p(devModuleFilename,"devModuleFilename"),c(devModuleFilename,"devModuleFilename");class DevBuildFinalizePlugin{static{p(this,"DevBuildFinalizePlugin")}static{c(this,"DevBuildFinalizePlugin")}constructor(e){this.options=e}apply(e){const{cache:i}=this.options;e.options.cache={...i,buildDependencies:{...i.buildDependencies}},e.options.context&&(e.context=e.options.context);const a=e.options.resolve?.plugins??[];if(a.some(o=>o instanceof workspace_paths_1.WorkspacePathsResolverPlugin)){const o=new Set(a);e.hooks.afterPlugins.tap("DevBuildFinalizePlugin",()=>{const s=e.options.resolve;s.plugins=s.plugins.filter(n=>o.has(n)||n?.constructor?.name!=="TsconfigPathsPlugin")})}if(this.options.tsSourceMaps)for(const o of e.options.module?.rules??[])typeof o?.loader!="string"||!/[\\/]ts-loader[\\/]/.test(o.loader)||(o.options={...o.options,compilerOptions:{...o.options?.compilerOptions,sourceMap:!0,inlineSourceMap:!1}})}}exports.DevBuildFinalizePlugin=DevBuildFinalizePlugin;function createDevWebpackConfig(r){const{appRoot:e,outputDir:i,main:a,tsConfig:o,workspaceRoot:s,assets:n=[],port:l,memoryLimit:_=8192,buildLibsFromSource:f=!0,orgScopes:k=[],bundlePackages:v=[],additionalEntryPoints:g=[],nodeExternalsConfig:d,webpackConfigPath:b}=r,{allowlistPatterns:y,scopePrefixes:x,scopePatterns:M}=(0,build_scope_patterns_1.buildScopePatterns)(k),C=process.env.LENSMCP_TYPECHECK,S=C==="tsgo"?(0,tsgo_check_plugin_1.resolveTsgoBin)(s,process.env.LENSMCP_TSGO_BIN):null;if(C==="tsgo"&&!S){const t=`${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only \u2014 so typecheck:// has no producer for this project until it is fixed`;console.warn(`[serve] ${r.appName}: LENSMCP_TYPECHECK=tsgo but ${t}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:r.appName,tsConfig:o,workspaceRoot:s,detail:t})}const w=!!S,P=(0,dev_devtool_1.resolveDevDevtool)(r.devtool);P.rejected&&console.warn(`[serve] ${r.appName}: '${P.rejected}' (devtool / ${dev_devtool_1.DEV_DEVTOOL_ENV}) is not a webpack devtool \u2014 building with '${dev_devtool_1.DEFAULT_DEV_DEVTOOL}'.`);const h=P.devtool,E=f?(0,workspace_paths_1.readWorkspacePaths)(o,s):null,D=v.map(t=>new RegExp(`^${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(/|$)`)),R=[/webpack\/hot\/poll\?100/,...y,...D,...d?.allowlist||[]],N={output:{path:i,...process.env.NODE_ENV!=="production"&&{devtoolModuleFilenameTemplate:devModuleFilename},clean:!0},externals:[(0,webpack_node_externals_1.default)({allowlist:R,additionalModuleDirs:d?.additionalModuleDirs||[],...d?.importType&&{importType:d.importType}}),({request:t},m)=>{if(t&&path.isAbsolute(t)&&t.startsWith(s)&&!t.includes("node_modules")||t&&(t.startsWith(path.join(s,"apps"))||t.startsWith(path.join(s,"libs")))||t&&x.some(u=>t.startsWith(u))||t&&M.some(u=>u.test(t))||t&&D.some(u=>u.test(t)))return m();if(t&&!(t.startsWith("./")||t.startsWith(".."))){const u=t.startsWith("@")?t.split("/").slice(0,2).join("/"):t.split("/")[0];return fs.existsSync(path.join(s,"node_modules",u))?m(null,`commonjs ${t}`):m()}return m()}],mode:"development",devtool:h,...(0,workspace_paths_1.hasPathMappings)(E)&&{resolve:{plugins:[new workspace_paths_1.WorkspacePathsResolverPlugin(E)]}},plugins:[new webpack.HotModuleReplacementPlugin,new app_plugin_1.NxAppWebpackPlugin({target:"node22",compiler:"tsc",main:a,additionalEntryPoints:g,verbose:!0,sourceMap:h,mergeExternals:!0,externalDependencies:[],memoryLimit:_,tsConfig:o,assets:n,optimization:!1,progress:!1,outputHashing:"none",generatePackageJson:!1,watchDependencies:!0,typeCheckOptions:process.env.LENSMCP_TYPECHECK==="off"||w?!1:{async:!0},buildLibsFromSource:f}),new DevBuildFinalizePlugin({cache:devCacheOptions(r,h),tsSourceMaps:(0,dev_devtool_1.bundleCarriesSourceMaps)(h)}),new DevServerReloadPlugin(l,r.httpsReload===!0,(0,devserver_notifier_1.devserverControlSock)(r.serviceName)),new perf_rules_plugin_1.PerfRulesPlugin({project:process.env.LENSMCP_PROJECT||r.appName,workspaceRoot:s,tsConfig:o}),...w?[new tsgo_check_plugin_1.TsgoCheckPlugin({appName:r.appName,tsConfig:o,workspaceRoot:s,...process.env.LENSMCP_TSGO_BIN?{binPath:process.env.LENSMCP_TSGO_BIN}:{}})]:process.env.LENSMCP_TYPECHECK==="off"?[]:[new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:r.appName,tsConfig:o,workspaceRoot:s})]],snapshot:{managedPaths:[/^(.+?[\\/])?node_modules[\\/]/]},watch:!0,watchOptions:{ignored:["**/*.env.template","**/config.template.json","**/*.md","**/dist/**","**/migrations/**"]}};if(b){const t=require(path.resolve(e,b));return(t.default||t)(N)}return N}p(createDevWebpackConfig,"createDevWebpackConfig"),c(createDevWebpackConfig,"createDevWebpackConfig");
@@ -49,8 +49,14 @@ export declare class DevserverNotifier {
49
49
  watchRun(paths: string[]): void;
50
50
  /** The open build as it goes on the wire. */
51
51
  private buildAccount;
52
- /** A build finished WITH errors: nothing is swapped, and the devserver is told the build is over — and how. */
53
- failed(): void;
52
+ /**
53
+ * A build finished WITH errors, or the compiler failed outright (`error`): nothing is swapped, and the devserver is
54
+ * told the build is over — and how. A compiler failure is a FAILED BUILD, never a degraded watcher: the one that
55
+ * compiles again is the compiler's owner (compile-retry.ts), and a `degrade` made the gateway recycle the pods for it
56
+ * (issues/closed/foodguard-91). A failure with no build open — the compile a retry started — still ends one, so the
57
+ * devserver publishes it (`service-build failed`).
58
+ */
59
+ failed(error?: string): void;
54
60
  /** The watcher can no longer be trusted to see every change. */
55
61
  degrade(reason: string): void;
56
62
  /** Send the testimony as it stands. */
@@ -84,3 +90,8 @@ export interface BuildAccountWire {
84
90
  changedFiles: string[];
85
91
  moreChanged?: number;
86
92
  }
93
+ /** A build that ended with errors, as `failed` sends it: its account, when it ended, and — the compiler failed outright — why. */
94
+ export interface FailedBuildWire extends BuildAccountWire {
95
+ endedAt: number;
96
+ error?: string;
97
+ }
@@ -1 +1 @@
1
- "use strict";var p=Object.defineProperty;var u=(r,t)=>p(r,"name",{value:t,configurable:!0});var m=Object.defineProperty,a=u((r,t)=>m(r,"name",{value:t,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{u(this,"DevserverNotifier")}static{a(this,"DevserverNotifier")}static{this.RECENT_PATH_CAP=32}static{this.BUILD_PATH_CAP=50}constructor(t,e=!1,s,d=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=d,this.secure=e,this.base=`${e?"https":"http"}://localhost:${t}`,this.controlSock=s,this.generation=c}post(t,e){const s=JSON.stringify(e),d={"content-type":"application/json",...this.generation!==void 0?{[notice_generation_1.NOTICE_GENERATION_HEADER]:String(this.generation)}:{}},c=a((i,n,o)=>{if(i.statusCode!==409||this.generation===void 0){i.resume(),i.on("end",n);return}let h="";i.setEncoding("utf8"),i.on("data",l=>{h+=l}),i.on("end",()=>{let l;try{l=JSON.parse(h).generation}catch{}o(new notice_generation_1.NotThisGeneration(this.generation,l))})},"answered"),v=a(()=>new Promise((i,n)=>{const o=(this.secure?require("node:https"):require("node:http")).request(`${this.base}${t}`,{method:"POST",headers:d,rejectUnauthorized:!1},h=>c(h,i,n));o.on("error",n),o.end(s)}),"viaTcp"),g=this.controlSock;return!g||!fs.existsSync(g)?this.managed?Promise.reject(new Error("the devserver control socket is not up yet")):v():new Promise((i,n)=>{const o=require("node:http").request({socketPath:g,path:t,method:"POST",headers:d},h=>c(h,i,n));o.on("error",n),o.end(s)}).catch(i=>{if(this.managed||i instanceof notice_generation_1.NotThisGeneration)throw i;return v()})}snapshot(){return{...this.testimony,recentPaths:[...this.testimony.recentPaths]}}watchRun(t){if(this.testimony.lastWatchRunAt=Date.now(),t.length){const e=[...this.testimony.recentPaths,...t];this.testimony.recentPaths=e.slice(-DevserverNotifier.RECENT_PATH_CAP)}this.building??={startedAt:this.testimony.lastWatchRunAt,changed:new Set,more:0};for(const e of t)this.building.changed.has(e)||(this.building.changed.size>=DevserverNotifier.BUILD_PATH_CAP?this.building.more+=1:this.building.changed.add(e));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(){const t=this.buildAccount();this.building=void 0,delete this.testimony.building;const e=Date.now();this.testimony.lastFailedAt=e,this.post("/webpack/watching",{...this.testimony,...t?{failed:{...t,endedAt:e}}:{}}).catch(()=>{})}degrade(t){this.testimony.degraded=t,this.tellWatching()}tellWatching(){this.post("/webpack/watching",this.testimony).catch(()=>{})}async built(t=!0){this.testimony.lastBuildAt=Date.now(),this.testimony.buildsSinceSpawn+=1;const e=this.buildAccount();if(this.building=void 0,delete this.testimony.building,!t){this.tellWatching();return}try{await this.post("/webpack/reload",{watcher:this.testimony,...e?{build:{...e,endedAt:this.testimony.lastBuildAt}}:{}}),console.log("[DevServerReloadPlugin] notified devserver to reload")}catch(s){s instanceof notice_generation_1.NotThisGeneration?console.log(`[DevServerReloadPlugin] not this generation's devserver (${s.message}) \u2014 not reloaded; this generation's devserver loads the build when it starts`):console.log("[DevServerReloadPlugin] devserver not running yet")}}}exports.DevserverNotifier=DevserverNotifier;function devserverControlSock(r,t=process.env){const e=t.LENSMCP_SOCK_NS||t.LENSMCP_WS_KEY||"",s=e?path.join(os.tmpdir(),e,`${r}-devserver`):path.join(os.tmpdir(),`${r}-devserver`);return path.join(s,"parent.sock")}u(devserverControlSock,"devserverControlSock"),a(devserverControlSock,"devserverControlSock");
1
+ "use strict";var p=Object.defineProperty;var u=(h,t)=>p(h,"name",{value:t,configurable:!0});var m=Object.defineProperty,a=u((h,t)=>m(h,"name",{value:t,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{u(this,"DevserverNotifier")}static{a(this,"DevserverNotifier")}static{this.RECENT_PATH_CAP=32}static{this.BUILD_PATH_CAP=50}constructor(t,e=!1,s,n=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=n,this.secure=e,this.base=`${e?"https":"http"}://localhost:${t}`,this.controlSock=s,this.generation=c}post(t,e){const s=JSON.stringify(e),n={"content-type":"application/json",...this.generation!==void 0?{[notice_generation_1.NOTICE_GENERATION_HEADER]:String(this.generation)}:{}},c=a((i,o,r)=>{if(i.statusCode!==409||this.generation===void 0){i.resume(),i.on("end",o);return}let d="";i.setEncoding("utf8"),i.on("data",l=>{d+=l}),i.on("end",()=>{let l;try{l=JSON.parse(d).generation}catch{}r(new notice_generation_1.NotThisGeneration(this.generation,l))})},"answered"),v=a(()=>new Promise((i,o)=>{const r=(this.secure?require("node:https"):require("node:http")).request(`${this.base}${t}`,{method:"POST",headers:n,rejectUnauthorized:!1},d=>c(d,i,o));r.on("error",o),r.end(s)}),"viaTcp"),g=this.controlSock;return!g||!fs.existsSync(g)?this.managed?Promise.reject(new Error("the devserver control socket is not up yet")):v():new Promise((i,o)=>{const r=require("node:http").request({socketPath:g,path:t,method:"POST",headers:n},d=>c(d,i,o));r.on("error",o),r.end(s)}).catch(i=>{if(this.managed||i instanceof notice_generation_1.NotThisGeneration)throw i;return v()})}snapshot(){return{...this.testimony,recentPaths:[...this.testimony.recentPaths]}}watchRun(t){if(this.testimony.lastWatchRunAt=Date.now(),t.length){const e=[...this.testimony.recentPaths,...t];this.testimony.recentPaths=e.slice(-DevserverNotifier.RECENT_PATH_CAP)}this.building??={startedAt:this.testimony.lastWatchRunAt,changed:new Set,more:0};for(const e of t)this.building.changed.has(e)||(this.building.changed.size>=DevserverNotifier.BUILD_PATH_CAP?this.building.more+=1:this.building.changed.add(e));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(t){const e=Date.now(),s=this.buildAccount()??(t!==void 0?{startedAt:e,changedFiles:[]}:void 0);this.building=void 0,delete this.testimony.building,this.testimony.lastFailedAt=e;const n=s?{...s,endedAt:e,...t?{error:t.slice(0,FAILURE_TEXT_CAP)}:{}}:void 0;this.post("/webpack/watching",{...this.testimony,...n?{failed:n}:{}}).catch(()=>{})}degrade(t){this.testimony.degraded=t,this.tellWatching()}tellWatching(){this.post("/webpack/watching",this.testimony).catch(()=>{})}async built(t=!0){this.testimony.lastBuildAt=Date.now(),this.testimony.buildsSinceSpawn+=1;const e=this.buildAccount();if(this.building=void 0,delete this.testimony.building,!t){this.tellWatching();return}try{await this.post("/webpack/reload",{watcher:this.testimony,...e?{build:{...e,endedAt:this.testimony.lastBuildAt}}:{}}),console.log("[DevServerReloadPlugin] notified devserver to reload")}catch(s){s instanceof notice_generation_1.NotThisGeneration?console.log(`[DevServerReloadPlugin] not this generation's devserver (${s.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(h,t=process.env){const e=t.LENSMCP_SOCK_NS||t.LENSMCP_WS_KEY||"",s=e?path.join(os.tmpdir(),e,`${h}-devserver`):path.join(os.tmpdir(),`${h}-devserver`);return path.join(s,"parent.sock")}u(devserverControlSock,"devserverControlSock"),a(devserverControlSock,"devserverControlSock");
@@ -1 +1 @@
1
- "use strict";var d=Object.defineProperty;var p=(t,r)=>d(t,"name",{value:r,configurable:!0});var l=Object.defineProperty,n=p((t,r)=>l(t,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=gatewayExecutor;const gateway_lib_1=require("./gateway.lib"),project_graph_store_1=require("./runtime/project-graph-store"),server_1=require("./runtime/server");async function gatewayExecutor(t,r){process.on("uncaughtException",e=>{if((e.syscall==="read"||e.syscall==="write")&&server_1.BENIGN_SOCKET_ERRNOS.has(e.code??"")){console.warn(`[gateway] survived a stray socket ${e.syscall} error (${e.code}) \u2014 one connection dropped, the daemon lives`);return}console.error(e),process.exit(1)});let i;try{const e=(0,project_graph_store_1.createProjectGraphStore)(r.root,()=>(0,project_graph_store_1.computeProjectGraphInProcess)(r.root)),o=(await e.refresh())?.projectGraph,s=o?Object.fromEntries(Object.entries(o.dependencies).map(([c,a])=>[c,a.map(u=>u.target)])):void 0;i=await(0,gateway_lib_1.startGateway)(t,{...r,...s?{projectDependencies:s}:{},projectGraphStore:e})}catch(e){return console.error(e.message),{success:!1}}return new Promise(e=>{let o=!1;const s=n(c=>{if(o)return;o=!0;let a=!0;try{process.kill(process.ppid,0)}catch{a=!1}console.log(`[gateway] received ${c} (pid ${process.pid}, parent ${process.ppid} ${a?"alive":"gone \u2014 orphaned"}) \u2014 shutting down`),i.stop().then(()=>e({success:!0}))},"stop");process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM"))})}p(gatewayExecutor,"gatewayExecutor"),n(gatewayExecutor,"gatewayExecutor"),module.exports=gatewayExecutor,module.exports.default=gatewayExecutor,module.exports.startGateway=gateway_lib_1.startGateway;
1
+ "use strict";var w=Object.defineProperty;var i=(t,r)=>w(t,"name",{value:r,configurable:!0});var u=Object.defineProperty,c=i((t,r)=>u(t,"name",{value:r,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=gatewayExecutor;const gateway_lib_1=require("./gateway.lib"),own_log_1=require("./runtime/own-log"),project_graph_store_1=require("./runtime/project-graph-store"),server_1=require("./runtime/server");async function gatewayExecutor(t,r){const p=(0,own_log_1.takeOverGatewayLog)();p&&console.log(`[gateway] ${new Date().toISOString()} writing ${p.file} itself (pid ${process.pid}) \u2014 a line a full disk refuses is counted, and the next line that lands says how many were lost`),process.on("uncaughtException",e=>{if((e.syscall==="read"||e.syscall==="write")&&server_1.BENIGN_SOCKET_ERRNOS.has(e.code??"")){console.warn(`[gateway] survived a stray socket ${e.syscall} error (${e.code}) \u2014 one connection dropped, the daemon lives`);return}console.error(e),process.exit(1)});let l;try{const e=(0,project_graph_store_1.createProjectGraphStore)(r.root,()=>(0,project_graph_store_1.computeProjectGraphInProcess)(r.root)),o=(await e.refresh())?.projectGraph,s=o?Object.fromEntries(Object.entries(o.dependencies).map(([n,a])=>[n,a.map(d=>d.target)])):void 0;l=await(0,gateway_lib_1.startGateway)(t,{...r,...s?{projectDependencies:s}:{},projectGraphStore:e})}catch(e){return console.error(e.message),{success:!1}}return new Promise(e=>{let o=!1;const s=c(n=>{if(o)return;o=!0;let a=!0;try{process.kill(process.ppid,0)}catch{a=!1}console.log(`[gateway] received ${n} (pid ${process.pid}, parent ${process.ppid} ${a?"alive":"gone \u2014 orphaned"}) \u2014 shutting down`),l.stop().then(()=>e({success:!0}))},"stop");process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM"))})}i(gatewayExecutor,"gatewayExecutor"),c(gatewayExecutor,"gatewayExecutor"),module.exports=gatewayExecutor,module.exports.default=gatewayExecutor,module.exports.startGateway=gateway_lib_1.startGateway;
@@ -18,6 +18,7 @@ export interface ControlServerDeps {
18
18
  killService: (svc: WorkspaceFragment['services'][number], reason: string) => void;
19
19
  /** Restart one service (the svcLayer's `restartService`); `POST /restart` answers 501 without it. */
20
20
  restartService?: ServiceLayer['restartService'];
21
+ ensureService?: ServiceLayer['ensureUp'];
21
22
  /** Who signed in, and tickets to act as them (act.ts); `/identities` and `/act` answer 501 without it. */
22
23
  act?: ActStore;
23
24
  /** Hooks so the daemon can host/tear-down a workspace's dashboard around the route merge (P3d).
@@ -1,2 +1,2 @@
1
- "use strict";var T=Object.defineProperty;var g=(o,r)=>T(o,"name",{value:r,configurable:!0});var G=Object.defineProperty,u=g((o,r)=>G(o,"name",{value:r,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.instanceSummary=instanceSummary,exports.controlSocketPath=controlSocketPath,exports.versionAtLoad=versionAtLoad,exports.readClusterVersion=readClusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),guest_graph_1=require("./guest-graph"),guest_replay_1=require("./guest-replay"),registration_guards_1=require("./registration-guards"),scope_1=require("./scope"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),workspace_middleware_1=require("./workspace-middleware");function instanceSummary(o){const r=o?.instance;return r?{instance:r.name,baseKey:r.baseKey,apex:r.apex,baseApex:r.baseApex,...r.frozen?{frozen:!0}:{}}:{}}g(instanceSummary,"instanceSummary"),u(instanceSummary,"instanceSummary");function controlSocketPath(){const o=process.env.LENSMCP_HOME||os.homedir();return path.join(o,".lensmcp","control.sock")}g(controlSocketPath,"controlSocketPath"),u(controlSocketPath,"controlSocketPath");const CONTROL_BODY_BYTES=1e6;class BodyTooLarge extends Error{static{g(this,"BodyTooLarge")}static{u(this,"BodyTooLarge")}constructor(r){super(`body too large (over ${r} bytes)`),this.limit=r}}exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(o){const r=o();return()=>r}g(versionAtLoad,"versionAtLoad"),u(versionAtLoad,"versionAtLoad");function readClusterVersion(o){let r=o;for(let p=0;p<6;p++){const h=path.join(r,"package.json");if(fs.existsSync(h))try{const l=JSON.parse(fs.readFileSync(h,"utf8"));if(l.name==="@lensmcp/cluster"&&typeof l.version=="string")return l.version}catch{}const a=path.dirname(r);if(a===r)break;r=a}}g(readClusterVersion,"readClusterVersion"),u(readClusterVersion,"readClusterVersion");function readJsonBody(o,r=CONTROL_BODY_BYTES){return new Promise((p,h)=>{const a=[];let l=0,k=!1;o.on("data",m=>{if(!k){if(l+=m.length,l>r){k=!0,a.length=0,h(new BodyTooLarge(r));return}a.push(m)}}),o.on("end",()=>{if(k)return;const m=Buffer.concat(a).toString("utf8");try{p({value:m?JSON.parse(m):{},bytes:l})}catch(S){h(S)}}),o.on("error",h)})}g(readJsonBody,"readJsonBody"),u(readJsonBody,"readJsonBody");function graphShare(o,r){return r-Buffer.byteLength(JSON.stringify({...o,projectGraph:null}))}g(graphShare,"graphShare"),u(graphShare,"graphShare");function graphVerdict(o){return o?o.refused===void 0?{forwarded:!0,accepted:!0}:{forwarded:!0,accepted:!1,reason:o.refused}:{forwarded:!1,accepted:!1}}g(graphVerdict,"graphVerdict"),u(graphVerdict,"graphVerdict");function ensureOwnerOnlyDir(o){try{fs.mkdirSync(o,{recursive:!0,mode:448})}catch{}try{const r=fs.lstatSync(o);return r.isDirectory()?((r.mode&63)!==0&&fs.chmodSync(o,448),!0):!1}catch{return!1}}g(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(o){const{rt:r}=o,p=controlSocketPath(),h=path.dirname(p);ensureOwnerOnlyDir(h)||console.warn(`[gateway] control home ${h} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(p)}catch{}const a=u((e,n,c)=>{e.writeHead(n,{"content-type":"application/json"}),e.end(JSON.stringify(c))},"send"),l=u(async(e,n,c=!1)=>{if(!e.wsKey||!e.root)return{status:400,json:{error:"wsKey and root are required"}};const t=e.projects??{},s=(0,discovery_1.projectSourceRoots)(e.root,t,e.projectDependencies),i=(0,scope_1.readLensScope)(e.root),y=i.key===e.wsKey,d=y&&i.sockNs!==e.wsKey?i.sockNs:void 0,{routes:w,services:f,instance:j}=(0,discovery_1.discoverRoutes)(e.root,t,void 0,e.wsKey,s,d,y?i.instance:void 0),b=(0,workspace_middleware_1.findGatewayMiddleware)(e.root,t),$={wsKey:e.wsKey,...d?{sockNs:d}:{},root:e.root,routes:w,services:f,...j?{instance:j}:{},...b?{middleware:b}:{}},_=(0,registration_guards_1.refuseRegistration)(r.registry,$);if(_)return console.warn(`[gateway] refused to register '${e.wsKey}' from ${e.root}: ${_.error}`),{status:409,json:_};if(c)return j?{status:200,json:{wsKey:e.wsKey,root:e.root,...instanceSummary($),env:j.env}}:{status:400,json:{error:`'${e.wsKey}' has no resolvable instance configuration`}};try{const v=(0,service_keys_1.adoptServiceKeys)(r,e.root,f,e.wsKey);v.added.length>0&&console.log(`[gateway] adopted east-west keys for ${e.wsKey}: ${v.added.join(", ")}`),v.regenerated.length>0&&console.warn(`[gateway] ${e.root} carried another workspace's east-west keys (a copied key file) \u2014 new keys for ${e.wsKey}: ${v.regenerated.join(", ")}`)}catch(v){console.error(`[gateway] could not adopt east-west keys for ${e.wsKey}: ${v.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(e.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${e.wsKey}') is served by the daemon at ${r.root} (pid ${process.pid}); its gateway-level log is ${path.join(r.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
2
- `)}catch{}let K;if(r.graphStores){const v={root:e.root,daemonRoot:r.root,otherRoots:r.registry.keys().filter(O=>O!==e.wsKey).flatMap(O=>r.registry.get(O)?.root??[])};K=e.projectGraph===void 0?void 0:(0,guest_graph_1.admitForwardedGraph)(e.projectGraph,{...v,bytes:graphShare(e,n)}),K?.refused!==void 0&&console.warn(`[gateway] refused the project graph '${e.wsKey}' forwarded from ${e.root}: ${K.refused} \u2014 its services start through \`nx run\` until the gateway computes it`),(0,registration_guards_1.sameRoot)(e.root,r.root)||r.graphStores.set((0,guest_graph_1.createGuestGraphStore)({wsKey:e.wsKey,root:e.root,...K?{admission:K}:{},context:v}))}return r.registry.register($),await o.onRegister?.($),r.rebuildRoutes(),r.root&&!(0,registration_guards_1.sameRoot)(e.root,r.root)&&(0,guest_replay_1.recordGuest)({wsKey:e.wsKey,root:e.root,projects:t,...e.projectDependencies?{projectDependencies:e.projectDependencies}:{},daemonRoot:r.root,registeredAt:new Date().toISOString()}),{status:200,json:{ok:!0,wsKey:e.wsKey,routes:w.length,services:f.length,workspaces:r.registry.keys(),...r.graphStores?{projectGraph:graphVerdict(K)}:{}}}},"register"),k=u((e,n)=>{const c=r.registry.get(e);if(!c)return{error:`no workspace '${e}' is registered`,workspaces:r.registry.keys()};const t=c.services.find(s=>s.project===n);return t?{svc:t}:{error:`'${e}' has no service '${n}'`,services:c.services.map(s=>s.project)}},"findService"),m=u(e=>({wsKey:e.wsKey,service:e.project,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{}}),"restartView"),S=http.createServer((e,n)=>{(async()=>{try{const c=(e.url??"").split("?")[0];if(e.method==="GET"&&c==="/list")return a(n,200,{workspaces:r.registry.keys().map(t=>{const s=r.registry.get(t);return{key:t,root:s?.root,routes:s?.routes.length??0,services:s?.services.length??0,...instanceSummary(s)}})});if(e.method==="GET"&&c==="/status"){const t=(0,exports.clusterVersion)();return a(n,200,{daemon:{pid:process.pid,wsKey:r.wsKey,...t?{version:t}:{}},workspaces:r.registry.keys().map(s=>{const i=r.registry.get(s);return{key:s,root:i?.root,routes:i?.routes.length??0,services:i?.services.length??0,isDaemon:s===r.wsKey,...instanceSummary(i)}}),services:r.registry.allServices().map(s=>({project:s.project,wsKey:s.wsKey,host:s.decl.host,state:s.state,...s.generation!==void 0?{generation:s.generation}:{},...s.restart?{restart:s.restart}:{}}))})}if(e.method==="GET"&&c==="/instance-env"){const t=new URL(e.url??"/","http://control").searchParams.get("wsKey")??"",s=r.registry.get(t);return s?s.instance?a(n,200,{wsKey:t,root:s.root,...instanceSummary(s),env:s.instance.env}):a(n,404,{error:`'${t}' is not an instance`}):a(n,404,{error:`no workspace '${t}' is registered`})}if(e.method==="POST"&&c==="/instance-env"){const{value:t,bytes:s}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),i=await l(t,s,!0);return a(n,i.status,i.json)}if(c==="/restart"&&(e.method==="POST"||e.method==="GET")){const t=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},s=typeof t.wsKey=="string"?t.wsKey:"",i=typeof t.service=="string"?t.service:"";if(!s||!i)return a(n,400,{error:"wsKey and service are required"});const y=k(s,i);if("error"in y)return a(n,404,y);const d=y.svc;if(e.method==="GET")return a(n,200,m(d));if(!o.restartService)return a(n,501,{error:"this gateway cannot restart a service"});const w=typeof t.reason=="string"&&t.reason.trim()?t.reason.trim().slice(0,200):"asked for",f=o.restartService(d,{hard:t.hard===!0,reason:w});return"refused"in f?a(n,409,{error:f.refused,...m(d)}):a(n,202,{ok:!0,already:f.already,...m(d)})}if(e.method==="GET"&&c==="/identities"||e.method==="POST"&&c==="/act"){const t=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},s=typeof t.wsKey=="string"?t.wsKey:"";if(!s)return a(n,400,{error:"wsKey is required"});if(!r.registry.get(s)&&s!==r.wsKey)return a(n,404,{error:`no workspace '${s}' is registered`,workspaces:r.registry.keys()});if(!o.act)return a(n,501,{error:"acting as a signed-in user is turned off on this gateway (LENSMCP_GW_ACT=0)"});if(e.method==="GET")return a(n,200,{wsKey:s,identities:o.act.list(s)});const i=u(f=>typeof t[f]=="string"&&t[f].trim()?t[f].trim().slice(0,256):void 0,"pick"),y=i("user"),d=i("tenant"),w=o.act.mint(s,{...y?{user:y}:{},...d?{tenant:d}:{}});return"error"in w?a(n,404,{wsKey:s,...w}):a(n,200,{wsKey:s,...w})}if(e.method==="POST"&&c==="/register"){const{value:t,bytes:s}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),i=await l(t,s);return a(n,i.status,i.json)}if(e.method==="POST"&&c==="/unregister"){const t=(await readJsonBody(e)).value;if(!t.wsKey)return a(n,400,{error:"wsKey is required"});const s=(0,registration_guards_1.refuseUnregistration)(r.registry,{wsKey:t.wsKey,...t.root?{root:t.root}:{}},r.wsKey);if(s)return console.warn(`[gateway] refused to unregister '${t.wsKey}'${t.root?` for ${t.root}`:""}: ${s.error}`),a(n,409,s);const i=r.registry.unregister(t.wsKey);if(i){for(const y of i.services){const d=(0,service_keys_1.serviceKeyId)(y.wsKey??t.wsKey,y.project),w=r.serviceKeys[d];w&&(delete r.serviceKeys[d],r.keyToProject.delete(w),r.keyToWorkspace?.delete(w))}for(const y of i.services)o.killService(y,"workspace unregistered");r.graphStores?.delete(i.root),(0,guest_replay_1.forgetGuest)(t.wsKey),o.onUnregister?.(i)}return r.rebuildRoutes(),a(n,200,{ok:!0,removed:!!i,workspaces:r.registry.keys()})}a(n,404,{error:"not found"})}catch(c){if(c instanceof BodyTooLarge)return e.resume(),a(n,413,{error:c.message});a(n,500,{error:c.message})}})()});return S.listen(p,()=>{try{fs.chmodSync(p,384)}catch(e){console.warn(`[gateway] could not restrict ${p} to 0600:`,e.message)}}),{socketPath:p,async replay(){const{replay:e,gone:n}=(0,guest_replay_1.guestsToReplay)(r.root,(0,guest_replay_1.readGuests)()),c={replayed:[],refused:[],gone:n.map(t=>t.wsKey)};for(const t of n)(0,guest_replay_1.forgetGuest)(t.wsKey),console.log(`[gateway] not re-hosting '${t.wsKey}': ${t.root} is no longer a workspace \u2014 forgotten`);for(const t of e)if(!r.registry.has(t.wsKey))try{const s=await l({wsKey:t.wsKey,root:t.root,projects:t.projects,...t.projectDependencies?{projectDependencies:t.projectDependencies}:{}},0);s.status===200?(c.replayed.push(t.wsKey),console.log(`[gateway] re-hosted '${t.wsKey}' (${t.root}) \u2014 registered before this daemon restarted`)):(c.refused.push(t.wsKey),console.warn(`[gateway] could not re-host '${t.wsKey}' (${t.root}): ${JSON.stringify(s.json)} \u2014 \`lensmcp gateway start\` there`))}catch(s){c.refused.push(t.wsKey),console.warn(`[gateway] could not re-host '${t.wsKey}' (${t.root}): ${s.message}`)}return c},close:u(()=>{S.close();try{fs.unlinkSync(p)}catch{}},"close")}}g(startControlServer,"startControlServer"),u(startControlServer,"startControlServer");
1
+ "use strict";var O=Object.defineProperty;var w=(i,t)=>O(i,"name",{value:t,configurable:!0});var G=Object.defineProperty,f=w((i,t)=>G(i,"name",{value:t,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.instanceSummary=instanceSummary,exports.controlSocketPath=controlSocketPath,exports.versionAtLoad=versionAtLoad,exports.readClusterVersion=readClusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),guest_graph_1=require("./guest-graph"),guest_replay_1=require("./guest-replay"),registration_guards_1=require("./registration-guards"),scope_1=require("./scope"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),workspace_middleware_1=require("./workspace-middleware");function instanceSummary(i){const t=i?.instance;return t?{instance:t.name,baseKey:t.baseKey,apex:t.apex,baseApex:t.baseApex,...t.frozen?{frozen:!0}:{}}:{}}w(instanceSummary,"instanceSummary"),f(instanceSummary,"instanceSummary");function controlSocketPath(){const i=process.env.LENSMCP_HOME||os.homedir();return path.join(i,".lensmcp","control.sock")}w(controlSocketPath,"controlSocketPath"),f(controlSocketPath,"controlSocketPath");const CONTROL_BODY_BYTES=1e6;class BodyTooLarge extends Error{static{w(this,"BodyTooLarge")}static{f(this,"BodyTooLarge")}constructor(t){super(`body too large (over ${t} bytes)`),this.limit=t}}exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(i){const t=i();return()=>t}w(versionAtLoad,"versionAtLoad"),f(versionAtLoad,"versionAtLoad");function readClusterVersion(i){let t=i;for(let l=0;l<6;l++){const h=path.join(t,"package.json");if(fs.existsSync(h))try{const g=JSON.parse(fs.readFileSync(h,"utf8"));if(g.name==="@lensmcp/cluster"&&typeof g.version=="string")return g.version}catch{}const a=path.dirname(t);if(a===t)break;t=a}}w(readClusterVersion,"readClusterVersion"),f(readClusterVersion,"readClusterVersion");function readJsonBody(i,t=CONTROL_BODY_BYTES){return new Promise((l,h)=>{const a=[];let g=0,S=!1;i.on("data",v=>{if(!S){if(g+=v.length,g>t){S=!0,a.length=0,h(new BodyTooLarge(t));return}a.push(v)}}),i.on("end",()=>{if(S)return;const v=Buffer.concat(a).toString("utf8");try{l({value:v?JSON.parse(v):{},bytes:g})}catch(k){h(k)}}),i.on("error",h)})}w(readJsonBody,"readJsonBody"),f(readJsonBody,"readJsonBody");function graphShare(i,t){return t-Buffer.byteLength(JSON.stringify({...i,projectGraph:null}))}w(graphShare,"graphShare"),f(graphShare,"graphShare");function graphVerdict(i){return i?i.refused===void 0?{forwarded:!0,accepted:!0}:{forwarded:!0,accepted:!1,reason:i.refused}:{forwarded:!1,accepted:!1}}w(graphVerdict,"graphVerdict"),f(graphVerdict,"graphVerdict");function ensureOwnerOnlyDir(i){try{fs.mkdirSync(i,{recursive:!0,mode:448})}catch{}try{const t=fs.lstatSync(i);return t.isDirectory()?((t.mode&63)!==0&&fs.chmodSync(i,448),!0):!1}catch{return!1}}w(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),f(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(i){const{rt:t}=i,l=controlSocketPath(),h=path.dirname(l);ensureOwnerOnlyDir(h)||console.warn(`[gateway] control home ${h} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(l)}catch{}const a=f((e,n,c)=>{e.writeHead(n,{"content-type":"application/json"}),e.end(JSON.stringify(c))},"send"),g=f(async(e,n,c=!1)=>{if(!e.wsKey||!e.root)return{status:400,json:{error:"wsKey and root are required"}};const r=e.projects??{},s=(0,discovery_1.projectSourceRoots)(e.root,r,e.projectDependencies),o=(0,scope_1.readLensScope)(e.root),u=o.key===e.wsKey,p=u&&o.sockNs!==e.wsKey?o.sockNs:void 0,{routes:y,services:d,instance:j}=(0,discovery_1.discoverRoutes)(e.root,r,void 0,e.wsKey,s,p,u?o.instance:void 0),b=(0,workspace_middleware_1.findGatewayMiddleware)(e.root,r),$={wsKey:e.wsKey,...p?{sockNs:p}:{},root:e.root,routes:y,services:d,...j?{instance:j}:{},...b?{middleware:b}:{}},_=(0,registration_guards_1.refuseRegistration)(t.registry,$);if(_)return console.warn(`[gateway] refused to register '${e.wsKey}' from ${e.root}: ${_.error}`),{status:409,json:_};if(c)return j?{status:200,json:{wsKey:e.wsKey,root:e.root,...instanceSummary($),env:j.env}}:{status:400,json:{error:`'${e.wsKey}' has no resolvable instance configuration`}};try{const m=(0,service_keys_1.adoptServiceKeys)(t,e.root,d,e.wsKey);m.added.length>0&&console.log(`[gateway] adopted east-west keys for ${e.wsKey}: ${m.added.join(", ")}`),m.regenerated.length>0&&console.warn(`[gateway] ${e.root} carried another workspace's east-west keys (a copied key file) \u2014 new keys for ${e.wsKey}: ${m.regenerated.join(", ")}`)}catch(m){console.error(`[gateway] could not adopt east-west keys for ${e.wsKey}: ${m.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(e.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${e.wsKey}') is served by the daemon at ${t.root} (pid ${process.pid}); its gateway-level log is ${path.join(t.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
2
+ `)}catch{}let K;if(t.graphStores){const m={root:e.root,daemonRoot:t.root,otherRoots:t.registry.keys().filter(T=>T!==e.wsKey).flatMap(T=>t.registry.get(T)?.root??[])};K=e.projectGraph===void 0?void 0:(0,guest_graph_1.admitForwardedGraph)(e.projectGraph,{...m,bytes:graphShare(e,n)}),K?.refused!==void 0&&console.warn(`[gateway] refused the project graph '${e.wsKey}' forwarded from ${e.root}: ${K.refused} \u2014 its services start through \`nx run\` until the gateway computes it`),(0,registration_guards_1.sameRoot)(e.root,t.root)||t.graphStores.set((0,guest_graph_1.createGuestGraphStore)({wsKey:e.wsKey,root:e.root,...K?{admission:K}:{},context:m}))}return t.registry.register($),await i.onRegister?.($),t.rebuildRoutes(),t.root&&!(0,registration_guards_1.sameRoot)(e.root,t.root)&&(0,guest_replay_1.recordGuest)({wsKey:e.wsKey,root:e.root,projects:r,...e.projectDependencies?{projectDependencies:e.projectDependencies}:{},daemonRoot:t.root,registeredAt:new Date().toISOString()}),{status:200,json:{ok:!0,wsKey:e.wsKey,routes:y.length,services:d.length,workspaces:t.registry.keys(),...t.graphStores?{projectGraph:graphVerdict(K)}:{}}}},"register"),S=f((e,n)=>{const c=t.registry.get(e);if(!c)return{error:`no workspace '${e}' is registered`,workspaces:t.registry.keys()};const r=c.services.find(s=>s.project===n);return r?{svc:r}:{error:`'${e}' has no service '${n}'`,services:c.services.map(s=>s.project)}},"findService"),v=f(e=>({wsKey:e.wsKey,service:e.project,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{}}),"restartView"),k=http.createServer((e,n)=>{(async()=>{try{const c=(e.url??"").split("?")[0];if(e.method==="GET"&&c==="/list")return a(n,200,{workspaces:t.registry.keys().map(r=>{const s=t.registry.get(r);return{key:r,root:s?.root,routes:s?.routes.length??0,services:s?.services.length??0,...instanceSummary(s)}})});if(e.method==="GET"&&c==="/status"){const r=(0,exports.clusterVersion)();return a(n,200,{daemon:{pid:process.pid,wsKey:t.wsKey,...r?{version:r}:{}},workspaces:t.registry.keys().map(s=>{const o=t.registry.get(s);return{key:s,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0,isDaemon:s===t.wsKey,...instanceSummary(o)}}),services:t.registry.allServices().map(s=>({project:s.project,wsKey:s.wsKey,host:s.decl.host,state:s.state,...s.generation!==void 0?{generation:s.generation}:{},...s.restart?{restart:s.restart}:{}}))})}if(e.method==="GET"&&c==="/instance-env"){const r=new URL(e.url??"/","http://control").searchParams.get("wsKey")??"",s=t.registry.get(r);return s?s.instance?a(n,200,{wsKey:r,root:s.root,...instanceSummary(s),env:s.instance.env}):a(n,404,{error:`'${r}' is not an instance`}):a(n,404,{error:`no workspace '${r}' is registered`})}if(e.method==="POST"&&c==="/instance-env"){const{value:r,bytes:s}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),o=await g(r,s,!0);return a(n,o.status,o.json)}if(c==="/service"&&(e.method==="GET"||e.method==="POST")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{};if(typeof r.wsKey!="string"||typeof r.service!="string")return a(n,400,{error:"wsKey and service are required"});const s=S(r.wsKey,r.service);if("error"in s)return a(n,404,s);const o=s.svc,u=f(()=>{let d=(o.stoppingUntil??0)>Date.now();if(o.stoppingPid)try{process.kill(o.stoppingPid,0),d=!0}catch{o.stoppingPid=void 0}return{service:o.project,state:o.state,stopped:o.manualStopped===!0&&!d,ready:!o.manualStopped&&o.state==="up"&&o.pool.socks.length>0,busy:d||o.actionStarting===!0||!!o.rolling||!!(o.restart&&!o.restart.done),failure:o.actionFailure??(!o.manualStopped&&o.restart?.done?.ok===!1?o.restart.done.error:void 0)}},"view");if(e.method==="GET")return a(n,200,u());const p=r.action;if(!["start","stop","restart"].includes(String(p)))return a(n,400,{error:"action must be start, stop or restart"});if(u().busy&&!(p==="stop"&&o.actionStarting&&!o.rolling))return a(n,409,{error:"A service lifecycle operation is already in progress"});if(p==="stop")return!o.proc&&o.pool.socks.length&&!o.manualStopped?a(n,409,{error:"This gateway does not own the adopted service process; stop it with its owner"}):(o.manualStopped=!0,o.stoppingPid=o.proc?.pid,o.stoppingUntil=Date.now()+(o.proc?1600:0),i.killService(o,"workspace action stop"),o.state="down",a(n,202,u()));if(p==="restart"){if(!i.restartService)return a(n,501,{error:"This gateway cannot restart services"});const d=i.restartService(o,{hard:r.hard===!0,reason:"workspace action restart"});return"refused"in d?a(n,409,{error:d.refused}):a(n,202,u())}if(!i.ensureService)return a(n,501,{error:"This gateway cannot start services"});const y=o.state!=="up"&&!o.proc;return o.manualStopped=!1,o.actionFailure=void 0,o.restart=void 0,o.actionStarting=!0,i.ensureService(o).then(d=>{!d&&!o.manualStopped&&(o.actionFailure="Service did not become ready")},d=>{o.actionFailure=String(d).slice(0,500)}).finally(()=>{o.actionStarting=!1}),a(n,202,{...u(),started:y})}if(c==="/restart"&&(e.method==="POST"||e.method==="GET")){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},s=typeof r.wsKey=="string"?r.wsKey:"",o=typeof r.service=="string"?r.service:"";if(!s||!o)return a(n,400,{error:"wsKey and service are required"});const u=S(s,o);if("error"in u)return a(n,404,u);const p=u.svc;if(e.method==="GET")return a(n,200,v(p));if(!i.restartService)return a(n,501,{error:"this gateway cannot restart a service"});const y=typeof r.reason=="string"&&r.reason.trim()?r.reason.trim().slice(0,200):"asked for",d=i.restartService(p,{hard:r.hard===!0,reason:y});return"refused"in d?a(n,409,{error:d.refused,...v(p)}):a(n,202,{ok:!0,already:d.already,...v(p)})}if(e.method==="GET"&&c==="/identities"||e.method==="POST"&&c==="/act"){const r=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},s=typeof r.wsKey=="string"?r.wsKey:"";if(!s)return a(n,400,{error:"wsKey is required"});if(!t.registry.get(s)&&s!==t.wsKey)return a(n,404,{error:`no workspace '${s}' is registered`,workspaces:t.registry.keys()});if(!i.act)return a(n,501,{error:"acting as a signed-in user is turned off on this gateway (LENSMCP_GW_ACT=0)"});if(e.method==="GET")return a(n,200,{wsKey:s,identities:i.act.list(s)});const o=f(d=>typeof r[d]=="string"&&r[d].trim()?r[d].trim().slice(0,256):void 0,"pick"),u=o("user"),p=o("tenant"),y=i.act.mint(s,{...u?{user:u}:{},...p?{tenant:p}:{}});return"error"in y?a(n,404,{wsKey:s,...y}):a(n,200,{wsKey:s,...y})}if(e.method==="POST"&&c==="/register"){const{value:r,bytes:s}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),o=await g(r,s);return a(n,o.status,o.json)}if(e.method==="POST"&&c==="/unregister"){const r=(await readJsonBody(e)).value;if(!r.wsKey)return a(n,400,{error:"wsKey is required"});const s=(0,registration_guards_1.refuseUnregistration)(t.registry,{wsKey:r.wsKey,...r.root?{root:r.root}:{}},t.wsKey);if(s)return console.warn(`[gateway] refused to unregister '${r.wsKey}'${r.root?` for ${r.root}`:""}: ${s.error}`),a(n,409,s);const o=t.registry.unregister(r.wsKey);if(o){for(const u of o.services){const p=(0,service_keys_1.serviceKeyId)(u.wsKey??r.wsKey,u.project),y=t.serviceKeys[p];y&&(delete t.serviceKeys[p],t.keyToProject.delete(y),t.keyToWorkspace?.delete(y))}for(const u of o.services)i.killService(u,"workspace unregistered");t.graphStores?.delete(o.root),(0,guest_replay_1.forgetGuest)(r.wsKey),i.onUnregister?.(o)}return t.rebuildRoutes(),a(n,200,{ok:!0,removed:!!o,workspaces:t.registry.keys()})}a(n,404,{error:"not found"})}catch(c){if(c instanceof BodyTooLarge)return e.resume(),a(n,413,{error:c.message});a(n,500,{error:c.message})}})()});return k.listen(l,()=>{try{fs.chmodSync(l,384)}catch(e){console.warn(`[gateway] could not restrict ${l} to 0600:`,e.message)}}),{socketPath:l,async replay(){const{replay:e,gone:n}=(0,guest_replay_1.guestsToReplay)(t.root,(0,guest_replay_1.readGuests)()),c={replayed:[],refused:[],gone:n.map(r=>r.wsKey)};for(const r of n)(0,guest_replay_1.forgetGuest)(r.wsKey),console.log(`[gateway] not re-hosting '${r.wsKey}': ${r.root} is no longer a workspace \u2014 forgotten`);for(const r of e)if(!t.registry.has(r.wsKey))try{const s=await g({wsKey:r.wsKey,root:r.root,projects:r.projects,...r.projectDependencies?{projectDependencies:r.projectDependencies}:{}},0);s.status===200?(c.replayed.push(r.wsKey),console.log(`[gateway] re-hosted '${r.wsKey}' (${r.root}) \u2014 registered before this daemon restarted`)):(c.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${JSON.stringify(s.json)} \u2014 \`lensmcp gateway start\` there`))}catch(s){c.refused.push(r.wsKey),console.warn(`[gateway] could not re-host '${r.wsKey}' (${r.root}): ${s.message}`)}return c},close:f(()=>{k.close();try{fs.unlinkSync(l)}catch{}},"close")}}w(startControlServer,"startControlServer"),f(startControlServer,"startControlServer");
@@ -1 +1 @@
1
- "use strict";var Ce=Object.defineProperty;var v=(s,f)=>Ce(s,"name",{value:f,configurable:!0});var Le=Object.defineProperty,d=v((s,f)=>Le(s,"name",{value:f,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,f){return new Promise(E=>{const _=net.createServer();_.unref(),_.once("error",p=>{const g=p.code??"";E(g==="EADDRINUSE"||g==="EACCES"?"in-use":"unavailable")}),_.listen(f===void 0?{port:s}:{port:s,host:f},()=>_.close(()=>E("free")))})}v(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(s){for(const f of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,f)==="in-use")return!1;return!0}v(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(s,f,E){return new Promise(_=>{let p=!1;const g=net.createConnection({host:s,port:f});g.unref();const $=setTimeout(()=>P(!1),E);$.unref?.();function P(b){p||(p=!0,clearTimeout($),g.destroy(),_(b))}v(P,"R"),d(P,"finish"),g.once("connect",()=>P(!0)),g.once("error",()=>P(!1))})}v(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(s,f=1e3){return await probeConnect("127.0.0.1",s,f)?!0:probeConnect("::1",s,f)}v(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(s,f=1500){try{const E=new AbortController,_=setTimeout(()=>E.abort(),f);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:E.signal});if(!p.ok)return null;const g=await p.json();if(typeof g?.lastFsSetEventAt!="number")return null;const $=Array.isArray(g.recentEvents)?g.recentEvents.filter(P=>P&&typeof P.path=="string"&&typeof P.at=="number"&&typeof P.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(P.kind)):[];return{schemaVersion:g.schemaVersion===2?2:1,ready:g.ready!==!1,sequence:typeof g.sequence=="number"?g.sequence:0,lastFsSetEventAt:g.lastFsSetEventAt,fsSetEvents:typeof g.fsSetEvents=="number"?g.fsSetEvents:0,recentEvents:$}}finally{clearTimeout(_)}}catch{return null}}v(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(s=>new Promise(f=>{setTimeout(f,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,f,E={}){const _=E.isPortFree??probePortFree,p=Math.max(1,E.intervalMs??types_1.PORT_FREE_POLL_MS),g=Date.now()+Math.max(0,f);for(;;){if(await _(s))return!0;const $=g-Date.now();if($<=0)return!1;await sleepUnref(Math.min(p,$))}}v(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(s){const f=d(E=>Number.isInteger(E)&&E>0&&E<65536?E:void 0,"valid");for(let E=0;E<s.length;E+=1){const _=s[E];if(_==="--port")return f(Number(s[E+1]));if(_.startsWith("--port="))return f(Number(_.slice(7)))}}v(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}v(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");function signalChild(s,f){if(s.pid===void 0)return!1;try{return s.kill(f)}catch{return!1}}v(signalChild,"signalChild"),d(signalChild,"signalChild");async function claimPort(s,f,E,_){let p=s;for(;f.has(p)||_?.has(p)||!await E(p);)p+=1;return f.add(p),p}v(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(s,f,E,_){const{emit:p}=f,g=_?.isPortFree??probePortFree,$=(E.ports?.length?E.ports:[443]).join(","),P=_?.probeWatcherHealth??probeWatcherHealth,b=[],W=new Map,A=new Map,I=new Map,Y=new Map,B=new Set;let j;const N=new Map,M=new Set,q=new Set,V=new Set,T=new Set,C=new Map,F=new WeakMap,G=new Set,Q=d((e,r,o)=>{if(G.has(e))return;G.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||M.has(e)){clearInterval(t),G.delete(e);return}await g(r)&&(clearInterval(t),G.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),o())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),K=d(async(e,r,o)=>{const t=await claimPort(e,r,g,o?.reserved);return t!==e&&(o?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${o.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ce=d((e,r,o)=>{const t=o?.LENSMCP_FRONTEND_KEY,n=Number(o?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(n))return;const a=[...C.get(t)?.generations??[]].find(l=>l.generation===n);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),H=d(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(q.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),de=d((e,r)=>{const o=N.get(e);!o||o===r||o.exitCode!==null||o.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${o.pid??"?"}).`),H(o))},"retireLabelHolder"),O=d((e,r,o,t,n=!1,a,l,c)=>{const u=Date.now(),i=(0,node_child_process_1.spawn)(r,o,{cwd:l??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,l??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});b.push(i),de(e,i),N.set(e,i),ce(e,i,t);const S=e==="vite"||e.endsWith(":vite"),m=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=m&&m!==s.eventFile?m:void 0,w=S?viteArgPort(o):c;S&&A.set(i,{key:t?.LENSMCP_FRONTEND_KEY??`${l??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:l??s.root,spawnedAt:u,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const h=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return h&&w!==void 0&&(p("info",`lens child starting: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-starting",project:h,label:e,pid:i.pid,port:w},void 0,y),(async()=>{const L=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<L;){if(i.exitCode!==null||i.signalCode!==null||M.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-up",project:h,label:e,pid:i.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),i.on("error",L=>{if(i.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${L.message}`);return}const D=b.indexOf(i);D>=0&&b.splice(D,1),N.get(e)===i&&N.delete(e),A.delete(i),q.delete(i),console.error(`[gateway] lens child ${e} failed to spawn (${L.message}) \u2014 not started, not retried.`)}),i.on("exit",(L,D)=>{const se=b.indexOf(i);se>=0&&b.splice(se,1),N.get(e)===i&&N.delete(e);const k=A.get(i);if(A.delete(i),q.delete(i))return;const ve=M.delete(e);if(s.stopped()||ve||(h?p("warning",`lens child exited: ${h}`,`cluster-lens-child:${h}`,{kind:"lens-child-exit",project:h,label:e,pid:i.pid,code:L,signal:D},void 0,y):k&&p("warning",`lens frontend exited: ${k.project}`,`cluster-lens-child:${k.project}`,{kind:"lens-child-exit",project:k.project,label:e,pid:i.pid,code:L,signal:D,generation:k.generation,port:k.port,ranMs:Date.now()-u},void 0,y),k&&Ee(i,k)))return;const ae=Date.now()-u,R=W.get(e)??{attempts:0,portConflicts:0};(async()=>{const Pe=w!==void 0&&!await g(w),ie=(0,lifecycle_1.isPortConflictExit)({code:L,signal:D,ranMs:ae,portHeld:Pe},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,W.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${L}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){R.portConflicts=0,W.set(e,R),console.warn(`[gateway] lens child ${e}: :${w} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Q(e,w,()=>O(e,r,o,t,n,a,l,c));return}else{R.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(R.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,W.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${L} sig=${D}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}s.stopped()||M.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:g,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||M.has(e))&&O(e,r,o,t,n,a,l,c))})().catch(()=>{})}),i},"spawnManagedChild"),ee=d(e=>{const r=`${e.wsKey}:${e.project}`,o=C.get(r);if(o)return o;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};C.set(r,t);for(const n of e.routes)n.lens&&(n.lens.workspaceKey??=e.wsKey,F.set(n.lens,t));return e.frontend.workspaceKey??=e.wsKey,F.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),o=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return o.length>0?{LENSMCP_WATCH_DIRS:o.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,r,o)=>{const t=++e.generation,n=`${e.key}:g${t}:`,a=`${n}vite`,l=`${n}browser-capture`;M.delete(a),M.delete(l);const c=d((S,m,y,w,h,L)=>O(S,m,y,w,h,L,e.workspaceRoot),"spawnInRoot"),u=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{...e.frontend.env??{},...e.frontend.frozen?{LENSMCP_FREEZE:"1"}:{},LENSMCP_PROJECT:e.project,...e.replayWorkspace?{LENSMCP_REPLAY_WORKSPACE:e.replayWorkspace}:{},LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:o,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:n,log:d(S=>console.log(S),"log")},c),i={ownerKey:e.key,generation:t,port:r,labelPrefix:n,vite:u?.vite??N.get(a),capture:N.get(l)};return e.generations.add(i),i},"spawnFrontendGeneration"),x=d((e,r)=>{if(!e)return;C.get(e.ownerKey)?.generations.delete(e);const o=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,n]of o)M.add(t),n&&H(n);r&&T.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const o=e.vite;if(!o||o.exitCode!==null||o.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await P(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),U=d(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),z=d(e=>!e.disposed&&!s.stopped()&&C.get(e.key)===e,"frontendIsCurrent"),X=d((e,r)=>{if(!z(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const o=e.active;e.state=o?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let n,a;const l=Date.now();try{const c=new Set;for(const h of C.values())h!==e&&c.add(h.desiredPort);const u=o&&o.port===e.desiredPort?`g${o.generation}`:void 0,i=await K(e.desiredPort,T,{reserved:c,heldBy:u});if(a=i,!z(e))return T.delete(i),!1;n=te(e,i,"replacement"),e.candidate=n;const S=await U(o);console.log(`[gateway] lens handoff start: ${e.key} g${o?.generation??0}\u2192g${n.generation} :${o?.port??"down"}\u2192:${i} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,oldPort:o?.port,newPort:i,oldServing:S},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const m=await ue(n);if(!z(e))return x(n,!0),e.candidate=void 0,!1;if(!m){x(n,!0),e.candidate=void 0;const h=await U(o);return e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${n.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,port:i,durationMs:Date.now()-l,oldKept:h},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const h of e.routes)h.target=`http://localhost:${n.port}`,h.lens&&(h.lens.port=n.port);e.frontend.port=n.port,e.active=n,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const y=n.vite?A.get(n.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${n.generation} :${n.port} in ${Date.now()-l}ms; draining g${o?.generation??0}${w>0?`; reloading ${w} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,oldPort:o?.port,newPort:n.port,durationMs:Date.now()-l,reloadedTabs:w},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),o){const h=`${o.labelPrefix}browser-capture`;M.add(h),o.capture&&H(o.capture),setTimeout(()=>x(o,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(c){x(n,!0),!n&&a!==void 0&&T.delete(a),e.candidate=void 0;const u=await U(o);e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0);const i=c.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${i}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:n?.generation??e.generation,port:n?.port??a,durationMs:Date.now()-l,oldKept:u,error:i},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),ne=d((e,r)=>{e.active=te(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Ee=d((e,r)=>{const o=C.get(r.key);return!o||types_1.LENS_RESTART_MODE==="in-place"?!1:(o.candidate?.vite===e||o.active?.vite!==e||(o.state="starting",o.reason="child-exit",o.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${o.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),X(o,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=F.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const o=F.get(e);return o?X(o,r):Promise.resolve(!1)}}:void 0;let J=!1,oe=0;const fe=d(()=>{J||Date.now()-oe<types_1.POD_STALE_SCAN_MS||(J=!0,oe=Date.now(),(async()=>{const e=new Map;for(const r of[...C.values()]){if(s.stopped())return;let o=e.get(r.workspaceRoot);if(!o){const i=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);o={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,i.exclude,{ignoreTests:i.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,o)}const t=o.startedAt,n=await o.pass.snapshot(r.sourceDirs);if(C.get(r.key)!==r)continue;const a=r.sourceSnapshot,l=r.sourceScanStartedAt;if(r.sourceSnapshot=n,r.sourceScanStartedAt=t,!a)continue;const c=Date.now(),u=(0,lifecycle_1.diffSourceSets)(a,n,c,(0,lifecycle_1.sourceChangeWindowMs)(c,l,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));u&&(r.sourceChange=u,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:u.added,removed:u.removed,truncated:u.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{J=!1}))},"scanFrontendSourceSets");let Z=!1;const ge=d(async(e,r)=>{if(!Z){Z=!0;try{for(const[o,t]of[...A]){if(s.stopped())return;if(t.port===void 0||o.exitCode!==null||o.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const c=C.get(t.key);c?.active?.vite===o&&(c.state="ready",c.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const n=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<n){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},I.get(t.key)??0,Date.now(),e);if(!a)continue;const l=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(n/1e3)}s boot allowance`;I.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${l} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(o,"SIGKILL")}}finally{Z=!1}}},"probeLensChildren"),he=d(()=>{if(j||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};j=setInterval(()=>{if(s.stopped()||A.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;fe();const r=Date.now();for(const[o,t]of A){const n=C.get(t.key);if(n?.frontend.frozen)continue;const a=n?.sourceChange?.changedAt??0,l=I.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,l,a,r,e)&&((Y.get(t.key)??0)>=a||B.has(t.key)||(B.add(t.key),(async()=>{try{const c=n?.sourceChange,u=c&&!c.truncated?[...c.added,...c.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],i=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await P(t.port),a,c?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,u):"unknown";if(i==="heard"){Y.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(i==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(o.exitCode!==null||o.signalCode!==null)return;I.set(t.key,Date.now());const S=C.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${i==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:S.wsKey,project:t.project,reason:"stale-source-set",watcher:i,added:c?.added,removed:c?.removed},void 0,t.bus),await X(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${i==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:i},void 0,t.bus);const m=(0,hmr_reload_1.broadcastFullReload)(s,t.key);m>0&&console.log(`[gateway] \u2192 told ${m} open tab(s) to reload (stale optimize-dep generation)`),signalChild(o,"SIGTERM"),setTimeout(()=>{o.exitCode===null&&o.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(o,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{B.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),j.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=E.dashboard!==!1,o=E.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const n=(0,scope_1.readLensScope)(s.root),a=await K(E.dashboardPort??n.dashboardPort,V),l=E.lensHost??"lensmcp.local",c=n.basePath;s.registry.appendRoutes(s.wsKey,[{host:l,prefix:c,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:n.key,root:s.root,base:c,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${l}${c}/)`),p("info",`lens dashboard up: ${l}${c} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:l,port:a,base:c}),O("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",n.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:c,LENSMCP_REPLAY_WORKSPACE:n.key,LENSMCP_GATEWAY_PORTS:$},!1,void 0,void 0,a)}}if(o){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const n=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??n.mcpHttpPort),l=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),O("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",n.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:n.key,LENSMCP_REPLAY_WORKSPACE:n.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await g(a)?l():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),Q("lens-mcp",a,l))}}for(const t of e){if(t.started)continue;t.started=!0;const n=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(u=>u.lens?.project===t.project),l=await K(t.port,T);for(const u of a)u.lens&&(u.lens.port=l),u.target=`http://localhost:${l}`;t.port=l;const c=ee({wsKey:s.wsKey,replayWorkspace:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:n});try{ne(c,l),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(u){T.delete(l),c.state="failed",console.error(`[gateway] lens app ${t.project}: ${u.message}`)}}he()},"bootSingletonsAndApps"),_e=d(async e=>{if(E.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const o=(0,scope_1.readLensScope)(e.root),t=E.lensHost??"lensmcp.local",n=o.basePath,a=await K(o.dashboardPort,V),l=`lens-dashboard:${e.wsKey}`,c=path.join(e.root,".lensmcp","events.jsonl");return M.delete(l),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:n,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:n,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString(),...e.instance?{instance:{name:e.instance.name,baseKey:e.instance.baseKey,apex:e.instance.apex}}:{}}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${n}/)`),p("info",`lens dashboard up: ${t}${n} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:n}),O(l,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[r],{LENSMCP_EVENT_FILE:c,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:n,LENSMCP_WS_KEY:e.wsKey,LENSMCP_HOSTED_GUEST:"1",LENSMCP_REPLAY_WORKSPACE:e.instance?.baseKey??e.wsKey,...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_GATEWAY_PORTS:$},!1,void 0,e.root,a),()=>{M.add(l),V.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const u=N.get(l);u&&H(u),N.delete(l)}},"hostWorkspaceDashboard"),Se=d(async e=>{const r=new Map;for(const n of e.routes){if(!n.lens)continue;const a=r.get(n.lens.project)??[];a.push(n),r.set(n.lens.project,a)}if(r.size===0)return()=>{};const o=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[n,a]of r){const l=a[0].lens;if(l.started)continue;const c=await K(l.port,T),u=l.port;for(const S of a)S.lens.started=!0,S.lens.port=c,S.target=`http://localhost:${c}`;const i=ee({wsKey:e.wsKey,replayWorkspace:e.instance?.baseKey??e.wsKey,project:n,workspaceRoot:e.root,projectRoot:l.projectRoot,eventFile:o,routes:a,frontend:l,desiredPort:u});t.push(i),console.log(`[gateway] hosting lens app '${n}' of '${e.wsKey}' \u2192 vite :${c}`);try{ne(i,c),p("info",`lens app started: ${e.wsKey}/${n} (vite :${c})`,`cluster-lens-app:${e.wsKey}:${n}`,{kind:"lens-app-up",project:n,port:c},void 0,o)}catch(S){T.delete(c),i.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${n}: ${S.message}`)}}return()=>{for(const n of t){n.disposed=!0;for(const a of[...n.generations])x(a,!0);for(const a of n.routes)a.lens&&F.delete(a.lens);F.delete(n.frontend),I.delete(n.key),Y.delete(n.key),B.delete(n.key),C.delete(n.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{j&&(clearInterval(j),j=void 0);const r=[...b];for(const n of r)signalChild(n,"SIGTERM");const o=d(n=>n.exitCode===null&&n.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(o);)await new Promise(n=>setTimeout(n,100));for(const n of r)o(n)&&signalChild(n,"SIGKILL")},"reapAll"),re=d(()=>ge({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:O,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}v(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
1
+ "use strict";var Ce=Object.defineProperty;var y=(s,g)=>Ce(s,"name",{value:g,configurable:!0});var Re=Object.defineProperty,d=y((s,g)=>Re(s,"name",{value:g,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),own_log_1=require("./own-log"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,g){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const w=p.code??"";f(w==="EADDRINUSE"||w==="EACCES"?"in-use":"unavailable")}),_.listen(g===void 0?{port:s}:{port:s,host:g},()=>_.close(()=>f("free")))})}y(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(s){for(const g of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,g)==="in-use")return!1;return!0}y(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(s,g,f){return new Promise(_=>{let p=!1;const w=net.createConnection({host:s,port:g});w.unref();const N=setTimeout(()=>v(!1),f);N.unref?.();function v(A){p||(p=!0,clearTimeout(N),w.destroy(),_(A))}y(v,"R"),d(v,"finish"),w.once("connect",()=>v(!0)),w.once("error",()=>v(!1))})}y(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(s,g=1e3){return await probeConnect("127.0.0.1",s,g)?!0:probeConnect("::1",s,g)}y(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(s,g=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),g);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const w=await p.json();if(typeof w?.lastFsSetEventAt!="number")return null;const N=Array.isArray(w.recentEvents)?w.recentEvents.filter(v=>v&&typeof v.path=="string"&&typeof v.at=="number"&&typeof v.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(v.kind)):[];return{schemaVersion:w.schemaVersion===2?2:1,ready:w.ready!==!1,sequence:typeof w.sequence=="number"?w.sequence:0,lastFsSetEventAt:w.lastFsSetEventAt,fsSetEvents:typeof w.fsSetEvents=="number"?w.fsSetEvents:0,recentEvents:N}}finally{clearTimeout(_)}}catch{return null}}y(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(s=>new Promise(g=>{setTimeout(g,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,g,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),w=Date.now()+Math.max(0,g);for(;;){if(await _(s))return!0;const N=w-Date.now();if(N<=0)return!1;await sleepUnref(Math.min(p,N))}}y(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(s){const g=d(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const _=s[f];if(_==="--port")return g(Number(s[f+1]));if(_.startsWith("--port="))return g(Number(_.slice(7)))}}y(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}y(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");function signalChild(s,g){if(s.pid===void 0)return!1;try{return s.kill(g)}catch{return!1}}y(signalChild,"signalChild"),d(signalChild,"signalChild");async function claimPort(s,g,f,_){let p=s;for(;g.has(p)||_?.has(p)||!await f(p);)p+=1;return g.add(p),p}y(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(s,g,f,_){const{emit:p}=g,w=_?.isPortFree??probePortFree,N=(f.ports?.length?f.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,A=[],B=new Map,T=new Map,K=new Map,q=new Map,G=new Set;let F;const b=new Map,M=new Set,V=new Set,U=new Set,k=new Set,L=new Map,I=new WeakMap,H=new Set,ee=d((e,r,n)=>{if(H.has(e))return;H.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||M.has(e)){clearInterval(t),H.delete(e);return}await w(r)&&(clearInterval(t),H.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),x=d(async(e,r,n)=>{const t=await claimPort(e,r,w,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),de=d((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),Y=d(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(V.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),pe=d((e,r)=>{const n=b.get(e);!n||n===r||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),Y(n))},"retireLabelHolder"),D=d((e,r,n,t,o=!1,a,i,l)=>{const u=Date.now(),h=(0,own_log_1.gatewayLogTakeover)()!==void 0,c=(0,node_child_process_1.spawn)(r,n,{cwd:i??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,i??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:h?["inherit","pipe","pipe"]:"inherit"});h&&(c.stdout?.on("data",S=>process.stdout.write(S)),c.stderr?.on("data",S=>process.stderr.write(S))),A.push(c),pe(e,c),b.set(e,c),de(e,c,t);const m=e==="vite"||e.endsWith(":vite"),$=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,C=$&&$!==s.eventFile?$:void 0,E=m?viteArgPort(n):l;m&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??s.root,spawnedAt:u,port:E,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...C?{bus:C}:{}});const P=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return P&&E!==void 0&&(p("info",`lens child starting: ${P} (:${E})`,`cluster-lens-child:${P}`,{kind:"lens-child-starting",project:P,label:e,pid:c.pid,port:E},void 0,C),(async()=>{const S=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<S;){if(c.exitCode!==null||c.signalCode!==null||M.has(e))return;if(await probePortListening(E,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${P} (:${E})`,`cluster-lens-child:${P}`,{kind:"lens-child-up",project:P,label:e,pid:c.pid,port:E},void 0,C);return}await sleepUnref(150)}})().catch(()=>{})),c.on("error",S=>{if(c.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${S.message}`);return}const j=A.indexOf(c);j>=0&&A.splice(j,1),b.get(e)===c&&b.delete(e),T.delete(c),V.delete(c),console.error(`[gateway] lens child ${e} failed to spawn (${S.message}) \u2014 not started, not retried.`)}),c.on("exit",(S,j)=>{const ae=A.indexOf(c);ae>=0&&A.splice(ae,1),b.get(e)===c&&b.delete(e);const O=T.get(c);if(T.delete(c),V.delete(c))return;const Le=M.delete(e);if(s.stopped()||Le||(P?p("warning",`lens child exited: ${P}`,`cluster-lens-child:${P}`,{kind:"lens-child-exit",project:P,label:e,pid:c.pid,code:S,signal:j},void 0,C):O&&p("warning",`lens frontend exited: ${O.project}`,`cluster-lens-child:${O.project}`,{kind:"lens-child-exit",project:O.project,label:e,pid:c.pid,code:S,signal:j,generation:O.generation,port:O.port,ranMs:Date.now()-u},void 0,C),O&&fe(c,O)))return;const ie=Date.now()-u,R=B.get(e)??{attempts:0,portConflicts:0};(async()=>{const Pe=E!==void 0&&!await w(E),le=(0,lifecycle_1.isPortConflictExit)({code:S,signal:j,ranMs:ie,portHeld:Pe},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(le&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${E} bind race (EADDRINUSE, code=${S}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(le){R.portConflicts=0,B.set(e,R),console.warn(`[gateway] lens child ${e}: :${E} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${E} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:E}),ee(e,E,()=>D(e,r,n,t,o,a,i,l));return}else{R.portConflicts=0,ie>=HEAL_HEALTHY_MS&&(R.attempts=0);const ce=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${S} sig=${j}); auto-healing in ${Math.round(ce/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(ce)}s.stopped()||M.has(e)||(E!==void 0&&!await waitForPortFree(E,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${E} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||M.has(e))&&D(e,r,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),te=d(e=>{const r=`${e.wsKey}:${e.project}`,n=L.get(r);if(n)return n;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,I.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,I.set(e.frontend,t),t},"registerFrontend"),ue=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),oe=d((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;M.delete(a),M.delete(i);const l=d((c,m,$,C,E,P)=>D(c,m,$,C,E,P,e.workspaceRoot),"spawnInRoot"),u=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{...e.frontend.env??{},...e.frontend.frozen?{LENSMCP_FREEZE:"1"}:{},LENSMCP_PROJECT:e.project,...e.replayWorkspace?{LENSMCP_REPLAY_WORKSPACE:e.replayWorkspace}:{},LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...ue(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(c=>console.log(c),"log")},l),h={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:u?.vite??b.get(a),capture:b.get(i)};return e.generations.add(h),h},"spawnFrontendGeneration"),W=d((e,r)=>{if(!e)return;L.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)M.add(t),o&&Y(o);r&&k.delete(e.port)},"terminateGeneration"),Ee=d(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await v(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),z=d(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),X=d(e=>!e.disposed&&!s.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),J=d((e,r)=>{if(!X(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const E of L.values())E!==e&&l.add(E.desiredPort);const u=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,h=await x(e.desiredPort,k,{reserved:l,heldBy:u});if(a=h,!X(e))return k.delete(h),!1;o=oe(e,h,"replacement"),e.candidate=o;const c=await z(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${h} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:h,oldServing:c},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const m=await Ee(o);if(!X(e))return W(o,!0),e.candidate=void 0,!1;if(!m){W(o,!0),e.candidate=void 0;const E=await z(n);return e.state=E?"ready":"failed",E&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,port:h,durationMs:Date.now()-i,oldKept:E},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const E of e.routes)E.target=`http://localhost:${o.port}`,E.lens&&(E.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const $=o.vite?T.get(o.vite):void 0;$&&($.role="active",$.listening=!0);const C=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${C>0?`; reloading ${C} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:C},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const E=`${n.labelPrefix}browser-capture`;M.add(E),n.capture&&Y(n.capture),setTimeout(()=>W(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){W(o,!0),!o&&a!==void 0&&k.delete(a),e.candidate=void 0;const u=await z(n);e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0);const h=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${h}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:u,error:h},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),ne=d((e,r)=>{e.active=oe(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),fe=d((e,r)=>{const n=L.get(r.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),J(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=I.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const n=I.get(e);return n?J(n,r):Promise.resolve(!1)}}:void 0;let Z=!1,re=0;const ge=d(()=>{Z||Date.now()-re<types_1.POD_STALE_SCAN_MS||(Z=!0,re=Date.now(),(async()=>{const e=new Map;for(const r of[...L.values()]){if(s.stopped())return;let n=e.get(r.workspaceRoot);if(!n){const h=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);n={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,h.exclude,{ignoreTests:h.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,n)}const t=n.startedAt,o=await n.pass.snapshot(r.sourceDirs);if(L.get(r.key)!==r)continue;const a=r.sourceSnapshot,i=r.sourceScanStartedAt;if(r.sourceSnapshot=o,r.sourceScanStartedAt=t,!a)continue;const l=Date.now(),u=(0,lifecycle_1.diffSourceSets)(a,o,l,(0,lifecycle_1.sourceChangeWindowMs)(l,i,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));u&&(r.sourceChange=u,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:u.added,removed:u.removed,truncated:u.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{Z=!1}))},"scanFrontendSourceSets");let Q=!1;const he=d(async(e,r)=>{if(!Q){Q=!0;try{for(const[n,t]of[...T]){if(s.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const l=L.get(t.key);l?.active?.vite===n&&(l.state="ready",l.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},K.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;K.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(n,"SIGKILL")}}finally{Q=!1}}},"probeLensChildren"),we=d(()=>{if(F||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};F=setInterval(()=>{if(s.stopped()||T.size===0||(se(),!types_1.POD_RECYCLE_ENABLED))return;ge();const r=Date.now();for(const[n,t]of T){const o=L.get(t.key);if(o?.frontend.frozen)continue;const a=o?.sourceChange?.changedAt??0,i=K.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,r,e)&&((q.get(t.key)??0)>=a||G.has(t.key)||(G.add(t.key),(async()=>{try{const l=o?.sourceChange,u=l&&!l.truncated?[...l.added,...l.removed].map($=>path.resolve(t.workspaceRoot,$)):[],h=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await v(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,u):"unknown";if(h==="heard"){q.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(h==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;K.set(t.key,Date.now());const c=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&c){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${h==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:c.wsKey,project:t.project,reason:"stale-source-set",watcher:h,added:l?.added,removed:l?.removed},void 0,t.bus),await J(c,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${h==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:h},void 0,t.bus);const m=(0,hmr_reload_1.broadcastFullReload)(s,t.key);m>0&&console.log(`[gateway] \u2192 told ${m} open tab(s) to reload (stale optimize-dep generation)`),signalChild(n,"SIGTERM"),setTimeout(()=>{n.exitCode===null&&n.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(n,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{G.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),F.unref?.()},"startLensSweeper"),_e=d(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,n=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(s.root),a=await x(f.dashboardPort??o.dashboardPort,U),i=f.lensHost??"lensmcp.local",l=o.basePath;s.registry.appendRoutes(s.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:s.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),D("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_GATEWAY_PORTS:N},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),D("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await w(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),ee("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(u=>u.lens?.project===t.project),i=await x(t.port,k);for(const u of a)u.lens&&(u.lens.port=i),u.target=`http://localhost:${i}`;t.port=i;const l=te({wsKey:s.wsKey,replayWorkspace:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:o});try{ne(l,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(u){k.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${u.message}`)}}we()},"bootSingletonsAndApps"),Se=d(async e=>{if(f.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const n=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",o=n.basePath,a=await x(n.dashboardPort,U),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return M.delete(i),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString(),...e.instance?{instance:{name:e.instance.name,baseKey:e.instance.baseKey,apex:e.instance.apex}}:{}}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o}),D(i,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[r],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_HOSTED_GUEST:"1",LENSMCP_REPLAY_WORKSPACE:e.instance?.baseKey??e.wsKey,...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_GATEWAY_PORTS:N},!1,void 0,e.root,a),()=>{M.add(i),U.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const u=b.get(i);u&&Y(u),b.delete(i)}},"hostWorkspaceDashboard"),ye=d(async e=>{const r=new Map;for(const o of e.routes){if(!o.lens)continue;const a=r.get(o.lens.project)??[];a.push(o),r.set(o.lens.project,a)}if(r.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of r){const i=a[0].lens;if(i.started)continue;const l=await x(i.port,k),u=i.port;for(const c of a)c.lens.started=!0,c.lens.port=l,c.target=`http://localhost:${l}`;const h=te({wsKey:e.wsKey,replayWorkspace:e.instance?.baseKey??e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:u});t.push(h),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{ne(h,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(c){k.delete(l),h.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${c.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])W(a,!0);for(const a of o.routes)a.lens&&I.delete(a.lens);I.delete(o.frontend),K.delete(o.key),q.delete(o.key),G.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ve=d(async(e=2500)=>{F&&(clearInterval(F),F=void 0);const r=[...A];for(const o of r)signalChild(o,"SIGTERM");const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of r)n(o)&&signalChild(o,"SIGKILL")},"reapAll"),se=d(()=>he({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:D,bootSingletonsAndApps:_e,hostWorkspaceDashboard:Se,hostWorkspaceApps:ye,probeLensFrontends:se,reapAll:ve}}y(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
@@ -3,12 +3,13 @@
3
3
  * load balancer in front of each pod service's unix-socket pool and owns the
4
4
  * pods' serverless lifecycle (eager/minPods/maxPods/idleKillSec).
5
5
  */
6
- import { spawn, type ChildProcess } from 'node:child_process';
6
+ import { ChildProcess, spawn } from 'node:child_process';
7
7
  import * as net from 'node:net';
8
8
  import { probePodAlive } from './pod-probe';
9
9
  import { type SharedBuildRelay } from './shared-build';
10
10
  import type { GatewayRuntime, LensWatcherHealth, PodWatcherTestimony, PodWatcherVerdict, ServiceCtl, ServiceRestart, SockPool, SourceSetChange } from './types';
11
11
  import type { Observability } from './observability';
12
+ import { type PsRow } from './orphan-reaper';
12
13
  /** Bounded, async, non-blocking walk of the workspace source tree → a signature of the file SET
13
14
  * (`<count>:<xor-of-path-hashes>`). Skips node_modules/dist/build/dotdirs + `*-devserver` sock dirs + every
14
15
  * GITIGNORED path (runtime output is not source — see `SRC_EXCLUDE`), and only counts source-extension
@@ -292,8 +293,14 @@ export interface ServiceLayer {
292
293
  };
293
294
  /** Self-heal a WEDGED service (live parent, 0 live pods): recycle it, within the crash-loop budget. */
294
295
  recycleWedged(svc: ServiceCtl): void;
295
- /** Stop every tree a handoff replaced that is still draining — at gateway stop, where nothing waits behind it. */
296
+ /** Stop every tree a handoff replaced that is still draining, and every standby that has not taken over — at
297
+ * gateway stop, where nothing waits behind it. */
296
298
  stopRetiringTrees(): void;
299
+ /**
300
+ * At gateway stop, after every tree was told to stop: wait — at most `timeoutMs` — for their process groups to empty,
301
+ * then SIGKILL what is left (issues/closed/foodguard-93: an executor that ignored SIGTERM outlived the gateway).
302
+ */
303
+ finishDyingTrees(timeoutMs?: number): Promise<void>;
297
304
  /** Stop the build host of the workspace at `root` — one that unregistered — or, with no root, every build host
298
305
  * (shared-build.ts): after the service trees, at gateway stop. */
299
306
  stopBuildHost(root?: string): Promise<void>;
@@ -321,8 +328,16 @@ export interface ServiceLayerDeps {
321
328
  maxConcurrentRolls?: number;
322
329
  /** Cold starts allowed at once across every service. Default {@link MAX_COLD_STARTS}; `0` = no cap. */
323
330
  maxConcurrentColdStarts?: number;
324
- /** Skip the nx foreign-runner reap before a spawn (tests). */
331
+ /** Skip the nx foreign-runner reap before a spawn, and the sweep's orphaned-executor reap (tests). */
325
332
  reapForeign?: boolean;
333
+ /** How long past `rollReadyMs` a standby that has not taken over is stopped regardless of its roll. Default 30 s. */
334
+ standbyGraceMs?: number;
335
+ /** How long a tree told to stop (SIGTERM) has before its group is SIGKILLed. Default {@link TREE_KILL_GRACE_MS}; `0`: never. */
336
+ killGraceMs?: number;
337
+ /** The process table the orphan reaper reads. Default: `ps` ({@link PS_ROW_ARGS}). */
338
+ listProcesses?: () => PsRow[];
339
+ /** Signal a pid (negative: a process group) — the orphan reaper's. Default `process.kill`. */
340
+ signalProcess?: (pid: number, signal: NodeJS.Signals) => void;
326
341
  /** DNS for an `adminUrl` scale POST. Default the OS resolver; tests point a non-loopback name at a local server. */
327
342
  scaleLookup?: net.LookupFunction;
328
343
  /** The shared compiler's relay. Default one for this runtime ({@link createSharedBuildRelay}). */