@lensmcp/cluster 1.23.13 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-host.d.ts +1 -0
- package/build-host.js +1 -0
- package/bundle-parent.d.ts +8 -0
- package/bundle-parent.js +1 -0
- package/create-webpack-dev.d.ts +74 -40
- package/create-webpack-dev.js +1 -1
- package/dev-devtool.d.ts +45 -0
- package/dev-devtool.js +1 -0
- package/devserver-notifier.d.ts +57 -0
- package/devserver-notifier.js +1 -0
- package/executors/gateway/body-stall.d.ts +13 -1
- package/executors/gateway/body-stall.js +1 -1
- package/executors/gateway/canonical-path.d.ts +1 -1
- package/executors/gateway/client-ip.d.ts +1 -1
- package/executors/gateway/gateway.impl.js +1 -1
- package/executors/gateway/jwt-claims.d.ts +1 -1
- package/executors/gateway/prod-config.d.ts +1 -1
- package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
- package/executors/gateway/rate-limit.d.ts +1 -1
- package/executors/gateway/runtime/auth.d.ts +1 -1
- package/executors/gateway/runtime/chooser.d.ts +2 -2
- package/executors/gateway/runtime/control.d.ts +3 -3
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/dev-auth.d.ts +1 -1
- package/executors/gateway/runtime/edge.d.ts +3 -3
- package/executors/gateway/runtime/guest-graph.d.ts +113 -0
- package/executors/gateway/runtime/guest-graph.js +2 -0
- package/executors/gateway/runtime/lens-children.d.ts +11 -1
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +56 -8
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/process-names.d.ts +18 -0
- package/executors/gateway/runtime/process-names.js +1 -0
- package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
- package/executors/gateway/runtime/project-graph-store.js +1 -0
- package/executors/gateway/runtime/proxy.d.ts +2 -2
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/scope.d.ts +1 -1
- package/executors/gateway/runtime/serve-launch.d.ts +118 -0
- package/executors/gateway/runtime/serve-launch.js +3 -0
- package/executors/gateway/runtime/server.d.ts +3 -3
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/shared-build.d.ts +65 -0
- package/executors/gateway/runtime/shared-build.js +4 -0
- package/executors/gateway/runtime/types.d.ts +39 -9
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/workspace-log.js +1 -1
- package/executors/gateway/schema.d.ts +3 -0
- package/executors/gateway/schema.json +4 -0
- package/executors/serve/schema.d.ts +2 -0
- package/executors/serve/schema.json +4 -0
- package/executors/serve/serve.impl.d.ts +12 -6
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/hostname.d.ts +1 -1
- package/exit-reason.d.ts +1 -1
- package/fork-ts-check-reporter.d.ts +12 -0
- package/fork-ts-check-reporter.js +1 -1
- package/graph-snapshot.d.ts +1 -0
- package/graph-snapshot.js +1 -0
- package/heap-snapshot.d.ts +66 -0
- package/heap-snapshot.js +1 -0
- package/hot-swap-registries.d.ts +1 -1
- package/inspector-secret.d.ts +39 -0
- package/inspector-secret.js +1 -0
- package/main.devserver.d.ts +33 -5
- package/main.devserver.js +11 -9
- package/package.json +4 -4
- package/perf/heavy-barrel-import.d.ts +135 -0
- package/perf/heavy-barrel-import.js +1 -0
- package/perf/perf-config.d.ts +29 -0
- package/perf/perf-config.js +1 -0
- package/perf/perf-rule-events.d.ts +71 -0
- package/perf/perf-rule-events.js +2 -0
- package/perf/perf-rules-analysis.d.ts +28 -0
- package/perf/perf-rules-analysis.js +1 -0
- package/perf/perf-rules-plugin.d.ts +71 -0
- package/perf/perf-rules-plugin.js +1 -0
- package/perf/perf-rules-worker.d.ts +1 -0
- package/perf/perf-rules-worker.js +1 -0
- package/perf/source-scan.d.ts +59 -0
- package/perf/source-scan.js +5 -0
- package/perf/workspace-libraries.d.ts +52 -0
- package/perf/workspace-libraries.js +2 -0
- package/pod-memory-events.d.ts +71 -0
- package/pod-memory-events.js +2 -0
- package/pod-memory.d.ts +264 -0
- package/pod-memory.js +1 -0
- package/process-name.d.ts +45 -0
- package/process-name.js +1 -1
- package/serve-runner.d.ts +2 -0
- package/serve-runner.js +1 -0
- package/shared-build/attach.d.ts +56 -0
- package/shared-build/attach.js +3 -0
- package/shared-build/host.d.ts +52 -0
- package/shared-build/host.js +4 -0
- package/shared-build/protocol.d.ts +118 -0
- package/shared-build/protocol.js +1 -0
- package/shared-build/shared-compilation.d.ts +91 -0
- package/shared-build/shared-compilation.js +3 -0
- package/shared-build/signature.d.ts +52 -0
- package/shared-build/signature.js +1 -0
- package/stdio-guard.d.ts +1 -1
- package/swap-recycle.d.ts +21 -0
- package/swap-recycle.js +1 -1
- package/tsgo-check-plugin.d.ts +1 -1
- package/workspace-paths.d.ts +76 -0
- package/workspace-paths.js +1 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var ue=Object.defineProperty;var m=(e,r)=>ue(e,"name",{value:r,configurable:!0});var le=Object.defineProperty,n=m((e,r)=>le(e,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(e,r){return new Promise((u,S)=>{const a=(e.startsWith("https:")?require("node:https"):require("node:http")).request(e,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},g=>{const d=[];g.on("data",p=>d.push(p)),g.on("end",()=>{const p=g.statusCode??0,l=Buffer.concat(d).toString("utf8");u({ok:p>=200&&p<300,status:p,json:n(async()=>JSON.parse(l),"json")})})});a.on("error",S),a.end()})}m(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(e){const r=process.env.LENSMCP_GW_JWKS_URL,u=process.env.LENSMCP_GW_JWT_ISS,S=new Map;let a;const g=n((d,p)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${d}${p}`);const l=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:d,...u?{issuer:u}:{},fetchImpl:insecureLocalFetch});return l.refresh().catch(s=>console.warn("[gateway] edge JWKS warm failed (will retry):",s.message)),l},"make");return{for(d){if(r)return a??=g(r,"");const p=d.split(":")[0].toLowerCase();let l;for(const i of e()){const h=i.host;if(typeof h!="string"||!/^auth\./.test(h)||h.startsWith("internal."))continue;const C=h.slice(5);(p===C||p.endsWith(`.${C}`))&&(!l||C.length>l.length)&&(l=C)}if(!l)return;let s=S.get(l);return s||(s=g(`https://auth.${l}/jwks.json`,` (auto-derived for apex ${l})`),S.set(l,s)),s},stop(){a?.stop();for(const d of S.values())d.stop()}}}m(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(e,r){return e&&exports.BENIGN_SOCKET_ERRNOS.has(e)?{status:null,report:!1,cause:"client-abort"}:e==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:e==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:e==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:e?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:e??r??"unknown"}}m(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(e=1e4){const r=new Map;return u=>{const S=Date.now(),a=r.get(u)??0;return S-a<e?!1:(r.set(u,S),!0)}}m(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(e){e.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),e.destroy()})}m(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(e,r,u){const S=e.get(r);if(!S)return!1;e.delete(r),u==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{S()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${u}): ${a.message}`)}return!0}m(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(e,r){if(e===void 0||e.trim()==="")return r;const u=Number(e);return!Number.isFinite(u)||u<0?r:Math.floor(u)}m(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(e){return parseBudget(e,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}m(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(e=process.env){return{maxSessions:parseBudget(e.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(e.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(e.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}m(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(e,r){const{NGHTTP2_ENHANCE_YOUR_CALM:u,NGHTTP2_REFUSED_STREAM:S}=require("node:http2").constants,a=new Map;let g=0;const d=n(s=>{s.idle&&(clearTimeout(s.idle),s.idle=void 0)},"disarm"),p=n((s,i)=>{d(i),!(e.idleSessionMs<=0||i.open>0)&&(i.idle=setTimeout(()=>{s.destroyed||s.closed||(r("session-reclaimed",{idleMs:e.idleSessionMs,sessions:a.size}),a.delete(s),s.close())},e.idleSessionMs),i.idle.unref?.())},"arm"),l=n(()=>{for(const[s,i]of a)if(!(i.open>0||s.destroyed))return d(i),a.delete(s),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:e.maxSessions}),s.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(s){if(e.maxSessions>0&&a.size>=e.maxSessions&&!l()){r("session-refused",{sessions:a.size,maxSessions:e.maxSessions,streams:g});try{s.goaway(u)}catch{}try{s.close()}catch{}return!1}const i={open:0};return a.set(s,i),p(s,i),s.once("close",()=>{d(i),a.delete(s)}),!0},admitStream(s,i){const h=a.get(s);if(!h)return!0;if(e.maxStreams>0&&g>=e.maxStreams){r("stream-refused",{streams:g,maxStreams:e.maxStreams,sessions:a.size});try{i.close(S)}catch{}return!1}return g+=1,h.open+=1,d(h),i.once("close",()=>{g=Math.max(0,g-1),h.open=Math.max(0,h.open-1),h.open===0&&a.has(s)&&p(s,h)}),!0},dispose(){for(const s of a.values())d(s);a.clear()}}}m(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(e,r){const u=e.https!==!1,S=e.ports?.length?e.ports:[443],a=e.scanTtlMs??1500,g=e.startupTimeoutMs??12e4,d=e.sweepMs??1e4,p=e.trafficFlushMs??5e3;let l=!1;const s=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),i=(0,scope_1.readLensScope)(r.root).key,h=r.projectsConfigurations?.projects??{},C=(0,discovery_1.projectSourceRoots)(r.root,h,r.projectDependencies),{routes:F,services:v}=(0,discovery_1.discoverRoutes)(r.root,h,void 0,i,C);if(F.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const b=new route_registry_1.RouteRegistry;b.register({wsKey:i,root:r.root,routes:F,services:v});const N=[];let U=n(()=>{},"refreshCert");const Y=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(N,b),U()},"rebuildRoutes");Y();const{serviceKeys:Q,keyToProject:Z,keyToWorkspace:ee}=(0,service_keys_1.loadServiceKeys)(r.root,v,i),te=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...e.trustProxyHops!==void 0?{trustProxyHops:e.trustProxyHops}:{},...e.clientIpHeader?{clientIpHeader:e.clientIpHeader}:{},...e.clientIpTrustedProxies?{clientIpTrustedProxies:e.clientIpTrustedProxies}:{}}),T={root:r.root,wsKey:i,eventFile:s,routes:N,registry:b,rebuildRoutes:Y,services:v,serviceKeys:Q,keyToProject:Z,keyToWorkspace:ee,clientIp:te,bodyStallMs:e.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:g,sweepMs:d,trafficFlushMs:p,https:u,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([t,o])=>[t,path.resolve(r.root,o.root)])),projectSourceRoots:C,stopped:n(()=>l,"stopped")},f=(0,observability_1.createObservability)(s,{trafficFlushMs:p}),re=createClientErrorLogGate(),x=h2BudgetFromEnv(),se=createClientErrorLogGate(3e4),$=createH2Governor(x,(t,o)=>{se(t)&&console.warn(`[gateway] h2 aggregate budget: ${t} (${JSON.stringify(o)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),f.emit(t==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${t}`,`gateway-h2-budget:${t}`,{kind:"h2-aggregate-budget",event:t,project:"gateway",...o})}),P=(0,lifecycle_1.createServiceLayer)(T,f),O=(0,lens_children_1.createLensChildren)(T,f,e),H=new Map;let B;try{B=(0,control_1.startControlServer)({rt:T,killService:n((t,o)=>P.killSpawned(t,o),"killService"),onRegister:n(async t=>{retirePreviousWorkspace(H,t.wsKey,"re-registered");const o=await O.hostWorkspaceDashboard(t),w=await O.hostWorkspaceApps(t);H.set(t.wsKey,()=>{try{o?.()}finally{w()}})},"onRegister"),onUnregister:n(t=>{retirePreviousWorkspace(H,t.wsKey,"unregistered")},"onUnregister")})}catch(t){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",t.message)}const L=(0,proxy_1.createProxy)(T,f,P),X=buildEdgeVerifierPool(()=>T.routes),K=(0,auth_1.createEdgeAuth)(T,f,X);let G;if(e.middleware){const t=require(path.resolve(r.root,e.middleware));G=t.default??t}const oe=n(t=>{if(G)try{G(t.req)}catch(o){f.traceStep(t.trace,"auth",{mode:"middleware",ok:!1,reason:o.message}),t.route&&f.finishTrace(t.trace,t.route.project,401),t.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${o.message}`)}},"builtinMiddleware"),J=e.hooks??{},V=(0,hooks_1.createHooks)({...J,afterAuth:[oe,...J.afterAuth??[]]}),z=(0,handler_1.createHandler)({rt:T,obs:f,auth:K,proxy:L,hooks:V}),ne=(0,upgrade_1.createUpgrade)({rt:T,auth:K,proxy:L,hooks:V}),ae=f.startEdgeFlusher(),ie=P.startSweeper();await O.bootSingletonsAndApps();for(const t of v)t.decl.eager&&P.ensureUp(t);let R,W,k;if(u){const t=require("../../../basic-ssl"),o=(0,service_keys_1.gatewayCacheDir)(r.root);k=n(()=>t.getCertificateSync(o,"gateway",T.routes.map(w=>w.host).filter(w=>!!w)),"mintCert"),R=k(),W=t.caCertPath()}const j=[],I=[],q=e.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const t of S){const o=R?require("node:http2").createSecureServer({key:R,cert:R,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:e.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:e.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(y,_)=>z(y,_)):http.createServer(z);o.on("upgrade",ne),o.on("error",y=>console.error(`[gateway] port ${t}: ${y.code}`)),o.on("connection",guardInboundSocket),o.on("secureConnection",guardInboundSocket),o.on("tlsClientError",(y,_)=>_.destroy()),o.on("clientError",(y,_)=>{const c=y,E=classifyClientError(c.code,c.message);if(E.report&&re(E.cause)){const M=_.remoteAddress??"unknown",A=E.status===null?"dropped":String(E.status);console.warn(`[gateway] inbound H1 clientError from ${M}: ${E.cause} (${c.code??c.message}${c.bytesParsed!==void 0?`, bytesParsed=${c.bytesParsed}`:""}) \u2192 ${A}. This status is the GATEWAY's, not the upstream service's.`),f.emit("warning",`inbound clientError: ${E.cause} \u2192 ${A}`,`gateway-client-error:${E.cause}`,{kind:"gateway-client-error",cause:E.cause,code:c.code??null,status:E.status,peer:M,bytesParsed:c.bytesParsed??null})}E.status!==null&&_.writable&&_.end(`HTTP/1.1 ${E.status} ${CLIENT_ERROR_REASON[E.status]}\r
|
|
1
|
+
"use strict";var ue=Object.defineProperty;var T=(e,t)=>ue(e,"name",{value:t,configurable:!0});var le=Object.defineProperty,n=T((e,t)=>le(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(e,t){return new Promise((u,E)=>{const a=(e.startsWith("https:")?require("node:https"):require("node:http")).request(e,{method:"GET",headers:t?.headers,rejectUnauthorized:!1},g=>{const d=[];g.on("data",p=>d.push(p)),g.on("end",()=>{const p=g.statusCode??0,l=Buffer.concat(d).toString("utf8");u({ok:p>=200&&p<300,status:p,json:n(async()=>JSON.parse(l),"json")})})});a.on("error",E),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(e){const t=process.env.LENSMCP_GW_JWKS_URL,u=process.env.LENSMCP_GW_JWT_ISS,E=new Map;let a;const g=n((d,p)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${d}${p}`);const l=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:d,...u?{issuer:u}:{},fetchImpl:insecureLocalFetch});return l.refresh().catch(s=>console.warn("[gateway] edge JWKS warm failed (will retry):",s.message)),l},"make");return{for(d){if(t)return a??=g(t,"");const p=d.split(":")[0].toLowerCase();let l;for(const i of e()){const h=i.host;if(typeof h!="string"||!/^auth\./.test(h)||h.startsWith("internal."))continue;const C=h.slice(5);(p===C||p.endsWith(`.${C}`))&&(!l||C.length>l.length)&&(l=C)}if(!l)return;let s=E.get(l);return s||(s=g(`https://auth.${l}/jwks.json`,` (auto-derived for apex ${l})`),E.set(l,s)),s},stop(){a?.stop();for(const d of E.values())d.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(e,t){return e&&exports.BENIGN_SOCKET_ERRNOS.has(e)?{status:null,report:!1,cause:"client-abort"}:e==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:e==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:e==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:e?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:e??t??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(e=1e4){const t=new Map;return u=>{const E=Date.now(),a=t.get(u)??0;return E-a<e?!1:(t.set(u,E),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(e){e.on("error",t=>{exports.BENIGN_SOCKET_ERRNOS.has(t.code??"")||console.warn(`[gateway] inbound socket error (${t.code??t.message}) \u2014 dropping this connection only`),e.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(e,t,u){const E=e.get(t);if(!E)return!1;e.delete(t),u==="re-registered"&&console.log(`[gateway] workspace '${t}' re-registered \u2014 retiring the children of its previous registration first.`);try{E()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${t}' (${u}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(e,t){if(e===void 0||e.trim()==="")return t;const u=Number(e);return!Number.isFinite(u)||u<0?t:Math.floor(u)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(e){return parseBudget(e,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(e=process.env){return{maxSessions:parseBudget(e.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(e.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(e.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(e,t){const{NGHTTP2_ENHANCE_YOUR_CALM:u,NGHTTP2_REFUSED_STREAM:E}=require("node:http2").constants,a=new Map;let g=0;const d=n(s=>{s.idle&&(clearTimeout(s.idle),s.idle=void 0)},"disarm"),p=n((s,i)=>{d(i),!(e.idleSessionMs<=0||i.open>0)&&(i.idle=setTimeout(()=>{s.destroyed||s.closed||(t("session-reclaimed",{idleMs:e.idleSessionMs,sessions:a.size}),a.delete(s),s.close())},e.idleSessionMs),i.idle.unref?.())},"arm"),l=n(()=>{for(const[s,i]of a)if(!(i.open>0||s.destroyed))return d(i),a.delete(s),t("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:e.maxSessions}),s.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(s){if(e.maxSessions>0&&a.size>=e.maxSessions&&!l()){t("session-refused",{sessions:a.size,maxSessions:e.maxSessions,streams:g});try{s.goaway(u)}catch{}try{s.close()}catch{}return!1}const i={open:0};return a.set(s,i),p(s,i),s.once("close",()=>{d(i),a.delete(s)}),!0},admitStream(s,i){const h=a.get(s);if(!h)return!0;if(e.maxStreams>0&&g>=e.maxStreams){t("stream-refused",{streams:g,maxStreams:e.maxStreams,sessions:a.size});try{i.close(E)}catch{}return!1}return g+=1,h.open+=1,d(h),i.once("close",()=>{g=Math.max(0,g-1),h.open=Math.max(0,h.open-1),h.open===0&&a.has(s)&&p(s,h)}),!0},dispose(){for(const s of a.values())d(s);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(e,t){const u=e.https!==!1,E=e.ports?.length?e.ports:[443],a=e.scanTtlMs??1500,g=e.startupTimeoutMs??12e4,d=e.sweepMs??1e4,p=e.trafficFlushMs??5e3;let l=!1;const s=process.env.LENSMCP_EVENT_FILE??path.join(t.root,".lensmcp","events.jsonl"),i=(0,scope_1.readLensScope)(t.root).key,h=t.projectsConfigurations?.projects??{},C=(0,discovery_1.projectSourceRoots)(t.root,h,t.projectDependencies),{routes:I,services:v}=(0,discovery_1.discoverRoutes)(t.root,h,void 0,i,C);if(I.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const H=new route_registry_1.RouteRegistry;H.register({wsKey:i,root:t.root,routes:I,services:v});const R=[];let U=n(()=>{},"refreshCert");const B=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,H),U()},"rebuildRoutes");B();const{serviceKeys:Q,keyToProject:Z,keyToWorkspace:ee}=(0,service_keys_1.loadServiceKeys)(t.root,v,i),re=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...e.trustProxyHops!==void 0?{trustProxyHops:e.trustProxyHops}:{},...e.clientIpHeader?{clientIpHeader:e.clientIpHeader}:{},...e.clientIpTrustedProxies?{clientIpTrustedProxies:e.clientIpTrustedProxies}:{}}),y={root:t.root,wsKey:i,eventFile:s,routes:R,registry:H,rebuildRoutes:B,services:v,serviceKeys:Q,keyToProject:Z,keyToWorkspace:ee,clientIp:re,bodyStallMs:e.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:g,sweepMs:d,trafficFlushMs:p,https:u,projectRoots:Object.fromEntries(Object.entries(t.projectsConfigurations?.projects??{}).map(([r,o])=>[r,path.resolve(t.root,o.root)])),projectSourceRoots:C,...t.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(t.projectGraphStore)}:{},...e.sharedCompiler!==void 0?{sharedCompiler:e.sharedCompiler}:{},stopped:n(()=>l,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(v,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!t.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,e.sharedCompiler),label:i||path.basename(t.root),lens:{key:i,dashboard:e.dashboard!==!1,mcp:e.mcp!==!1&&I.some(r=>!!r.lens)}}));const f=(0,observability_1.createObservability)(s,{trafficFlushMs:p}),te=createClientErrorLogGate(),P=h2BudgetFromEnv(),se=createClientErrorLogGate(3e4),$=createH2Governor(P,(r,o)=>{se(r)&&console.warn(`[gateway] h2 aggregate budget: ${r} (${JSON.stringify(o)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),f.emit(r==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${r}`,`gateway-h2-budget:${r}`,{kind:"h2-aggregate-budget",event:r,project:"gateway",...o})}),N=(0,lifecycle_1.createServiceLayer)(y,f),O=(0,lens_children_1.createLensChildren)(y,f,e),G=new Map;let Y;try{Y=(0,control_1.startControlServer)({rt:y,killService:n((r,o)=>N.killSpawned(r,o),"killService"),onRegister:n(async r=>{retirePreviousWorkspace(G,r.wsKey,"re-registered");const o=await O.hostWorkspaceDashboard(r),w=await O.hostWorkspaceApps(r);G.set(r.wsKey,()=>{try{o?.()}finally{w()}})},"onRegister"),onUnregister:n(r=>{retirePreviousWorkspace(G,r.wsKey,"unregistered"),N.stopBuildHost(r.root)},"onUnregister")})}catch(r){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",r.message)}const b=(0,proxy_1.createProxy)(y,f,N),X=buildEdgeVerifierPool(()=>y.routes),K=(0,auth_1.createEdgeAuth)(y,f,X);let j;if(e.middleware){const r=require(path.resolve(t.root,e.middleware));j=r.default??r}const oe=n(r=>{if(j)try{j(r.req)}catch(o){f.traceStep(r.trace,"auth",{mode:"middleware",ok:!1,reason:o.message}),r.route&&f.finishTrace(r.trace,r.route.project,401),r.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${o.message}`)}},"builtinMiddleware"),J=e.hooks??{},V=(0,hooks_1.createHooks)({...J,afterAuth:[oe,...J.afterAuth??[]]}),z=(0,handler_1.createHandler)({rt:y,obs:f,auth:K,proxy:b,hooks:V}),ne=(0,upgrade_1.createUpgrade)({rt:y,auth:K,proxy:b,hooks:V}),ae=f.startEdgeFlusher(),ie=N.startSweeper();await O.bootSingletonsAndApps();for(const r of v)r.decl.eager&&N.ensureUp(r);let x,q,L;if(u){const r=require("../../../basic-ssl"),o=(0,service_keys_1.gatewayCacheDir)(t.root);L=n(()=>r.getCertificateSync(o,"gateway",y.routes.map(w=>w.host).filter(w=>!!w)),"mintCert"),x=L(),q=r.caCertPath()}const W=[],k=[],D=e.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const r of E){const o=x?require("node:http2").createSecureServer({key:x,cert:x,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:e.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:e.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(m,_)=>z(m,_)):http.createServer(z);o.on("upgrade",ne),o.on("error",m=>console.error(`[gateway] port ${r}: ${m.code}`)),o.on("connection",guardInboundSocket),o.on("secureConnection",guardInboundSocket),o.on("tlsClientError",(m,_)=>_.destroy()),o.on("clientError",(m,_)=>{const c=m,S=classifyClientError(c.code,c.message);if(S.report&&te(S.cause)){const M=_.remoteAddress??"unknown",A=S.status===null?"dropped":String(S.status);console.warn(`[gateway] inbound H1 clientError from ${M}: ${S.cause} (${c.code??c.message}${c.bytesParsed!==void 0?`, bytesParsed=${c.bytesParsed}`:""}) \u2192 ${A}. This status is the GATEWAY's, not the upstream service's.`),f.emit("warning",`inbound clientError: ${S.cause} \u2192 ${A}`,`gateway-client-error:${S.cause}`,{kind:"gateway-client-error",cause:S.cause,code:c.code??null,status:S.status,peer:M,bytesParsed:c.bytesParsed??null})}S.status!==null&&_.writable&&_.end(`HTTP/1.1 ${S.status} ${CLIENT_ERROR_REASON[S.status]}\r
|
|
2
2
|
\r
|
|
3
|
-
`),_.destroy()}),o.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,o.headersTimeout=e.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,
|
|
3
|
+
`),_.destroy()}),o.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,o.headersTimeout=e.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,D>0&&(o.maxConnections=D);const w=e.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(w>0&&(o.connectionsCheckingInterval=w),x!=null){const m=o,{NGHTTP2_CANCEL:_}=require("node:http2").constants;m.on("session",c=>{if(!$.admitSession(c)){c.on("error",()=>{});return}let S=0,M=0;c.on("stream",A=>{$.admitStream(c,A)&&A.once("close",()=>{c.destroyed?M+=1:A.rstCode===_&&(S+=1)})}),c.once("close",()=>{M!==0&&(console.warn(`[gateway] h2 session closed with ${M} stream(s) in flight after ${S} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),f.emit("warning",`h2 session lost with ${M} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:M,clientResets:S}))}),c.on("error",A=>{const ce=A.code??A.message;console.warn(`[gateway] h2 session error (${ce}) \u2014 dropping this session only`),c.destroyed||c.destroy()})}),m.on("sessionError",c=>{console.warn(`[gateway] h2 sessionError: ${c.code??c.message}`)})}await new Promise(m=>{o.listen(r,()=>{const _=o.address()?.port??r;k.push(_),console.log(`[gateway] Listening on ${x?"https":"http"}://localhost:${_}`),m()})}),W.push(o)}L&&(U=n(()=>{const r=L();for(const o of W)o.setSecureContext?.({key:r,cert:r})},"refreshCert")),console.log("[gateway] Routes:");for(const r of R){const o=r.pool?`pods@${r.pool.dir}`:r.target;console.log(`[gateway] ${r.host??"(default)"} \u2192 ${o}${r.prependPrefix?` (+${r.prependPrefix})`:""}${r.internal?" [internal: no middleware]":""} [${r.project}]`)}q&&console.log(`[gateway] CA: ${q} \u2014 one-time setup via the trust executor.`);const F=n(r=>r>0?String(r):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${F(D)} connections, \u2264${F(P.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${F(P.maxStreams)} open streams in total, idle h2 session reclaimed after ${P.idleSessionMs>0?`${Math.round(P.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),f.emit("info",`gateway up: ${R.length} routes on ${k.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:k,routes:R.map(r=>({host:r.host??"(default)",project:r.project,mode:r.pool?"pods":"tcp",internal:!!r.internal,prependPrefix:r.prependPrefix}))}),{ports:k,routes:R,services:v,stop:n(async()=>{if(!l){l=!0,clearInterval(ae),clearInterval(ie),f.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),X.stop(),Y?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const r of v)N.killSpawned(r,"gateway shutdown");await N.stopBuildHost(),await O.reapAll(),await Promise.all(W.map(r=>new Promise(o=>{let w=!1;const m=n(()=>{w||(w=!0,o())},"done"),_=r;r.close(()=>m());try{_.closeIdleConnections?.()}catch{}setTimeout(()=>{try{_.closeAllConnections?.()}catch{}m()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),$.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...y.lensSupervisor?{lensSupervisor:y.lensSupervisor}:{},...y.graphStores?{graphStores:y.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gateway's half of the shared compiler (issues/open/pod-memory-05): it starts the workspace's BUILD HOST
|
|
3
|
+
* (build-host.ts — one webpack process for every service that can share it) and relays between it and each
|
|
4
|
+
* service's runner, over the two IPC channels it already owns. The runners never see the host and the host
|
|
5
|
+
* never sees a runner: no socket, no address, nothing another account could connect to.
|
|
6
|
+
*
|
|
7
|
+
* - A runner (serve-launch.ts) of THIS workspace is started with `LENSMCP_SHARED_BUILD=1`; its serve executor
|
|
8
|
+
* sends `join` on its channel (shared-build/attach.ts). The gateway names the member after the tree
|
|
9
|
+
* (`<project>#g<generation>`), starts the host if it is not up, and forwards the join.
|
|
10
|
+
* - The host answers per member; each answer goes to that member's runner.
|
|
11
|
+
* - A tree that dies or is killed leaves ({@link SharedBuildRelay.detach}) — the host pauses the project's
|
|
12
|
+
* entries once no tree holds it.
|
|
13
|
+
* - A host that exits unexpectedly is started again and every live join is sent to it again; runners keep
|
|
14
|
+
* their devservers serving the last good bundle meanwhile. A host that keeps dying (3 exits in a minute) is
|
|
15
|
+
* given up on: every member is refused, and each runner compiles on its own, as before.
|
|
16
|
+
* - A stale host of this same workspace root (a previous gateway's) is reaped before a start, matched exactly
|
|
17
|
+
* by its root argument — never another checkout's.
|
|
18
|
+
*
|
|
19
|
+
* One build host PER WORKSPACE the gateway serves: its own, and each REGISTERED (guest) workspace's, whose services
|
|
20
|
+
* start through the runner too since issues/closed/dev-gateway-21. A workspace's host is ITS OWN `@lensmcp/cluster`'s
|
|
21
|
+
* `build-host.js`, run in its root with its environment — the toolchain and the protocol of the executor that joins
|
|
22
|
+
* it — and it is stopped when the workspace unregisters ({@link SharedBuildRelay.release}).
|
|
23
|
+
*
|
|
24
|
+
* Not offered — the runner compiles on its own, exactly as before — to a service started through `nx run` (no
|
|
25
|
+
* channel to the gateway), to a workspace whose `@lensmcp/cluster` has no `build-host.js`, and when
|
|
26
|
+
* `LENSMCP_SHARED_COMPILER` / the gateway's `sharedCompiler` option is off.
|
|
27
|
+
*/
|
|
28
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
29
|
+
import type { RunnerScript } from './serve-launch';
|
|
30
|
+
import type { GatewayRuntime, ServiceCtl } from './types';
|
|
31
|
+
/** Exits within {@link CRASH_WINDOW_MS} after which the host is given up on. */
|
|
32
|
+
export declare const HOST_MAX_RESTARTS = 3;
|
|
33
|
+
/** The workspace a service belongs to, as the relay needs it: its root, and its key (a registered one's). */
|
|
34
|
+
export type BuildMember = Pick<ServiceCtl, 'root'> & Partial<Pick<ServiceCtl, 'wsKey'>>;
|
|
35
|
+
export interface SharedBuildRelay {
|
|
36
|
+
/** Will a runner of `svc` be compiled by its workspace's build host (it is then started with `LENSMCP_SHARED_BUILD=1`)? */
|
|
37
|
+
offeredTo(svc: BuildMember): boolean;
|
|
38
|
+
/** Relay this runner's joins: `proc` is the root of `svc`'s tree for `generation`. */
|
|
39
|
+
attach(svc: BuildMember & Pick<ServiceCtl, 'project'>, generation: number, proc: ChildProcess): void;
|
|
40
|
+
/** The tree rooted at `proc` is gone or going: its member leaves. Idempotent. */
|
|
41
|
+
detach(proc: ChildProcess): void;
|
|
42
|
+
/** The pid of the build host of the workspace at `root` (default: the gateway's own) while it runs — for the spec and `gateway status`. */
|
|
43
|
+
hostPid(root?: string): number | undefined;
|
|
44
|
+
/** A workspace left the gateway: stop its build host. */
|
|
45
|
+
release(root: string): Promise<void>;
|
|
46
|
+
/** Stop every build host (their watchers close and store the cache). */
|
|
47
|
+
stop(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export interface SharedBuildRelayDeps {
|
|
50
|
+
spawnProcess?: typeof spawn;
|
|
51
|
+
/** The gateway's own workspace's host. Default {@link buildHostScript}; `null`: there is none (never offered there). */
|
|
52
|
+
script?: RunnerScript | null;
|
|
53
|
+
/** A registered workspace's host. Default {@link workspaceBuildHostScript}. */
|
|
54
|
+
scriptFor?: (root: string) => RunnerScript | undefined;
|
|
55
|
+
/** The gateway's environment, for the switch. Default `process.env`. */
|
|
56
|
+
gatewayEnv?: NodeJS.ProcessEnv;
|
|
57
|
+
/** Skip the stale-host reap (tests). */
|
|
58
|
+
reapStale?: boolean;
|
|
59
|
+
log?: (line: string) => void;
|
|
60
|
+
}
|
|
61
|
+
/** The built `build-host.js` at the package root, or the `.ts` beside this source tree. */
|
|
62
|
+
export declare function buildHostScript(baseDir?: string): RunnerScript | undefined;
|
|
63
|
+
/** A registered workspace's own `@lensmcp/cluster/build-host.js`, or `undefined` when its package has none. */
|
|
64
|
+
export declare function workspaceBuildHostScript(root: string): RunnerScript | undefined;
|
|
65
|
+
export declare function createSharedBuildRelay(rt: GatewayRuntime, deps?: SharedBuildRelayDeps): SharedBuildRelay;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";var T=Object.defineProperty;var S=(r,t)=>T(r,"name",{value:t,configurable:!0});var L=Object.defineProperty,i=S((r,t)=>L(r,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.HOST_MAX_RESTARTS=void 0,exports.buildHostScript=buildHostScript,exports.workspaceBuildHostScript=workspaceBuildHostScript,exports.createSharedBuildRelay=createSharedBuildRelay;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path")),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),pod_env_1=require("./pod-env"),registration_guards_1=require("./registration-guards");exports.HOST_MAX_RESTARTS=3;const CRASH_WINDOW_MS=6e4;function buildHostScript(r=path.join(__dirname,"../../..")){const t=path.join(r,"build-host.js");if(fs.existsSync(t))return{path:t,execArgv:[]};const u=path.join(r,"build-host.ts");if(fs.existsSync(u))try{return{path:u,execArgv:["--require",require.resolve("@swc-node/register")]}}catch{return}}S(buildHostScript,"buildHostScript"),i(buildHostScript,"buildHostScript");function workspaceBuildHostScript(r){try{return{path:(0,node_module_1.createRequire)(path.join(r,"package.json")).resolve("@lensmcp/cluster/build-host.js"),execArgv:[]}}catch{return}}S(workspaceBuildHostScript,"workspaceBuildHostScript"),i(workspaceBuildHostScript,"workspaceBuildHostScript");function createSharedBuildRelay(r,t={}){const u=t.script===void 0?buildHostScript():t.script??void 0,_=t.scriptFor??workspaceBuildHostScript,h=new Map,f=new WeakMap,d=(0,registration_guards_1.canonicalRoot)(r.root),v=i(e=>{const a=(0,registration_guards_1.canonicalRoot)(e.root);let c=h.get(a);return c||(c=a===d?createWorkspaceRelay(r,{root:r.root,...r.wsKey?{wsKey:r.wsKey}:{},eventFile:r.eventFile},u,t):createWorkspaceRelay(r,{root:e.root,...e.wsKey?{wsKey:e.wsKey}:{},eventFile:path.join(e.root,".lensmcp","events.jsonl")},_(e.root),t),h.set(a,c)),c},"relayFor");return{offeredTo:i(e=>(0,protocol_1.sharedCompilerEnabled)(t.gatewayEnv??process.env,r.sharedCompiler)&&v(e).offered(),"offeredTo"),attach(e,a,c){const y=v(e);f.set(c,y),y.attach(`${e.project}#g${a}`,c)},detach:i(e=>f.get(e)?.detach(e),"detach"),hostPid:i((e=r.root)=>h.get((0,registration_guards_1.canonicalRoot)(e))?.hostPid(),"hostPid"),release(e){const a=(0,registration_guards_1.canonicalRoot)(e),c=h.get(a);return h.delete(a),c?.stop()??Promise.resolve()},async stop(){const e=[...h.values()];h.clear(),await Promise.all(e.map(a=>a.stop()))}}}S(createSharedBuildRelay,"createSharedBuildRelay"),i(createSharedBuildRelay,"createSharedBuildRelay");function createWorkspaceRelay(r,t,u,_){const h=_.spawnProcess??node_child_process_1.spawn,f=_.log??(o=>console.log(o)),d=new Map,v=new WeakMap;let e,a=[],c,y=!1;const E=(0,registration_guards_1.canonicalRoot)(t.root)===(0,registration_guards_1.canonicalRoot)(r.root)?"this workspace":`'${t.wsKey??path.basename(t.root)}'`,b=i((o,s)=>{if(!o.connected)return;const n={channel:protocol_1.SHARED_BUILD_CHANNEL,event:s};try{o.send(n,void 0,void 0,()=>{})}catch{}},"toRunner"),R=i(o=>{if(e?.connected)try{e.send(o,void 0,void 0,()=>{})}catch{}},"toHost"),A=i(()=>{if(_.reapStale===!1)return;let o;try{o=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return}for(const s of o.split(`
|
|
2
|
+
`)){const n=s.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!n||!(0,protocol_1.isBuildHostCommandOf)(n[3],t.root))continue;const l=Number(n[1]),p=Number(n[2]);if(!(l===e?.pid||l===process.pid)){try{process.kill(-p,"SIGKILL")}catch{try{process.kill(l,"SIGKILL")}catch{}}f(`[gateway] reaped a stale build host (pid ${l}) of ${E}`)}}},"reapStaleHosts"),x=i(()=>{if(e||y||c||!u)return e;A();const o=t.wsKey||path.basename(t.root),s={...(0,pod_env_1.podBaseEnv)(process.env,r.root,t.root),NX_DAEMON:"false",NODE_ENV:"development",NX_WORKSPACE_ROOT:t.root,...t.wsKey?{LENSMCP_WS_KEY:t.wsKey}:{},LENSMCP_EVENT_FILE:t.eventFile,LENSMCP_GATEWAY_PID:String(process.pid)},n=h((0,process_name_1.namedExecPath)((0,process_name_1.processName)(o,protocol_1.BUILD_HOST_ROLE)),[...u.execArgv,u.path,...(0,protocol_1.buildHostArgs)(t.root)],{cwd:t.root,detached:!0,env:s,stdio:["ignore","pipe","pipe","ipc"]});e=n,f(`[gateway] build host starting (one compilation for the services of ${E} \u2014 LENSMCP_SHARED_COMPILER)`);const l=i(p=>m=>{if(!r.stopped())for(const g of m.toString().split(`
|
|
3
|
+
`))g.trim()&&p.write(`[build] ${g}
|
|
4
|
+
`)},"relayOut");return n.stdout?.on("data",l(process.stdout)),n.stderr?.on("data",l(process.stderr)),n.on("message",p=>{if(p?.type==="member"){const m=d.get(p.member);m&&b(m.proc,p.event)}}),n.on("exit",(p,m)=>{if(e===n&&(e=void 0),y||r.stopped())return;const g=Date.now();if(a=[...a.filter(w=>g-w<CRASH_WINDOW_MS),g],a.length>=exports.HOST_MAX_RESTARTS){c=`the build host exited ${a.length} times in a minute (last: ${p??m})`,f(`[gateway] ${c} \u2014 every service compiles on its own from now on`);for(const w of d.values())b(w.proc,{type:"refused",reason:c});d.clear();return}if(d.size!==0&&(f(`[gateway] build host exited (${p??m}) \u2014 restarting it for ${d.size} service tree(s)`),x()))for(const[w,{join:P}]of d)R({type:"join",member:w,join:P})}),n},"start"),j=i(o=>{const s=v.get(o);!s||d.get(s)?.proc!==o||(d.delete(s),R({type:"leave",member:s}))},"detach");return{offered:i(()=>!!u&&!c,"offered"),attach:i((o,s)=>{v.set(s,o),s.on("message",n=>{if((0,protocol_1.isRunnerJoinMessage)(n)){if(c||!x()){b(s,{type:"refused",reason:c??"the build host could not be started"});return}d.set(o,{proc:s,join:n.join}),R({type:"join",member:o,join:n.join})}}),s.once("exit",()=>j(s))},"attach"),detach:j,hostPid:i(()=>e?.pid,"hostPid"),stop(){y=!0,d.clear();const o=e;return e=void 0,!o||o.exitCode!==null||o.signalCode!==null?Promise.resolve():new Promise(s=>{const n=i(()=>{clearTimeout(l),s()},"done");o.once("exit",n);try{process.kill(-o.pid,"SIGTERM")}catch{try{o.kill("SIGTERM")}catch{}}const l=setTimeout(()=>{try{process.kill(-o.pid,"SIGKILL")}catch{try{o.kill("SIGKILL")}catch{}}s()},6e3);l.unref?.()})}}}S(createWorkspaceRelay,"createWorkspaceRelay"),i(createWorkspaceRelay,"createWorkspaceRelay");
|
|
@@ -14,6 +14,7 @@ import type { AuthPolicy, Attrs } from '../manifest';
|
|
|
14
14
|
import type { GatewayErrorReason } from '../gateway-errors';
|
|
15
15
|
import type { RouteRegistry } from './route-registry';
|
|
16
16
|
import type { PodFaults } from './pod-output';
|
|
17
|
+
import type { ProjectGraphStore, ProjectGraphStores } from './project-graph-store';
|
|
17
18
|
export type { Attrs };
|
|
18
19
|
export interface GatewayContext {
|
|
19
20
|
root: string;
|
|
@@ -25,6 +26,10 @@ export interface GatewayContext {
|
|
|
25
26
|
};
|
|
26
27
|
/** Nx project-graph adjacency, populated by the executor wrapper when available. */
|
|
27
28
|
projectDependencies?: Record<string, string[]>;
|
|
29
|
+
/** The workspace's project graph, kept current by the executor wrapper — what lets the gateway start a
|
|
30
|
+
* service's serve task without an `nx run` client (serve-launch.ts). Absent ⇒ every service starts
|
|
31
|
+
* through `nx run`, as before. */
|
|
32
|
+
projectGraphStore?: ProjectGraphStore;
|
|
28
33
|
}
|
|
29
34
|
/** Per-project declaration: top-level `cluster` field in project.json (`davnx` legacy). */
|
|
30
35
|
export interface ClusterProjectDecl {
|
|
@@ -145,7 +150,7 @@ export interface SourceSetChange {
|
|
|
145
150
|
* COMPLETION, but the fs event happened anywhere after the PREVIOUS scan started — so the window is
|
|
146
151
|
* "now − previous scan start" (+ the settle margin), not a fixed fraction of the scan period. The old
|
|
147
152
|
* fixed `min(scanPeriod, 15s) + settle` = 23s against a 30s period falsely read a heard add as `missed`
|
|
148
|
-
* whenever the event fell in the first 7+ seconds of the period (issues/dev-gateway
|
|
153
|
+
* whenever the event fell in the first 7+ seconds of the period (issues/closed/dev-gateway-03).
|
|
149
154
|
*/
|
|
150
155
|
windowMs?: number;
|
|
151
156
|
}
|
|
@@ -227,6 +232,9 @@ export interface ServiceCtl {
|
|
|
227
232
|
rolling?: Promise<boolean>;
|
|
228
233
|
/** The pod GENERATION the current `proc` was spawned as (`LENSMCP_POD_GENERATION`, echoed in `/__lensmcp/alive`). */
|
|
229
234
|
generation?: number;
|
|
235
|
+
/** How the current `proc` was started: the gateway's serve runner, or `nx run` (serve-launch.ts). An `nx run`
|
|
236
|
+
* tree holds nx's task lock for its target, so no second generation can start beside it. */
|
|
237
|
+
launcher?: 'runner' | 'nx';
|
|
230
238
|
lastScaleAt: number;
|
|
231
239
|
spawnAt?: number;
|
|
232
240
|
lastErrors?: string[];
|
|
@@ -244,6 +252,10 @@ export interface ServiceCtl {
|
|
|
244
252
|
* failure recovers without a manual restart; too many in the window ⇒ stop hot-looping (the next request
|
|
245
253
|
* still spawns it via scale-from-zero). Shared by the exit-self-heal AND the sweeper's wedge-recycle. */
|
|
246
254
|
restarts?: number[];
|
|
255
|
+
/** Self-heal: wedge recycles in a row with no pod coming up in between — `markUp` ends the streak. The window above
|
|
256
|
+
* cannot see a wedge that returns slower than it: FoodGuard's engine was recycled every ~2.5 min with zero live
|
|
257
|
+
* pods, each one "attempt 1", for as long as anyone watched (issues/closed/foodguard-45). */
|
|
258
|
+
wedgeRecycles?: number;
|
|
247
259
|
/** Self-heal: when the sweeper FIRST observed this service wedged (live `proc`, 0 live pods, not
|
|
248
260
|
* cold-starting). Cleared once a pod appears; once it exceeds {@link WEDGE_RECYCLE_GRACE_MS} the sweeper
|
|
249
261
|
* recycles the wedged tree. */
|
|
@@ -390,7 +402,7 @@ export declare const LENS_WATCHER_UNVERIFIED: 'recycle' | 'keep';
|
|
|
390
402
|
* roots to Vite's chokidar. Vite watches only `config.root`; a file outside it is watched one-at-a-time
|
|
391
403
|
* as it is imported, so a NEW file in `shared/` or `web/libs/` never produced an `add` event, the
|
|
392
404
|
* watcher-health endpoint never stamped it, and the verdict above was `missed` BY CONSTRUCTION for every
|
|
393
|
-
* out-of-root add — 16 of 16 rolling handoffs in one foodguard day (issues/dev-gateway
|
|
405
|
+
* out-of-root add — 16 of 16 rolling handoffs in one foodguard day (issues/closed/dev-gateway-02). With the scope
|
|
394
406
|
* watched, the add is heard, the verdict is `heard`, and Vite's own create-handling works for those files
|
|
395
407
|
* too. Set `LENSMCP_LENS_WATCH_SCOPE=0` to hand nothing over (inotify-constrained Linux hosts).
|
|
396
408
|
*/
|
|
@@ -399,7 +411,7 @@ export declare const LENS_WATCH_SCOPE_ENABLED: boolean;
|
|
|
399
411
|
* POD watcher testimony ({@link PodWatcherTestimony}) — the pod-side twin of the two lens knobs above.
|
|
400
412
|
* The stale-source-set signal scaled EVERY pod to zero on EVERY source-set change in its scope — 83 cold
|
|
401
413
|
* recycles of six services in one foodguard day, each a ~30-60s API outage the app rendered as "not
|
|
402
|
-
* connected" (issues/dev-gateway
|
|
414
|
+
* connected" (issues/closed/dev-gateway-07). Now the sweeper reads the pod's testimony first.
|
|
403
415
|
*
|
|
404
416
|
* `LENSMCP_POD_WATCHER_UNVERIFIED` — no testimony at all (a pod on an older `@lensmcp/cluster` bundle,
|
|
405
417
|
* or an unreachable probe). `recycle` (default) is today's behaviour for those pods; `keep` leaves them.
|
|
@@ -414,7 +426,7 @@ export declare const POD_WATCHER_UNVERIFIED: 'recycle' | 'keep';
|
|
|
414
426
|
*/
|
|
415
427
|
export declare const POD_WATCHER_SILENT: 'recycle' | 'keep';
|
|
416
428
|
/**
|
|
417
|
-
* HOW a pod is recycled (stale-source-set / max-age) — issues/dev-gateway
|
|
429
|
+
* HOW a pod is recycled (stale-source-set / max-age) — issues/closed/dev-gateway-09. `rolling` (default): spawn a
|
|
418
430
|
* STANDBY `serve-hmr` generation for the same service, wait until one of its pods answers
|
|
419
431
|
* `/__lensmcp/alive` with the new generation number, then retire the old tree — the pool dir is shared, the
|
|
420
432
|
* devserver's bind-steal hands each socket path to the newer pod, and the gateway's rotation never runs
|
|
@@ -425,6 +437,15 @@ export declare const POD_RECYCLE_MODE: 'rolling' | 'in-place';
|
|
|
425
437
|
* generation kept (a cold nx + webpack compile of a large service can take minutes). Override
|
|
426
438
|
* `LENSMCP_POD_ROLL_READY_MS`. Default 3 min. */
|
|
427
439
|
export declare const POD_ROLL_READY_MS: number;
|
|
440
|
+
/**
|
|
441
|
+
* How many rolling recycles may be IN FLIGHT at once, gateway-wide (issues/closed/dev-gateway-17). Each roll
|
|
442
|
+
* holds a standby `serve-hmr` tree — an nx client, a devserver parent, its pods and a cold webpack compile —
|
|
443
|
+
* beside the live one for up to {@link POD_ROLL_READY_MS}, and a shared-lib edit makes every service stale
|
|
444
|
+
* in the same sweep: uncapped, that doubled the fleet (twelve concurrent cold compiles for foodguard's six
|
|
445
|
+
* eager services). The rest queue and roll as slots free. Override `LENSMCP_POD_MAX_CONCURRENT_ROLLS`;
|
|
446
|
+
* `0` removes the cap. Default 2.
|
|
447
|
+
*/
|
|
448
|
+
export declare const POD_MAX_CONCURRENT_ROLLS: number;
|
|
428
449
|
/** Spawn grace: a child is never probed (and so never recycled) until it has had this long to BIND its port
|
|
429
450
|
* — a cold vite/nx boot in a big monorepo takes many seconds, and killing a still-booting dev server would
|
|
430
451
|
* be a self-inflicted outage. Override `LENSMCP_LENS_PROBE_GRACE_MS`. Default 45s. */
|
|
@@ -518,7 +539,7 @@ export declare const LENS_REQUEST_RECOVERY_WAIT_MS: number;
|
|
|
518
539
|
* Override `LENSMCP_KEEPALIVE_TIMEOUT_MS`. Default 76s. */
|
|
519
540
|
export declare const GATEWAY_KEEPALIVE_TIMEOUT_MS: number;
|
|
520
541
|
/**
|
|
521
|
-
* The HEADER-phase bound, INDEPENDENT of `keepAliveTimeout` (issues/dev-gateway
|
|
542
|
+
* The HEADER-phase bound, INDEPENDENT of `keepAliveTimeout` (issues/closed/dev-gateway-12, the prod-gateway-2 #14
|
|
522
543
|
* twin). The old derivation (`keepAliveTimeout + 5s` = 81s) rested on a premise verified obsolete on Node
|
|
523
544
|
* 24: an idle keep-alive socket is NOT closed by a shorter `headersTimeout` — only a connection that has
|
|
524
545
|
* begun a request and stalled mid-headers is. So a partial-header hold was 81s for no reason; Node's own
|
|
@@ -539,7 +560,7 @@ export declare const GATEWAY_MAX_CONNECTIONS: number;
|
|
|
539
560
|
* 90s). 0 (default) keeps Node's value. Override `LENSMCP_CONN_CHECK_MS`. */
|
|
540
561
|
export declare const GATEWAY_CONN_CHECK_MS: number;
|
|
541
562
|
/**
|
|
542
|
-
* The transfer STALL bound (issues/dev-gateway
|
|
563
|
+
* The transfer STALL bound (issues/closed/dev-gateway-12, the prod-gateway-2 #06 / round-1 #10 twins): a request
|
|
543
564
|
* body or a response that has moved NOTHING for this long is cut, in either direction. A slow-but-moving
|
|
544
565
|
* upload, a draining download and an idle SSE stream (nothing queued) are never candidates. Dev streams
|
|
545
566
|
* long (MCP streamable-HTTP, SSE), so this is a stall bound, never a duration bound. `0` disables.
|
|
@@ -547,7 +568,7 @@ export declare const GATEWAY_CONN_CHECK_MS: number;
|
|
|
547
568
|
*/
|
|
548
569
|
export declare const BODY_STALL_MS: number;
|
|
549
570
|
/**
|
|
550
|
-
* The h2 front door's RST_STREAM budget, PER SESSION (issues/foodguard
|
|
571
|
+
* The h2 front door's RST_STREAM budget, PER SESSION (issues/closed/foodguard-14). nghttp2's Rapid-Reset mitigation
|
|
551
572
|
* (CVE-2023-44487) allows 1000 resets per session, refilling at 33/s, and CLOSES the session (GOAWAY
|
|
552
573
|
* INTERNAL_ERROR) on the next one — with NO 'error' event on the server side. A browser keeps ONE h2 session
|
|
553
574
|
* per origin for the life of the tab and cancels every in-flight stream on each navigation (a Vite dev page:
|
|
@@ -587,7 +608,7 @@ export interface GatewayRuntimeOptions extends GatewayExecutorSchema {
|
|
|
587
608
|
bodyStallMs?: number;
|
|
588
609
|
/** Per-session h2 RST_STREAM budget — burst (`LENSMCP_H2_RESET_BURST`, default 100k) and refill per second
|
|
589
610
|
* (`LENSMCP_H2_RESET_RATE`, default 10k). nghttp2's 1000 / 33 default tears a dev browser's session down
|
|
590
|
-
* after a few navigations (issues/foodguard
|
|
611
|
+
* after a few navigations (issues/closed/foodguard-14). Both must be set for Node to apply either. */
|
|
591
612
|
h2ResetBurst?: number;
|
|
592
613
|
h2ResetRate?: number;
|
|
593
614
|
/** Customer-supplied lifecycle hooks layered on top of the built-in pipeline. */
|
|
@@ -602,6 +623,8 @@ export interface GatewayHandle {
|
|
|
602
623
|
/** The lens-frontend supervisor (rolling handoffs) — `recover(route.lens, reason)` rolls an app on demand.
|
|
603
624
|
* Present when the gateway runs in `rolling` mode; what an operator command or an e2e drives. */
|
|
604
625
|
lensSupervisor?: LensFrontendSupervisor;
|
|
626
|
+
/** The project graphs services are planned from, one per workspace root (guest-graph.ts) — for the spec and `gateway status`. */
|
|
627
|
+
graphStores?: ProjectGraphStores;
|
|
605
628
|
}
|
|
606
629
|
export interface GwTrace {
|
|
607
630
|
startedAt: number;
|
|
@@ -651,7 +674,7 @@ export interface GatewayRuntime {
|
|
|
651
674
|
* workspace that joins a running daemon via `POST /register` adopts its keys here. */
|
|
652
675
|
readonly keyToProject: Map<string, string>;
|
|
653
676
|
/** key → owning workspace slug, the twin of `keyToProject` — so an internal caller can be attributed to
|
|
654
|
-
* `<wsKey>/<project>`, not just a bare project name (issues/dev-gateway
|
|
677
|
+
* `<wsKey>/<project>`, not just a bare project name (issues/closed/dev-gateway-13). Mutable like its twin. */
|
|
655
678
|
readonly keyToWorkspace: Map<string, string>;
|
|
656
679
|
/** The client-address resolver (shared with prod — `../client-ip`), built from options/env at boot. Optional
|
|
657
680
|
* only so a partial runtime in a spec still works: the edge helpers fall back to a peer-only resolver. */
|
|
@@ -684,6 +707,13 @@ export interface GatewayRuntime {
|
|
|
684
707
|
readonly projectRoots?: Readonly<Record<string, string>>;
|
|
685
708
|
/** Project root plus transitive Nx dependency roots. */
|
|
686
709
|
readonly projectSourceRoots?: Readonly<Record<string, string[]>>;
|
|
710
|
+
/** The project graphs the serve runner is planned from, one per workspace root: the daemon's own
|
|
711
|
+
* ({@link GatewayContext.projectGraphStore}) and each registered workspace's (guest-graph.ts). The sweeper keeps
|
|
712
|
+
* each current. Absent when the gateway was started without a graph — every service then starts through `nx run`. */
|
|
713
|
+
readonly graphStores?: ProjectGraphStores;
|
|
714
|
+
/** The gateway's `sharedCompiler` option — one webpack compilation for this workspace's services
|
|
715
|
+
* (shared-build.ts, issues/open/pod-memory-05). `LENSMCP_SHARED_COMPILER` wins over it. */
|
|
716
|
+
readonly sharedCompiler?: boolean;
|
|
687
717
|
/** Managed lens frontend handoff state, installed by the child supervisor. */
|
|
688
718
|
lensSupervisor?: LensFrontendSupervisor;
|
|
689
719
|
/** frontend identity (`workspace:project`) → live HMR sockets, registered by the WS upgrade pipeline. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var M=Object.defineProperty;var e=(_,E)=>M(_,"name",{value:E,configurable:!0});var
|
|
1
|
+
"use strict";var M=Object.defineProperty;var e=(_,E)=>M(_,"name",{value:E,configurable:!0});var O=Object.defineProperty,R=e((_,E)=>O(_,"name",{value:E,configurable:!0}),"R");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_RESET_RATE=exports.GATEWAY_H2_RESET_BURST=exports.BODY_STALL_MS=exports.GATEWAY_CONN_CHECK_MS=exports.GATEWAY_MAX_CONNECTIONS=exports.GATEWAY_STOP_GRACE_MS=exports.GATEWAY_HEADERS_TIMEOUT_MS=exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_REQUEST_RECOVERY_WAIT_MS=exports.LENS_REPLACEMENT_DRAIN_MS=exports.LENS_REPLACEMENT_READY_MS=exports.LENS_RESTART_MODE=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.POD_MAX_CONCURRENT_ROLLS=exports.POD_ROLL_READY_MS=exports.POD_RECYCLE_MODE=exports.POD_WATCHER_SILENT=exports.POD_WATCHER_UNVERIFIED=exports.LENS_WATCH_SCOPE_ENABLED=exports.LENS_WATCHER_UNVERIFIED=exports.LENS_WATCHER_PROBE_ENABLED=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_ALIVE_PATH=exports.POD_ALIVE_PROBE_TIMEOUT_MS=exports.POD_SLOW_FIRST_BYTE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;function envInt(_,E,L=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?L?0:E:S}e(envInt,"envInt"),R(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.POD_SLOW_FIRST_BYTE_MS=envInt("LENSMCP_POD_SLOW_FIRST_BYTE_MS",10*6e4,!0),exports.POD_ALIVE_PROBE_TIMEOUT_MS=envInt("LENSMCP_POD_ALIVE_PROBE_TIMEOUT_MS",1e3),exports.POD_ALIVE_PATH="/__lensmcp/alive",exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_WATCHER_PROBE_ENABLED=process.env.LENSMCP_LENS_WATCHER_PROBE!=="0",exports.LENS_WATCHER_UNVERIFIED=process.env.LENSMCP_LENS_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.LENS_WATCH_SCOPE_ENABLED=process.env.LENSMCP_LENS_WATCH_SCOPE!=="0",exports.POD_WATCHER_UNVERIFIED=process.env.LENSMCP_POD_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.POD_WATCHER_SILENT=process.env.LENSMCP_POD_WATCHER_SILENT==="recycle"?"recycle":"keep",exports.POD_RECYCLE_MODE=process.env.LENSMCP_POD_RECYCLE_MODE==="in-place"?"in-place":"rolling",exports.POD_ROLL_READY_MS=envInt("LENSMCP_POD_ROLL_READY_MS",18e4),exports.POD_MAX_CONCURRENT_ROLLS=envInt("LENSMCP_POD_MAX_CONCURRENT_ROLLS",2,!0),exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.LENS_RESTART_MODE=process.env.LENSMCP_LENS_RESTART_MODE==="in-place"?"in-place":"rolling",exports.LENS_REPLACEMENT_READY_MS=envInt("LENSMCP_LENS_REPLACEMENT_READY_MS",3e4),exports.LENS_REPLACEMENT_DRAIN_MS=envInt("LENSMCP_LENS_REPLACEMENT_DRAIN_MS",2e3,!0),exports.LENS_REQUEST_RECOVERY_WAIT_MS=envInt("LENSMCP_LENS_REQUEST_RECOVERY_WAIT_MS",15e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3),exports.GATEWAY_HEADERS_TIMEOUT_MS=envInt("LENSMCP_HEADERS_TIMEOUT_MS",6e4),exports.GATEWAY_STOP_GRACE_MS=envInt("LENSMCP_STOP_GRACE_MS",3e3),exports.GATEWAY_MAX_CONNECTIONS=envInt("LENSMCP_MAX_CONNECTIONS",0,!0),exports.GATEWAY_CONN_CHECK_MS=envInt("LENSMCP_CONN_CHECK_MS",0,!0),exports.BODY_STALL_MS=envInt("LENSMCP_BODY_STALL_MS",6e4,!0),exports.GATEWAY_H2_RESET_BURST=envInt("LENSMCP_H2_RESET_BURST",1e5),exports.GATEWAY_H2_RESET_RATE=envInt("LENSMCP_H2_RESET_RATE",1e4);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});var
|
|
1
|
+
"use strict";var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});var s=Object.defineProperty,n=r((e,t)=>s(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.workspaceLogWriter=workspaceLogWriter,exports.closeWorkspaceLogs=closeWorkspaceLogs;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),inspector_secret_1=require("../../../inspector-secret"),fds=new Map,written=new Map,MAX_BYTES=50*1024*1024,CHECK_EVERY=1024*1024;function ensurePrivateDir(e){fs.mkdirSync(e,{recursive:!0,mode:448});try{const t=fs.statSync(e);(t.mode&63)!==0&&(typeof process.getuid!="function"||t.uid===process.getuid())&&fs.chmodSync(e,448)}catch{}}r(ensurePrivateDir,"ensurePrivateDir"),n(ensurePrivateDir,"ensurePrivateDir");function ownerOnly(e){try{fs.chmodSync(e,384)}catch{}}r(ownerOnly,"ownerOnly"),n(ownerOnly,"ownerOnly");function ensureFd(e){const t=fds.get(e);if(t!==void 0)return t;try{ensurePrivateDir(path.dirname(e));const o=fs.openSync(e,"a",384);return ownerOnly(e),fds.set(e,o),written.set(e,0),o}catch{return}}r(ensureFd,"ensureFd"),n(ensureFd,"ensureFd");function closeFd(e){const t=fds.get(e);if(fds.delete(e),written.delete(e),t!==void 0)try{fs.closeSync(t)}catch{}}r(closeFd,"closeFd"),n(closeFd,"closeFd");function rotateIfHuge(e){try{if(fs.statSync(e).size<=MAX_BYTES)return}catch{return}closeFd(e);try{fs.renameSync(e,`${e}.1`)}catch{}ownerOnly(`${e}.1`)}r(rotateIfHuge,"rotateIfHuge"),n(rotateIfHuge,"rotateIfHuge");function workspaceLogWriter(e){const t=path.join(e,".lensmcp","gateway.log");if(ensureFd(t)!==void 0)return o=>{const c=ensureFd(t);if(c===void 0)return;try{fs.writeSync(c,(0,inspector_secret_1.redactInspectorUrls)(o))}catch{closeFd(t);return}const i=(written.get(t)??0)+o.length;i>=CHECK_EVERY?(written.set(t,0),rotateIfHuge(t)):written.set(t,i)}}r(workspaceLogWriter,"workspaceLogWriter"),n(workspaceLogWriter,"workspaceLogWriter");function closeWorkspaceLogs(){for(const e of[...fds.keys()])closeFd(e)}r(closeWorkspaceLogs,"closeWorkspaceLogs"),n(closeWorkspaceLogs,"closeWorkspaceLogs");
|
|
@@ -16,4 +16,7 @@ export interface GatewayExecutorSchema {
|
|
|
16
16
|
dashboardPort?: number;
|
|
17
17
|
/** Override the dashboard hostname. Default lens.<baseDomain>. */
|
|
18
18
|
lensHost?: string;
|
|
19
|
+
/** Compile every service of this workspace in ONE webpack compilation (a build host) instead of one watcher per
|
|
20
|
+
* service. `LENSMCP_SHARED_COMPILER=1|0` wins over it. */
|
|
21
|
+
sharedCompiler?: boolean;
|
|
19
22
|
}
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"lensHost": {
|
|
43
43
|
"type": "string",
|
|
44
44
|
"description": "Override the dashboard's dev hostname. Default lens.<baseDomain>, where <baseDomain> is the longest common dotted suffix of the declared cluster hosts (e.g. lens.tetros.ai.local)."
|
|
45
|
+
},
|
|
46
|
+
"sharedCompiler": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"description": "Compile every service of this workspace in ONE webpack compilation (a build host) instead of one watcher per service: shared libraries are compiled once. LENSMCP_SHARED_COMPILER=1|0 wins over it."
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"additionalProperties": false
|
|
@@ -54,4 +54,6 @@ export interface ServeExecutorSchema {
|
|
|
54
54
|
debugPort?: number;
|
|
55
55
|
/** Tee pod/worker logs (ANSI-stripped) to this file for IDE tailing (WebStorm Logs tab). Env fallback: LENSMCP_LOG_FILE. */
|
|
56
56
|
logFile?: string;
|
|
57
|
+
/** webpack `devtool` for the dev build (default `eval`: no source maps). Env fallback: LENSMCP_DEV_DEVTOOL. */
|
|
58
|
+
devtool?: string;
|
|
57
59
|
}
|
|
@@ -194,6 +194,10 @@
|
|
|
194
194
|
"logFile": {
|
|
195
195
|
"type": "string",
|
|
196
196
|
"description": "Tee every pod/worker log line (ANSI-stripped) to this file, relative to workspace root (e.g. '.lensmcp/logs/my-service.log'). Point an IDE at it — WebStorm: Run/Debug configuration → Logs tab — to see live service logs next to the debugger. Truncated once per serve run. Also settable via the LENSMCP_LOG_FILE env var (this option wins)."
|
|
197
|
+
},
|
|
198
|
+
"devtool": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"description": "webpack devtool for the dev build. Default 'eval': no source maps — every stack frame names its module by absolute path, at the transpiled line, and pods hold one copy of the bundle's text. A map-carrying value (e.g. 'cheap-module-source-map', the cheapest for pods) maps frames to TypeScript lines: ts-loader is told to emit maps and pods run with --enable-source-maps. 'false' = none. Also settable via the LENSMCP_DEV_DEVTOOL env var (this option wins)."
|
|
197
201
|
}
|
|
198
202
|
},
|
|
199
203
|
"required": [
|
|
@@ -2,23 +2,29 @@ import type { ServeExecutorSchema } from './schema';
|
|
|
2
2
|
interface ExecutorContext {
|
|
3
3
|
root: string;
|
|
4
4
|
projectName?: string;
|
|
5
|
+
targetName?: string;
|
|
6
|
+
configurationName?: string;
|
|
5
7
|
projectsConfigurations?: {
|
|
6
8
|
projects: Record<string, {
|
|
7
9
|
root: string;
|
|
8
10
|
}>;
|
|
9
11
|
};
|
|
10
12
|
}
|
|
13
|
+
interface ServeResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}
|
|
11
17
|
/**
|
|
12
18
|
* Dev serve executor — webpack watch mode with integrated devserver.
|
|
13
19
|
*
|
|
14
20
|
* Owns the full devserver lifecycle:
|
|
15
|
-
* 1. Starts webpack in watch mode
|
|
21
|
+
* 1. Starts webpack in watch mode — or, under a gateway running the shared compiler, joins the workspace's ONE
|
|
22
|
+
* compilation (shared-build/attach.ts, issues/open/pod-memory-05) and is told about each build of its bundle
|
|
16
23
|
* 2. On first successful build, forks main.devserver.js with proper env vars
|
|
17
|
-
* 3. On subsequent builds, POSTs /webpack/reload to devserver
|
|
24
|
+
* 3. On subsequent builds, POSTs /webpack/reload to devserver — attached: only when ITS bundle changed
|
|
18
25
|
* 4. On shutdown, cleans up devserver and webpack watcher
|
|
26
|
+
*
|
|
27
|
+
* webpack and the dev config are loaded only when this process compiles: an attached executor holds neither.
|
|
19
28
|
*/
|
|
20
|
-
declare function serveExecutor(options: ServeExecutorSchema, context: ExecutorContext): AsyncGenerator<
|
|
21
|
-
success: boolean;
|
|
22
|
-
baseUrl?: string;
|
|
23
|
-
}>;
|
|
29
|
+
declare function serveExecutor(options: ServeExecutorSchema, context: ExecutorContext): AsyncGenerator<ServeResult>;
|
|
24
30
|
export default serveExecutor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var ne=Object.defineProperty;var c=(e,r)=>ne(e,"name",{value:r,configurable:!0});var te=Object.defineProperty,o=c((e,r)=>te(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),M=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),U=e.configEnv||"development",L=path.join(i,"config",`config.${U}.yaml`),C=3050;let u=C,l=r.projectName,S={};fs.existsSync(L)?(S=yaml.load(fs.readFileSync(L,"utf8")),u=Number(S.port)||u,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${L} \u2014 public devserver port defaults to ${C}. If another service also lacks its config, both bind ${C} \u2192 EADDRINUSE. Add config/config.${U}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const R=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),H=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let O,T;e.gateway?.middleware&&(O=path.resolve(g,e.gateway.middleware),T=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",z=path.resolve(g,K),F=path.resolve(g,Q),Z=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),B={appName:r.projectName,appRoot:g,outputDir:M,main:z,tsConfig:F,assets:e.assets||[],additionalEntryPoints:Z,port:u,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let d=null,$=!1,D=!1,m=!1;const W=path.join(__dirname,"../../main.devserver.js"),q=path.join(__dirname,"../../main.devserver.ts"),V=!fs.existsSync(W)&&fs.existsSync(q),k=V?q:W,ee=path.join(M,"main.js");function I(){d&&!d.killed&&(d.kill("SIGTERM"),d=null)}c(I,"W"),o(I,"cleanup");function y(){m=!0,I()}c(y,"O"),o(y,"stop");const oe=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>y(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",y);function E(){if(m)return;if(!fs.existsSync(k)){console.error(`[serve] devserver script not found at ${k}`);return}const t=["--enable-source-maps"];V&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(k,{cwd:i,env:{...process.env,PORT:String(u),SERVICE_NAME:l,SERVE_PREFIX:R,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:ee,...O&&{GATEWAY_MIDDLEWARE:O},...T&&{GATEWAY_CONFIG:T},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:D?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});d=n,n.on("exit",(a,f)=>{d===n&&(d=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!d&&$&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${u} (service: ${l}${R?`, prefix: /${R}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),H?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${l}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!d||d.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${u}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),I(),E()})}c(re,"oe"),o(re,"_notifyDevServerReload");let N=null;const x=[];function h(t){if(N){const n=N;N=null,n(t)}else x.push(t)}c(h,"p"),o(h,"pushResult");function J(){return x.length>0?Promise.resolve(x.shift()):new Promise(t=>{N=t})}c(J,"Z"),o(J,"waitForNext");let w=o(()=>{},"closeBuild");function A(){return!$||!d?($=!0,E(),!0):!1}c(A,"q"),o(A,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(B),{watch:f,...P}=a,s=n(P);D=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const G=s.watch(a.watchOptions||{},(j,X)=>{if(j){console.error("[serve] Webpack error:",j),h({success:!1});return}const se=X.hasErrors();if(console.info(X.toString({colors:!0,chunks:!1,assets:!0})),se){h({success:!1});return}A(),h({success:!0,baseUrl:`http://localhost:${u}`})});w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"k"),o(b,"compileOnItsOwn");function Y(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(u,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:F,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),D=s.sourceMaps,f.built(s),!s.ok){a.tellWatching(),h({success:!1});return}const G=s.hash!==P;P=s.hash;const j=A();a.built(G&&!j),h({success:!0,baseUrl:`http://localhost:${u}`});return}default:return}})}if(c(Y,"ee"),o(Y,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:B,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?Y(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await J()}finally{y(),w(),oe.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `nx trust` collects host strings from every project's `project.json`, from embedded route options,
|
|
5
5
|
* and from its own `hosts` option, then writes them into `/etc/hosts` as root. Workspace
|
|
6
6
|
* configuration therefore reaches a privileged command, and the only check was "does not contain
|
|
7
|
-
* `*`" (issues/codex-security
|
|
7
|
+
* `*`" (issues/closed/codex-security-33).
|
|
8
8
|
*
|
|
9
9
|
* This is the first of two defences. The second — removing the shell — is what actually closes the
|
|
10
10
|
* injection; this one means a value that could never be a hostname fails loudly at the point it is
|
package/exit-reason.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Why a pod died, in words (issues/foodguard
|
|
2
|
+
* Why a pod died, in words (issues/closed/foodguard-22). "socket hang up" was the only trace a pod left when
|
|
3
3
|
* it died mid-work: a crash, a SIGKILL and an OOM kill read the same, and the stderr that explained
|
|
4
4
|
* which was gone with the process. Pure helpers so the devserver's exit handler stays a few lines.
|
|
5
5
|
*/
|
|
@@ -5,6 +5,18 @@ export interface ForkTsCheckReporterOptions {
|
|
|
5
5
|
/** Override the lens bus file; defaults to `LENSMCP_EVENT_FILE` / `<root>/.lensmcp/events.jsonl`. */
|
|
6
6
|
eventFile?: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Load the SAME module instance `@nx/webpack` loaded.
|
|
10
|
+
*
|
|
11
|
+
* `getCompilerHooks` reads a module-level `WeakMap` keyed by compiler, so hooks registered
|
|
12
|
+
* through a second copy of the package are a different map and never fire. `@nx/webpack` does a
|
|
13
|
+
* bare `require('fork-ts-checker-webpack-plugin')` from its own directory, so resolving from
|
|
14
|
+
* there first is what guarantees we get its copy; the workspace root and our own resolution are
|
|
15
|
+
* fallbacks for a layout that hoists differently.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadForkTsChecker(workspaceRoot: string): {
|
|
18
|
+
getCompilerHooks?: (c: unknown) => unknown;
|
|
19
|
+
} | null;
|
|
8
20
|
export declare class ForkTsCheckReporterPlugin {
|
|
9
21
|
private readonly opts;
|
|
10
22
|
private startedAt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty;var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var o=(e,t)=>p(e,"name",{value:t,configurable:!0});var a=Object.defineProperty,r=o((e,t)=>a(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.ForkTsCheckReporterPlugin=void 0,exports.loadForkTsChecker=loadForkTsChecker;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus");function loadForkTsChecker(e){const t=[];try{t.push(path.dirname(require.resolve("@nx/webpack/package.json",{paths:[e]})))}catch{}t.push(e,__dirname);for(const s of t)try{return require(require.resolve("fork-ts-checker-webpack-plugin",{paths:[s]}))}catch{}return null}o(loadForkTsChecker,"loadForkTsChecker"),r(loadForkTsChecker,"loadForkTsChecker");function toDiagnostic(e,t){return{file:e.file?path.relative(t,e.file)||e.file:"<unknown>",line:e.location?.start?.line??0,column:e.location?.start?.column??0,severity:e.severity==="warning"?"warning":"error",code:/^TS\d+$/.test(e.code)?e.code:`TS${e.code}`,message:e.message}}o(toDiagnostic,"toDiagnostic"),r(toDiagnostic,"toDiagnostic");class ForkTsCheckReporterPlugin{static{o(this,"ForkTsCheckReporterPlugin")}static{r(this,"ForkTsCheckReporterPlugin")}constructor(t){this.startedAt=0,this.opts=t}apply(t){const s=loadForkTsChecker(this.opts.workspaceRoot)?.getCompilerHooks;if(typeof s!="function")return;let c;try{c=s(t)}catch{return}c.start?.tapPromise("LensmcpForkTsCheckReporter",async i=>(this.startedAt=Date.now(),i)),c.issues?.tap("LensmcpForkTsCheckReporter",i=>{try{(0,typecheck_bus_1.publishTypecheckDiagnostics)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"fork-ts-checker",tsConfig:this.opts.tsConfig,diagnostics:(i??[]).map(n=>toDiagnostic(n,this.opts.workspaceRoot)),durationMs:this.startedAt?Date.now()-this.startedAt:0,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}return i})}}exports.ForkTsCheckReporterPlugin=ForkTsCheckReporterPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var i=Object.defineProperty;var o=(r,e)=>i(r,"name",{value:e,configurable:!0});var t=Object.defineProperty,s=o((r,e)=>t(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),guest_graph_1=require("./executors/gateway/runtime/guest-graph");function main(){const r=process.argv[2];r||(console.error("usage: graph-snapshot.js <output file> \u2014 run in the workspace root"),process.exit(2)),(0,guest_graph_1.snapshotWorkspaceGraph)(process.cwd()).then(e=>{fs.writeFileSync(r,JSON.stringify(e),{mode:384}),process.exit(0)},e=>{console.error(`[graph-snapshot] ${e?.message??String(e)}`),process.exit(1)})}o(main,"main"),s(main,"main"),require.main===module&&main();
|