@lensmcp/cluster 1.24.0 → 1.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/bundle-parent.d.ts +8 -0
- package/bundle-parent.js +1 -0
- package/checkout-cache.d.ts +5 -0
- package/checkout-cache.js +1 -0
- package/create-webpack-dev.d.ts +1 -1
- package/create-webpack-dev.js +1 -1
- package/devserver-notifier.d.ts +1 -1
- package/devserver-notifier.js +1 -1
- package/devserver-transport.d.ts +41 -0
- package/devserver-transport.js +1 -0
- package/executors/gateway/manifest.js +1 -1
- package/executors/gateway/runtime/auth.d.ts +7 -0
- package/executors/gateway/runtime/auth.js +1 -1
- package/executors/gateway/runtime/chooser.d.ts +2 -1
- package/executors/gateway/runtime/chooser.js +23 -10
- package/executors/gateway/runtime/control.d.ts +14 -0
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/discovery.d.ts +15 -2
- package/executors/gateway/runtime/discovery.js +1 -1
- package/executors/gateway/runtime/edge.d.ts +7 -0
- package/executors/gateway/runtime/edge.js +1 -1
- package/executors/gateway/runtime/guest-replay.d.ts +22 -0
- package/executors/gateway/runtime/guest-replay.js +2 -0
- package/executors/gateway/runtime/instance.d.ts +104 -0
- package/executors/gateway/runtime/instance.js +2 -0
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +4 -0
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/registration-guards.js +1 -1
- package/executors/gateway/runtime/route-registry.d.ts +7 -0
- package/executors/gateway/runtime/scope.d.ts +18 -0
- package/executors/gateway/runtime/scope.js +1 -1
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/service-keys.d.ts +12 -3
- package/executors/gateway/runtime/service-keys.js +1 -1
- package/executors/gateway/runtime/shared-build.d.ts +1 -1
- package/executors/gateway/runtime/shared-build.js +3 -3
- package/executors/gateway/runtime/types.d.ts +42 -0
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/upgrade.d.ts +5 -7
- package/executors/gateway/runtime/upgrade.js +1 -1
- package/executors/gateway/runtime/workspace-middleware.d.ts +38 -0
- package/executors/gateway/runtime/workspace-middleware.js +1 -0
- package/executors/gateway/runtime/workspace-registry.d.ts +7 -0
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/trust.impl.js +3 -3
- package/heap-snapshot.js +1 -1
- package/inspector-secret.d.ts +6 -0
- package/inspector-secret.js +1 -1
- package/main.devserver.d.ts +4 -38
- package/main.devserver.js +10 -10
- package/package.json +4 -4
- package/pod-memory.d.ts +6 -0
- package/pod-memory.js +1 -1
- package/shared-build/host.js +2 -2
|
@@ -1,3 +1,3 @@
|
|
|
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
|
|
1
|
+
"use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"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_middleware_1=require("./workspace-middleware"),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(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"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(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.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`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${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(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:v,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:v,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:re,keyToProject:te,keyToWorkspace:oe}=(0,service_keys_1.loadServiceKeys)(r.root,v,i),ne=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:v,serviceKeys:re,keyToProject:te,keyToWorkspace:oe,clientIp:ne,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(v,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ae=createClientErrorLogGate(),x=h2BudgetFromEnv(),ie=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ie(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). 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).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),N=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>N.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),N.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,N),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),ce=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[ce,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),le=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=N.startSweeper();await k.bootSingletonsAndApps();for(const e of v)e.decl.eager&&N.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",le),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ae(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
|
|
2
2
|
\r
|
|
3
|
-
`),
|
|
3
|
+
`),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,C=0;u.on("stream",M=>{G.admitStream(u,M)&&M.once("close",()=>{u.destroyed?C+=1:M.rstCode===E&&(g+=1)})}),u.once("close",()=>{C!==0&&(console.warn(`[gateway] h2 session closed with ${C} stream(s) in flight after ${g} 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).`),w.emit("warning",`h2 session lost with ${C} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:C,clientResets:g}))}),u.on("error",M=>{const pe=M.code??M.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:v,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of v)N.killSpawned(e,"gateway shutdown");await N.stopBuildHost(),await k.reapAll(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ServiceCtl } from './types';
|
|
2
|
-
/** The gateway's on-disk cache root — shared by service keys AND the TLS cert cache.
|
|
2
|
+
/** The gateway's on-disk cache root — shared by service keys AND the TLS cert cache. The CHECKOUT's own:
|
|
3
|
+
* `node_modules/.cache/davnx-webpack`, or `.lensmcp/cache/davnx-webpack` when `node_modules` is a symlink to
|
|
4
|
+
* another checkout's — one key file for two copies made each one's keys the other's (checkout-cache.ts). */
|
|
3
5
|
export declare function gatewayCacheDir(root: string): string;
|
|
4
6
|
/**
|
|
5
7
|
* The RUNTIME key for `rt.serviceKeys` — a workspace-qualified project id.
|
|
@@ -19,12 +21,18 @@ export declare function gatewayCacheDir(root: string): string;
|
|
|
19
21
|
export declare function serviceKeyId(wsKey: string | undefined, project: string): string;
|
|
20
22
|
/** Load (or generate + persist) a stable key per pod service, plus the inverse
|
|
21
23
|
* key→project map used to authenticate internal callers. Tolerant of a
|
|
22
|
-
* read-only fs (keys then live only for this process).
|
|
23
|
-
|
|
24
|
+
* read-only fs (keys then live only for this process).
|
|
25
|
+
*
|
|
26
|
+
* `taken` names key VALUES another workspace already holds: those are replaced with fresh ones and persisted
|
|
27
|
+
* (`regenerated`). A copy of a checkout — `cp -R`, or a copy-on-write clone of `node_modules` with its `.cache` —
|
|
28
|
+
* carries the original's key file, and two workspaces with one key value cannot be told apart: adopting it
|
|
29
|
+
* re-attributed the original's internal calls to the copy, which the gateway then refused as cross-workspace. */
|
|
30
|
+
export declare function loadServiceKeys(root: string, services: ServiceCtl[], wsKey?: string, taken?: (key: string) => boolean): {
|
|
24
31
|
serviceKeys: Record<string, string>;
|
|
25
32
|
keyToProject: Map<string, string>;
|
|
26
33
|
keyToWorkspace: Map<string, string>;
|
|
27
34
|
byProject: Record<string, string>;
|
|
35
|
+
regenerated: string[];
|
|
28
36
|
};
|
|
29
37
|
/**
|
|
30
38
|
* Mint + adopt keys for a workspace that JOINS a running daemon.
|
|
@@ -48,4 +56,5 @@ export declare function adoptServiceKeys(target: {
|
|
|
48
56
|
keyToWorkspace?: Map<string, string>;
|
|
49
57
|
}, root: string, services: ServiceCtl[], wsKey?: string): {
|
|
50
58
|
added: string[];
|
|
59
|
+
regenerated: string[];
|
|
51
60
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var l=Object.defineProperty;var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var a=(e,c)=>l(e,"name",{value:c,configurable:!0});var h=Object.defineProperty,y=a((e,c)=>h(e,"name",{value:c,configurable:!0}),"y");Object.defineProperty(exports,"__esModule",{value:!0}),exports.gatewayCacheDir=gatewayCacheDir,exports.serviceKeyId=serviceKeyId,exports.loadServiceKeys=loadServiceKeys,exports.adoptServiceKeys=adoptServiceKeys;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),checkout_cache_1=require("../../../checkout-cache");function gatewayCacheDir(e){return(0,checkout_cache_1.checkoutCacheDir)(e,"davnx-webpack")}a(gatewayCacheDir,"gatewayCacheDir"),y(gatewayCacheDir,"gatewayCacheDir");function serviceKeyId(e,c){return`${e??""} ${c}`}a(serviceKeyId,"serviceKeyId"),y(serviceKeyId,"serviceKeyId");function loadServiceKeys(e,c,p,k){const n=gatewayCacheDir(e),v=path.join(n,"service-keys.json");let r={};try{r=JSON.parse(fs.readFileSync(v,"utf8"))}catch{}let u=!1;const d=[];for(const[o,f]of Object.entries(r))k?.(f)&&(r[o]=(0,node_crypto_1.randomBytes)(16).toString("hex"),u=!0,d.push(o));for(const o of c)r[o.project]||(r[o.project]=(0,node_crypto_1.randomBytes)(16).toString("hex"),u=!0);if(u)try{fs.mkdirSync(n,{recursive:!0}),fs.writeFileSync(v,JSON.stringify(r,null,2))}catch{}const t=p??c[0]?.wsKey,s={};for(const[o,f]of Object.entries(r))s[serviceKeyId(t,o)]=f;const i=new Map(Object.entries(r).map(([o,f])=>[f,o])),j=new Map(t?Object.values(r).map(o=>[o,t]):[]);return{serviceKeys:s,keyToProject:i,keyToWorkspace:j,byProject:r,regenerated:d}}a(loadServiceKeys,"loadServiceKeys"),y(loadServiceKeys,"loadServiceKeys");function adoptServiceKeys(e,c,p,k){const n=k??p[0]?.wsKey,v=y(t=>{const s=e.keyToWorkspace?.get(t);return s!==void 0&&s!==n},"taken"),{byProject:r,regenerated:u}=loadServiceKeys(c,p,n,v),d=[];for(const t of p){const s=serviceKeyId(t.wsKey??n,t.project),i=r[t.project];if(!i||e.serviceKeys[s]===i)continue;e.serviceKeys[s]=i,e.keyToProject.set(i,t.project);const j=t.wsKey??n;j&&e.keyToWorkspace?.set(i,j),d.push(t.project)}return{added:d,regenerated:u}}a(adoptServiceKeys,"adoptServiceKeys"),y(adoptServiceKeys,"adoptServiceKeys");
|
|
@@ -31,7 +31,7 @@ import type { GatewayRuntime, ServiceCtl } from './types';
|
|
|
31
31
|
/** Exits within {@link CRASH_WINDOW_MS} after which the host is given up on. */
|
|
32
32
|
export declare const HOST_MAX_RESTARTS = 3;
|
|
33
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'>>;
|
|
34
|
+
export type BuildMember = Pick<ServiceCtl, 'root'> & Partial<Pick<ServiceCtl, 'wsKey' | 'sockNs'>>;
|
|
35
35
|
export interface SharedBuildRelay {
|
|
36
36
|
/** Will a runner of `svc` be compiled by its workspace's build host (it is then started with `LENSMCP_SHARED_BUILD=1`)? */
|
|
37
37
|
offeredTo(svc: BuildMember): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)){const
|
|
1
|
+
"use strict";var A=Object.defineProperty;var y=(r,o)=>A(r,"name",{value:o,configurable:!0});var j=Object.defineProperty,i=y((r,o)=>j(r,"name",{value:o,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 o=path.join(r,"build-host.js");if(fs.existsSync(o))return{path:o,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}}y(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}}y(workspaceBuildHostScript,"workspaceBuildHostScript"),i(workspaceBuildHostScript,"workspaceBuildHostScript");function createSharedBuildRelay(r,o={}){const u=o.script===void 0?buildHostScript():o.script??void 0,_=o.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 n=h.get(a);return n||(n=a===d?createWorkspaceRelay(r,{root:r.root,...r.wsKey?{wsKey:r.wsKey}:{},...r.sockNs?{sockNs:r.sockNs}:{},eventFile:r.eventFile},u,o):createWorkspaceRelay(r,{root:e.root,...e.wsKey?{wsKey:e.wsKey}:{},...e.sockNs?{sockNs:e.sockNs}:{},eventFile:path.join(e.root,".lensmcp","events.jsonl")},_(e.root),o),h.set(a,n)),n},"relayFor");return{offeredTo:i(e=>(0,protocol_1.sharedCompilerEnabled)(o.gatewayEnv??process.env,r.sharedCompiler)&&v(e).offered(),"offeredTo"),attach(e,a,n){const S=v(e);f.set(n,S),S.attach(`${e.project}#g${a}`,n)},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),n=h.get(a);return h.delete(a),n?.stop()??Promise.resolve()},async stop(){const e=[...h.values()];h.clear(),await Promise.all(e.map(a=>a.stop()))}}}y(createSharedBuildRelay,"createSharedBuildRelay"),i(createSharedBuildRelay,"createSharedBuildRelay");function createWorkspaceRelay(r,o,u,_){const h=_.spawnProcess??node_child_process_1.spawn,f=_.log??(t=>console.log(t)),d=new Map,v=new WeakMap;let e,a=[],n,S=!1;const E=(0,registration_guards_1.canonicalRoot)(o.root)===(0,registration_guards_1.canonicalRoot)(r.root)?"this workspace":`'${o.wsKey??path.basename(o.root)}'`,b=i((t,s)=>{if(!t.connected)return;const c={channel:protocol_1.SHARED_BUILD_CHANNEL,event:s};try{t.send(c,void 0,void 0,()=>{})}catch{}},"toRunner"),R=i(t=>{if(e?.connected)try{e.send(t,void 0,void 0,()=>{})}catch{}},"toHost"),x=i(()=>{if(_.reapStale===!1)return;let t;try{t=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return}for(const s of t.split(`
|
|
2
|
+
`)){const c=s.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!c||!(0,protocol_1.isBuildHostCommandOf)(c[3],o.root))continue;const l=Number(c[1]),p=Number(c[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"),N=i(()=>{if(e||S||n||!u)return e;x();const t=o.wsKey||path.basename(o.root),s={...(0,pod_env_1.podBaseEnv)(process.env,r.root,o.root),NX_DAEMON:"false",NODE_ENV:"development",NX_WORKSPACE_ROOT:o.root,...o.wsKey?{LENSMCP_WS_KEY:o.wsKey}:{},...o.sockNs?{LENSMCP_SOCK_NS:o.sockNs}:{},LENSMCP_EVENT_FILE:o.eventFile,LENSMCP_GATEWAY_PID:String(process.pid)},c=h((0,process_name_1.namedExecPath)((0,process_name_1.processName)(t,protocol_1.BUILD_HOST_ROLE)),[...u.execArgv,u.path,...(0,protocol_1.buildHostArgs)(o.root)],{cwd:o.root,detached:!0,env:s,stdio:["ignore","pipe","pipe","ipc"]});e=c,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
3
|
`))g.trim()&&p.write(`[build] ${g}
|
|
4
|
-
`)},"relayOut");return
|
|
4
|
+
`)},"relayOut");return c.stdout?.on("data",l(process.stdout)),c.stderr?.on("data",l(process.stderr)),c.on("message",p=>{if(p?.type==="member"){const m=d.get(p.member);m&&b(m.proc,p.event)}}),c.on("exit",(p,m)=>{if(e===c&&(e=void 0),S||r.stopped())return;const g=Date.now();if(a=[...a.filter(w=>g-w<CRASH_WINDOW_MS),g],a.length>=exports.HOST_MAX_RESTARTS){n=`the build host exited ${a.length} times in a minute (last: ${p??m})`,f(`[gateway] ${n} \u2014 every service compiles on its own from now on`);for(const w of d.values())b(w.proc,{type:"refused",reason:n});d.clear();return}if(d.size!==0&&(f(`[gateway] build host exited (${p??m}) \u2014 restarting it for ${d.size} service tree(s)`),N()))for(const[w,{join:P}]of d)R({type:"join",member:w,join:P})}),c},"start"),k=i(t=>{const s=v.get(t);!s||d.get(s)?.proc!==t||(d.delete(s),R({type:"leave",member:s}))},"detach");return{offered:i(()=>!!u&&!n,"offered"),attach:i((t,s)=>{v.set(s,t),s.on("message",c=>{if((0,protocol_1.isRunnerJoinMessage)(c)){if(n||!N()){b(s,{type:"refused",reason:n??"the build host could not be started"});return}d.set(t,{proc:s,join:c.join}),R({type:"join",member:t,join:c.join})}}),s.once("exit",()=>k(s))},"attach"),detach:k,hostPid:i(()=>e?.pid,"hostPid"),stop(){S=!0,d.clear();const t=e;return e=void 0,!t||t.exitCode!==null||t.signalCode!==null?Promise.resolve():new Promise(s=>{const c=i(()=>{clearTimeout(l),s()},"done");t.once("exit",c);try{process.kill(-t.pid,"SIGTERM")}catch{try{t.kill("SIGTERM")}catch{}}const l=setTimeout(()=>{try{process.kill(-t.pid,"SIGKILL")}catch{try{t.kill("SIGKILL")}catch{}}s()},6e3);l.unref?.()})}}}y(createWorkspaceRelay,"createWorkspaceRelay"),i(createWorkspaceRelay,"createWorkspaceRelay");
|
|
@@ -55,6 +55,16 @@ export interface ClusterProjectDecl {
|
|
|
55
55
|
default?: boolean;
|
|
56
56
|
/** Override the auto `internal.<host>` name, or `false` to disable it. */
|
|
57
57
|
internalHost?: string | false;
|
|
58
|
+
/**
|
|
59
|
+
* The identities this service may NAME as its `x-api-key-id` on an `internal.<host>` call, besides its own
|
|
60
|
+
* project name. The gateway stamps `x-api-key-id` from the caller's validated key — its project — which is
|
|
61
|
+
* right for one identity per service, and collapsed a service that legitimately acts as several: FoodGuard's
|
|
62
|
+
* engine calls knowledge as `engine`, `console` and `platform` on one secret, knowledge grants each different
|
|
63
|
+
* stores, and every call arrived as `engine` (issues/closed/foodguard-50). A name listed here is kept when this
|
|
64
|
+
* service's own key presents it; anything else it claims is replaced by its project name, as before. A name
|
|
65
|
+
* that is ANOTHER service's project in the same workspace is never honoured — that would be impersonation.
|
|
66
|
+
*/
|
|
67
|
+
callerIds?: string[];
|
|
58
68
|
/** Edge auth policy (default + per-path overrides) — generated from controller decorators
|
|
59
69
|
* by @tetros-server/gateway-sync; the gateway verifies + injects identity + enforces it.
|
|
60
70
|
* Absent ⇒ the service is unguarded at the edge (current behavior — opt-in enforcement). */
|
|
@@ -99,6 +109,8 @@ export interface SockPool {
|
|
|
99
109
|
scannedAt: number;
|
|
100
110
|
}
|
|
101
111
|
export interface Route {
|
|
112
|
+
/** The workspace the route belongs to (its key) — whose edge middleware runs on it (workspace-middleware.ts). */
|
|
113
|
+
wsKey?: string;
|
|
102
114
|
host?: string;
|
|
103
115
|
prefix?: string;
|
|
104
116
|
/** For a host-less `default` catch-all: the apex it belongs to — so a shared multi-workspace gateway
|
|
@@ -137,6 +149,10 @@ export interface LensFrontend {
|
|
|
137
149
|
sourceRoots?: string[];
|
|
138
150
|
/** Set once the Vite child has been spawned (idempotency guard). */
|
|
139
151
|
started?: boolean;
|
|
152
|
+
/** A worktree instance's variables (instance.ts `InstanceRouting.env`), handed to this app's Vite. */
|
|
153
|
+
env?: Readonly<Record<string, string>>;
|
|
154
|
+
/** A FROZEN instance's app (instance.ts): its Vite watches nothing, and no source change hands it off. */
|
|
155
|
+
frozen?: boolean;
|
|
140
156
|
}
|
|
141
157
|
export interface SourceSetChange {
|
|
142
158
|
changedAt: number;
|
|
@@ -214,6 +230,17 @@ export interface ServiceCtl {
|
|
|
214
230
|
/** The owning workspace's key — passed as `LENSMCP_WS_KEY` to the spawn so the devserver writes its pods
|
|
215
231
|
* to `$TMPDIR/<wsKey>/<svc>-devserver` (the namespaced pool the daemon scans for this fragment). */
|
|
216
232
|
wsKey: string;
|
|
233
|
+
/** Set when the pods' socket folder is NOT `wsKey` (a worktree instance's short namespace — scope.ts
|
|
234
|
+
* `sockNs`): passed as `LENSMCP_SOCK_NS`, and the pool is `$TMPDIR/<sockNs>/<svc>-devserver`. */
|
|
235
|
+
sockNs?: string;
|
|
236
|
+
/** A worktree instance's variables (instance.ts `InstanceRouting.env`): its hosts and `instance.env`, injected
|
|
237
|
+
* into this service's tree under the gateway's own variables. */
|
|
238
|
+
instanceEnv?: Readonly<Record<string, string>>;
|
|
239
|
+
/** Set while this service waits for a cold-start slot ({@link MAX_COLD_STARTS}): later requests wait on it
|
|
240
|
+
* rather than queueing a second start. */
|
|
241
|
+
coldWait?: Promise<void>;
|
|
242
|
+
/** A FROZEN instance's service (instance.ts): compiled once, no watcher, no type checker — `LENSMCP_FREEZE=1`. */
|
|
243
|
+
frozen?: boolean;
|
|
217
244
|
pool: SockPool;
|
|
218
245
|
proc?: ChildProcess;
|
|
219
246
|
state: 'down' | 'starting' | 'up';
|
|
@@ -252,6 +279,10 @@ export interface ServiceCtl {
|
|
|
252
279
|
* failure recovers without a manual restart; too many in the window ⇒ stop hot-looping (the next request
|
|
253
280
|
* still spawns it via scale-from-zero). Shared by the exit-self-heal AND the sweeper's wedge-recycle. */
|
|
254
281
|
restarts?: number[];
|
|
282
|
+
/** Self-heal: wedge recycles in a row with no pod coming up in between — `markUp` ends the streak. The window above
|
|
283
|
+
* cannot see a wedge that returns slower than it: FoodGuard's engine was recycled every ~2.5 min with zero live
|
|
284
|
+
* pods, each one "attempt 1", for as long as anyone watched (issues/closed/foodguard-45). */
|
|
285
|
+
wedgeRecycles?: number;
|
|
255
286
|
/** Self-heal: when the sweeper FIRST observed this service wedged (live `proc`, 0 live pods, not
|
|
256
287
|
* cold-starting). Cleared once a pod appears; once it exceeds {@link WEDGE_RECYCLE_GRACE_MS} the sweeper
|
|
257
288
|
* recycles the wedged tree. */
|
|
@@ -442,6 +473,15 @@ export declare const POD_ROLL_READY_MS: number;
|
|
|
442
473
|
* `0` removes the cap. Default 2.
|
|
443
474
|
*/
|
|
444
475
|
export declare const POD_MAX_CONCURRENT_ROLLS: number;
|
|
476
|
+
/**
|
|
477
|
+
* How many services may COLD START at once, daemon-wide — every workspace's and every worktree instance's
|
|
478
|
+
* together. A cold start is a whole `serve-hmr` tree and a cold webpack compile; a page that touches a dozen lazy
|
|
479
|
+
* services, or two dozen agent worktrees waking at once, used to start them all in the same second and swap the
|
|
480
|
+
* machine (the 2026-10-06 stall had eleven compiles at once). The rest queue, first come first served, and start
|
|
481
|
+
* as slots free; a waiting request just waits longer. Override `LENSMCP_MAX_COLD_STARTS`; `0` removes the cap.
|
|
482
|
+
* Default: half the cores, at least 2.
|
|
483
|
+
*/
|
|
484
|
+
export declare const MAX_COLD_STARTS: number;
|
|
445
485
|
/** Spawn grace: a child is never probed (and so never recycled) until it has had this long to BIND its port
|
|
446
486
|
* — a cold vite/nx boot in a big monorepo takes many seconds, and killing a still-booting dev server would
|
|
447
487
|
* be a self-inflicted outage. Override `LENSMCP_LENS_PROBE_GRACE_MS`. Default 45s. */
|
|
@@ -655,6 +695,8 @@ export interface GatewayRuntime {
|
|
|
655
695
|
* multi-workspace gateway can host several workspaces without same-named services colliding. Flows to
|
|
656
696
|
* each service devserver as `LENSMCP_WS_KEY`. */
|
|
657
697
|
readonly wsKey: string;
|
|
698
|
+
/** This workspace's pod socket folder when it is not `wsKey` (scope.ts `sockNs`). */
|
|
699
|
+
readonly sockNs?: string;
|
|
658
700
|
readonly eventFile: string;
|
|
659
701
|
/** The LIVE merged route table (mutated in place from `registry` on every register/unregister, so the
|
|
660
702
|
* request pipeline — which reads `rt.routes` fresh per request — picks the change up with no restart). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var O=Object.defineProperty;var L=(_,E)=>O(_,"name",{value:E,configurable:!0});var e=Object.defineProperty,R=L((_,E)=>e(_,"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.MAX_COLD_STARTS=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;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os"));function envInt(_,E,M=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?M?0:E:S}L(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.MAX_COLD_STARTS=envInt("LENSMCP_MAX_COLD_STARTS",Math.max(2,Math.floor(os.availableParallelism()/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,21 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The WebSocket upgrade pipeline. WS upgrades enforce the SAME trust model as
|
|
3
|
-
* HTTP — otherwise an `internal.<host>` socket would be reachable with no
|
|
4
|
-
* x-api-key, and a public socket would reach pods without the x-internal-token
|
|
5
|
-
* they expect. Reuses the shared auth + proxy layers so it can't drift from the
|
|
6
|
-
* HTTP handler.
|
|
7
|
-
*/
|
|
8
1
|
import type * as http from 'node:http';
|
|
9
2
|
import type * as stream from 'node:stream';
|
|
10
3
|
import type { GatewayRuntime } from './types';
|
|
11
4
|
import type { EdgeAuth } from './auth';
|
|
12
5
|
import type { ProxyLayer } from './proxy';
|
|
13
6
|
import type { HooksLayer } from './hooks';
|
|
7
|
+
import type { Observability } from './observability';
|
|
14
8
|
export interface UpgradeDeps {
|
|
15
9
|
rt: GatewayRuntime;
|
|
16
10
|
auth: EdgeAuth;
|
|
17
11
|
proxy: ProxyLayer;
|
|
18
12
|
hooks: HooksLayer;
|
|
13
|
+
/** Where a refused lens HMR socket is reported (see {@link watchLensHmr}). */
|
|
14
|
+
obs?: Pick<Observability, 'emit'>;
|
|
19
15
|
}
|
|
16
|
+
/** One report per frontend per this window: a reload storm must not become a log storm. */
|
|
17
|
+
export declare const LENS_HMR_REFUSAL_REPORT_MS = 10000;
|
|
20
18
|
export type UpgradeListener = (req: http.IncomingMessage, socket: stream.Duplex, head: Buffer) => void;
|
|
21
19
|
export declare function createUpgrade(deps: UpgradeDeps): UpgradeListener;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var x=Object.defineProperty;var h=(c,s)=>x(c,"name",{value:s,configurable:!0});var b=Object.defineProperty,f=h((c,s)=>b(c,"name",{value:s,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.LENS_HMR_REFUSAL_REPORT_MS=void 0,exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");exports.LENS_HMR_REFUSAL_REPORT_MS=1e4;function accountUpgrade(c,s){const i=c.svc;if(!i||s.destroyed)return;i.inflight+=1,i.lastUsed=Date.now();let l=!1;s.once("close",()=>{l||(l=!0,i.inflight-=1,i.lastUsed=Date.now())})}h(accountUpgrade,"accountUpgrade"),f(accountUpgrade,"accountUpgrade");function createUpgrade(c){const{rt:s,auth:i,proxy:l,hooks:p,obs:R}=c,w=new WeakMap,v=new Map,_=f((e,r)=>{const n=Date.now();n-(v.get(e.frontendKey)??0)<exports.LENS_HMR_REFUSAL_REPORT_MS||(v.set(e.frontendKey,n),console.warn(`[gateway] lens HMR socket refused: ${e.frontendKey} at ${e.target} answered ${r} for a tab on ${e.host} \u2014 that tab loads but gets no hot updates; the lens runtime reloads it once if it had a socket before.`),R?.emit("warning",`lens HMR socket refused: ${e.frontendKey} (${r})`,`cluster-lens-hmr-refused:${e.frontendKey}`,{kind:"lens-hmr-refused",frontendKey:e.frontendKey,host:e.host,target:e.target,answer:r},void 0,e.busFile))},"reportRefusal");l.proxy.on?.("proxyReqWs",(e,r)=>{const n=w.get(r);if(!n)return;const a=e;a.once("upgrade",()=>{n.answered=!0}),a.once("response",y=>{n.answered=!0,_(n,`HTTP ${y.statusCode??"?"}`)})});const k=f(async(e,r,n)=>{(0,edge_1.stripOwnedHeaders)(e);const a=(0,edge_1.canonicalize)(e.url??"/");if(a===void 0){r.destroy();return}e.url=a.path+a.query;const y=(e.headers.host||"").split(":")[0],t=(0,manifest_1.matchRoute)(s.routes,y,a.policy+a.query);if(!t){r.destroy();return}(0,edge_1.resolveClientIp)(s,e);const d=p.upgradeContext(e,r,void 0);if(d.route=t,t.internal){const o=i.internal(e,t,void 0);if(!o.ok){r.destroy();return}d.caller=o.caller}else{const o=i.public(e,t,void 0,a.policy);if(!o.ok){r.destroy();return}if(d.claims=o.claims,p.has("afterAuth")&&(await p.run("afterAuth",d),d.responded()))return}const m=t.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(t.svc?.wsKey,t.project)]:void 0;if(m&&(e.headers["x-internal-token"]=m),t.prependPrefix&&!e.url?.startsWith(t.prependPrefix)&&(e.url=t.prependPrefix+(e.url??"/")),(0,edge_1.stampForwardingHeaders)(s,e),p.has("onUpgrade")&&(await p.run("onUpgrade",d),d.responded()))return;const g=t.pool?(()=>{const o=(0,discovery_1.pickSock)(t.pool,s.scanTtlMs);return o?{socketPath:o}:void 0})():t.target;if(!g){r.destroy();return}if(t.lens&&String(e.headers["sec-websocket-protocol"]??"").split(",").map(o=>o.trim()).includes("vite-hmr")){const o=t.lens.workspaceKey?`${t.lens.workspaceKey}:${t.lens.project}`:t.lens.project;(0,hmr_reload_1.registerHmrClient)(s,o,r);const u=!t.lens.workspaceRoot||path.resolve(t.lens.workspaceRoot)===path.resolve(s.root??"");w.set(e,{frontendKey:o,host:y,target:String(g),answered:!1,...u?{}:{busFile:path.join(t.lens.workspaceRoot,".lensmcp","events.jsonl")}})}accountUpgrade(t,r),l.proxy.ws(e,r,n,{target:g,changeOrigin:!t.pool,agent:l.agentForTarget(g)},o=>{const u=w.get(e);u&&!u.answered&&_(u,o?.code??o?.message??"an error"),r.destroy()})},"run");return(e,r,n)=>{r.on("error",()=>r.destroy()),k(e,r,n).catch(()=>r.destroy())}}h(createUpgrade,"createUpgrade"),f(createUpgrade,"createUpgrade");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type * as http from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* What a middleware is told besides the request: whose route it is. A middleware runs inside the daemon, so its
|
|
4
|
+
* `process.env` is the daemon owner's — a worktree instance's middleware reads the instance's own addresses (its
|
|
5
|
+
* `AUTH_API_RESOURCE`, …) from `env`: the instance's variables (instance.ts), empty for any other workspace.
|
|
6
|
+
* A middleware that takes one argument ignores it.
|
|
7
|
+
*/
|
|
8
|
+
export interface EdgeMiddlewareContext {
|
|
9
|
+
wsKey?: string;
|
|
10
|
+
root?: string;
|
|
11
|
+
env: Readonly<Record<string, string>>;
|
|
12
|
+
}
|
|
13
|
+
export type EdgeMiddleware = (req: http.IncomingMessage, ctx?: EdgeMiddlewareContext) => void;
|
|
14
|
+
export type GuestMiddlewarePolicy = 'own' | 'owner' | 'none';
|
|
15
|
+
export declare function guestMiddlewarePolicy(env?: NodeJS.ProcessEnv): GuestMiddlewarePolicy;
|
|
16
|
+
/**
|
|
17
|
+
* The `middleware` a workspace's own gateway target declares (relative to its root), found the way `lensmcp gateway
|
|
18
|
+
* start` finds the target: the project whose `targets.*.executor` is `@lensmcp/cluster:gateway`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function findGatewayMiddleware(root: string, projects: Readonly<Record<string, {
|
|
21
|
+
root: string;
|
|
22
|
+
}>>): string | undefined;
|
|
23
|
+
/** Load a middleware module fresh (an edit in the workspace takes effect on its next registration). */
|
|
24
|
+
export declare function loadMiddleware(file: string): EdgeMiddleware;
|
|
25
|
+
/** The daemon's middleware per workspace. */
|
|
26
|
+
export interface WorkspaceMiddleware {
|
|
27
|
+
/** A workspace registered: load its own middleware (`relPath` under `root`), or record that it has none. */
|
|
28
|
+
set(wsKey: string, root: string, relPath: string | undefined): void;
|
|
29
|
+
drop(wsKey: string): void;
|
|
30
|
+
/** The middleware for a route of workspace `wsKey` (undefined `wsKey` = the owner's, e.g. its dashboard route). */
|
|
31
|
+
for(wsKey: string | undefined): EdgeMiddleware | undefined;
|
|
32
|
+
}
|
|
33
|
+
export declare function createWorkspaceMiddleware(opts: {
|
|
34
|
+
ownerKey: string;
|
|
35
|
+
owner?: EdgeMiddleware;
|
|
36
|
+
policy?: GuestMiddlewarePolicy;
|
|
37
|
+
load?: (file: string) => EdgeMiddleware;
|
|
38
|
+
}): WorkspaceMiddleware;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var w=Object.defineProperty;var a=(r,t)=>w(r,"name",{value:t,configurable:!0});var c=Object.defineProperty,i=a((r,t)=>c(r,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.guestMiddlewarePolicy=guestMiddlewarePolicy,exports.findGatewayMiddleware=findGatewayMiddleware,exports.loadMiddleware=loadMiddleware,exports.createWorkspaceMiddleware=createWorkspaceMiddleware;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function guestMiddlewarePolicy(r=process.env){const t=r.LENSMCP_GUEST_MIDDLEWARE;return t==="owner"||t==="none"?t:"own"}a(guestMiddlewarePolicy,"guestMiddlewarePolicy"),i(guestMiddlewarePolicy,"guestMiddlewarePolicy");function findGatewayMiddleware(r,t){for(const n of Object.values(t)){let o;try{o=JSON.parse(fs.readFileSync(path.join(r,n.root,"project.json"),"utf8"))}catch{continue}for(const e of Object.values(o.targets??{}))if(e?.executor==="@lensmcp/cluster:gateway"&&typeof e.options?.middleware=="string"&&e.options.middleware)return e.options.middleware}}a(findGatewayMiddleware,"findGatewayMiddleware"),i(findGatewayMiddleware,"findGatewayMiddleware");function loadMiddleware(r){const t=require.resolve(r);delete require.cache[t];const n=require(t),o=n.default??n;if(typeof o!="function")throw new Error(`${r} exports no function`);return o}a(loadMiddleware,"loadMiddleware"),i(loadMiddleware,"loadMiddleware");function createWorkspaceMiddleware(r){const t=r.policy??"own",n=r.load??loadMiddleware,o=new Map;return{set(e,l,s){if(e===r.ownerKey)return;if(!s){o.set(e,void 0);return}const d=path.resolve(l,s);try{o.set(e,n(d)),t==="own"&&console.log(`[gateway] '${e}' routes run its own middleware: ${d}`)}catch(u){o.set(e,void 0),console.warn(`[gateway] could not load '${e}''s middleware ${d} (${u.message}) \u2014 its routes run with NO middleware until it registers again`)}},drop(e){o.delete(e)},for(e){if(e===void 0||e===r.ownerKey||t==="owner")return r.owner;if(t!=="none")return o.get(e)}}}a(createWorkspaceMiddleware,"createWorkspaceMiddleware"),i(createWorkspaceMiddleware,"createWorkspaceMiddleware");
|
|
@@ -17,6 +17,13 @@ export interface WorkspaceEntry {
|
|
|
17
17
|
procStamp?: string;
|
|
18
18
|
/** ISO timestamp of the last register. */
|
|
19
19
|
updatedAt: string;
|
|
20
|
+
/** Set when the workspace is a worktree INSTANCE of another (runtime/instance.ts): the chooser lists it under its
|
|
21
|
+
* base, and the base's dashboard lists it among its worktrees. */
|
|
22
|
+
instance?: {
|
|
23
|
+
name: string;
|
|
24
|
+
baseKey: string;
|
|
25
|
+
apex: string;
|
|
26
|
+
};
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* The start-time identity stamp for a pid (`''` when `ps` is unavailable — Windows, restricted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var ie=Object.defineProperty;var c=(e,r)=>ie(e,"name",{value:r,configurable:!0});var se=Object.defineProperty,o=c((e,r)=>se(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),attach_1=require("../../shared-build/attach");function definedEnv(e){const r={};for(const[_,i]of Object.entries(e))typeof i=="string"&&(r[_]=i);return r}c(definedEnv,"definedEnv"),o(definedEnv,"definedEnv");const frozen=o(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,r){process.env.NODE_ENV="development";const _=r.projectsConfigurations.projects[r.projectName],i=r.root,g=path.join(i,_.root),U=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",_.root),B=e.configEnv||"development",R=path.join(i,"config",`config.${B}.yaml`),$=3050;let d=$,l=r.projectName,S={};fs.existsSync(R)?(S=yaml.load(fs.readFileSync(R,"utf8")),d=Number(S.port)||d,l=S.serviceName||l):console.warn(`[serve] ${r.projectName}: no config file at ${R} \u2014 public devserver port defaults to ${$}. If another service also lacks its config, both bind ${$} \u2192 EADDRINUSE. Add config/config.${B}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(l=e.serviceName);const O=e.servePrefix??"",v=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),z=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let T,D;e.gateway?.middleware&&(T=path.resolve(g,e.gateway.middleware),D=JSON.stringify(S));const K=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",Z=path.resolve(g,K),W=path.resolve(g,Q),ee=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(g,t.entryPath)})),q={appName:r.projectName,appRoot:g,outputDir:U,main:Z,tsConfig:W,assets:e.assets||[],additionalEntryPoints:ee,port:d,serviceName:l,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let u=null,k=!1,I=!1,m=!1;const V=path.join(__dirname,"../../main.devserver.js"),H=path.join(__dirname,"../../main.devserver.ts"),J=!fs.existsSync(V)&&fs.existsSync(H),x=J?H:V,oe=path.join(U,"main.js");function A(){u&&!u.killed&&(u.kill("SIGTERM"),u=null)}c(A,"H"),o(A,"cleanup");function N(){m=!0,A()}c(N,"k"),o(N,"stop");const te=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"].map(t=>{const n=o(()=>N(),"handler");return process.on(t,n),{sig:t,handler:n}});process.on("exit",N);function E(){if(m)return;if(!fs.existsSync(x)){console.error(`[serve] devserver script not found at ${x}`);return}const t=["--enable-source-maps"];J&&t.unshift("--require","@swc-node/register");const n=(0,node_child_process_1.fork)(x,{cwd:i,env:{...process.env,PORT:String(d),SERVICE_NAME:l,SERVE_PREFIX:O,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||r.projectName||l,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:oe,...T&&{GATEWAY_MIDDLEWARE:T},...D&&{GATEWAY_CONFIG:D},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...v&&{LENSMCP_DEBUG_PORT:String(v)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:JSON.stringify(e.workers.map(a=>a.name))},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:I?"1":"0"},stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),l,"dev"))});u=n,n.on("exit",(a,f)=>{u===n&&(u=null),!(f||a===0||m)&&(console.error(`[serve] devserver exited unexpectedly (code=${a}, signal=${f}) \u2014 respawning in 1.5s`),setTimeout(()=>{!m&&!u&&k&&E()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${d} (service: ${l}${O?`, prefix: /${O}`:""})`),v?(console.warn(`[serve] DEBUGGER ENABLED for ${l}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),z?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${v}+slot (workers ${v}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${v}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${l}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${l}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}c(E,"S"),o(E,"startDevServer");function re(){if(!u||u.killed){console.log("[serve] Devserver not running, restarting..."),E();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),A(),E()})}c(re,"ne"),o(re,"_notifyDevServerReload");let y=null;const M=[];function h(t){if(y){const n=y;y=null,n(t)}else M.push(t)}c(h,"h"),o(h,"pushResult");function Y(){return M.length>0?Promise.resolve(M.shift()):new Promise(t=>{y=t})}c(Y,"ee"),o(Y,"waitForNext");let w=o(()=>{},"closeBuild");function F(){return!k||!u?(k=!0,E(),!0):!1}c(F,"J"),o(F,"builtOk");function b(){if(m)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),n=require("webpack"),a=t(q),{watch:f,...P}=a,s=n(P);I=(0,dev_devtool_1.bundleCarriesSourceMaps)(s.options.devtool);const j=o((L,C)=>{if(L){console.error("[serve] Webpack error:",L),h({success:!1});return}const ne=C.hasErrors();if(console.info(C.toString({colors:!0,chunks:!1,assets:!0})),ne){h({success:!1});return}F(),h({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${r.projectName}: frozen \u2014 compiling once, no file watcher`),s.run((L,C)=>{j(L,C),s.close(()=>{})});return}const G=s.watch(a.watchOptions||{},j);w=o(()=>G.close(()=>{}),"closeBuild")}c(b,"A"),o(b,"compileOnItsOwn");function X(t){const n=t.accepted.projects.filter(s=>s!==r.projectName);console.log(`[serve] ${r.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${n.length?` with ${n.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(l)),f=(0,attach_1.createAttachedTypecheck)({appName:r.projectName,tsConfig:W,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let P;w=o(()=>{t.close(),f.dispose()},"closeBuild"),t.listen(s=>{if(!m)switch(s.type){case"watch-run":a.watchRun(s.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${s.message}`),a.degrade(`compiler failed: ${s.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${r.projectName}: compiling on its own from now on \u2014 ${s.reason}`),w(),b();return;case"built":{if(console.info(s.text),I=s.sourceMaps,f.built(s),!s.ok){a.tellWatching(),h({success:!1});return}const j=s.hash!==P;P=s.hash;const G=F();a.built(j&&!G),h({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(c(X,"re"),o(X,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const t=await(0,attach_1.joinSharedBuild)({project:r.projectName,target:r.targetName??"serve-hmr",...r.configurationName?{configuration:r.configurationName}:{},options:q,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?X(t.session):(console.log(`[serve] ${r.projectName}: compiling on its own \u2014 ${t.reason}`),b())}else b();try{for(;;)yield await Y()}finally{N(),w(),te.forEach(({sig:t,handler:n})=>process.removeListener(t,n))}}c(serveExecutor,"serveExecutor"),o(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var v=Object.defineProperty;var
|
|
2
|
-
`).some(S=>{const f=S.trim();if(f.startsWith("#"))return!1;const y=f.split(/\s+/);return y[0]===
|
|
1
|
+
"use strict";var v=Object.defineProperty;var h=(o,t)=>v(o,"name",{value:t,configurable:!0});var m=Object.defineProperty,d=h((o,t)=>m(o,"name",{value:t,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=trustExecutor,exports.collectHosts=collectHosts;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),basic_ssl_1=require("../../basic-ssl"),checkout_cache_1=require("../../checkout-cache"),hostname_1=require("./hostname");async function trustExecutor(o,t){const r=(0,checkout_cache_1.checkoutCacheDir)(t.root,"davnx-webpack"),c=(0,basic_ssl_1.ensureCaSync)(r),a=process.env.LENSMCP_TRUST_HOSTS_ONLY==="1",u=collectHosts(o,t);if(u.length===0&&console.log("[trust] no concrete hostnames found (only wildcards?) \u2014 keychain step only."),process.platform!=="darwin"){console.log("[trust] non-macOS platform \u2014 do these manually:"),console.log(` \u2022 trust ${c} in your OS/browser store (Linux: update-ca-certificates / certutil)`);for(const e of u)console.log(` \u2022 /etc/hosts: "127.0.0.1 ${e}" and "::1 ${e}"`);return{success:!0}}const s=caSha256Fingerprint(c),p=a||s!==void 0&&(0,node_child_process_1.spawnSync)("security",["find-certificate","-a","-c","lensmcp local dev CA","-Z","/Library/Keychains/System.keychain"],{encoding:"utf8"}).stdout?.includes(s)===!0;if(a)console.log("[trust] hosts-only: a shared daemon serves TLS with its own CA \u2014 skipping the keychain step.");else if(p)console.log("[trust] CA already in the System keychain \u2014 skipping.");else{if(console.log(`[trust] adding CA to the System keychain (sudo will prompt): ${c}`),(0,node_child_process_1.spawnSync)("sudo",["security","add-trusted-cert","-d","-r","trustRoot","-k","/Library/Keychains/System.keychain",c],{stdio:"inherit"}).status!==0)return console.error("[trust] add-trusted-cert failed \u2014 rerun in a terminal so sudo can prompt."),{success:!1};console.log("[trust] CA trusted. Restart your browser once (it caches certificate verdicts).")}const i=fs.readFileSync("/etc/hosts","utf8"),n=d((e,g)=>i.split(`
|
|
2
|
+
`).some(S=>{const f=S.trim();if(f.startsWith("#"))return!1;const y=f.split(/\s+/);return y[0]===e&&y.slice(1).includes(g)}),"hasEntry"),l=[];for(const e of u)n("127.0.0.1",e)||l.push(`127.0.0.1 ${e}`),n("::1",e)||l.push(`::1 ${e}`);if(l.length===0)console.log("[trust] /etc/hosts already complete (both families for every hostname).");else{if(console.log(`[trust] appending to /etc/hosts (sudo):
|
|
3
3
|
${l.join(`
|
|
4
4
|
`)}`),(0,node_child_process_1.spawnSync)("sudo",["tee","-a","/etc/hosts"],{input:l.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`,stdio:["pipe","ignore","inherit"]}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["dscacheutil","-flushcache"],{stdio:"inherit"}),(0,node_child_process_1.spawnSync)("sudo",["killall","-HUP","mDNSResponder"],{stdio:"inherit"})}if(!a){const
|
|
6
|
+
`,stdio:["pipe","ignore","inherit"]}).status!==0)return console.error("[trust] /etc/hosts update failed."),{success:!1};(0,node_child_process_1.spawnSync)("sudo",["dscacheutil","-flushcache"],{stdio:"inherit"}),(0,node_child_process_1.spawnSync)("sudo",["killall","-HUP","mDNSResponder"],{stdio:"inherit"})}if(!a){const e=(0,node_child_process_1.spawnSync)("security",["verify-cert","-c",c,"-p","basic"],{stdio:"ignore"});console.log(`[trust] CA trust check: ${e.status===0?"OK":"NOT TRUSTED YET (browser restart may be needed)"}`)}for(const e of u)console.log(`[trust] ${e} \u2192 https ready (hosts + cert SAN ride the gateway config)`);return{success:!0}}h(trustExecutor,"trustExecutor"),d(trustExecutor,"trustExecutor");function caSha256Fingerprint(o){const t=(0,node_child_process_1.spawnSync)("openssl",["x509","-in",o,"-noout","-fingerprint","-sha256"],{encoding:"utf8"});if(t.status!==0||!t.stdout)return;const r=t.stdout.split("=")[1]?.replaceAll(":","").trim().toUpperCase();return r&&/^[0-9A-F]{64}$/.test(r)?r:void 0}h(caSha256Fingerprint,"caSha256Fingerprint"),d(caSha256Fingerprint,"caSha256Fingerprint");function collectHosts(o,t){const r=[],c=t.projectName?t.projectsConfigurations?.projects[t.projectName]:void 0,a=o.serveTarget??"serve-hmr",u=c?.targets?.[a]?.options?.gateway?.routes??[];for(const s of u)s.host&&(0,hostname_1.isValidHostname)(s.host)?r.push(s.host):s.host&&console.warn(`[trust] ignoring invalid hostname in ${a} routes: ${JSON.stringify(s.host)}`);for(const s of Object.values(t.projectsConfigurations?.projects??{}))try{const p=JSON.parse(fs.readFileSync(path.join(t.root,s.root,"project.json"),"utf8")),i=p.cluster??p.davnx,n=i?.host;n&&!(0,hostname_1.isValidHostname)(n)&&console.warn(`[trust] ignoring invalid hostname in ${s.root}/project.json: ${JSON.stringify(n)}`),n&&(0,hostname_1.isValidHostname)(n)&&(r.push(n),i?.service&&i.internalHost!==!1&&r.push(typeof i.internalHost=="string"?i.internalHost:`internal.${n}`))}catch{}return[...new Set([...r,"lensmcp.local",...o.hosts??[]])]}h(collectHosts,"collectHosts"),d(collectHosts,"collectHosts");
|
package/heap-snapshot.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var _=Object.defineProperty;var p=(e,t)=>_(e,"name",{value:t,configurable:!0});var
|
|
1
|
+
"use strict";var _=Object.defineProperty;var p=(e,t)=>_(e,"name",{value:t,configurable:!0});var m=Object.defineProperty,i=p((e,t)=>m(e,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.heapSnapshotPolicy=heapSnapshotPolicy,exports.availableSystemMemory=availableSystemMemory,exports.heapSnapshotRefusal=heapSnapshotRefusal,exports.heapSnapshotDir=heapSnapshotDir,exports.heapSnapshotFileName=heapSnapshotFileName,exports.ensureHeapDir=ensureHeapDir,exports.pruneHeapSnapshots=pruneHeapSnapshots,exports.writeHeapSnapshot=writeHeapSnapshot;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),inspector=tslib_1.__importStar(require("node:inspector")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),MB=1024*1024,numberFrom=i((e,t,s)=>{if(e===void 0||e.trim()==="")return t;const a=Number(e);return Number.isFinite(a)&&a>=s?a:t},"numberFrom");function heapSnapshotPolicy(e=process.env){return{maxHeapBytes:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MAX_MB,1024,1)*MB,minFreeBytes:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MIN_FREE_MB,1024,0)*MB,freeFactor:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_FREE_FACTOR,3,0),keep:Math.max(1,Math.floor(numberFrom(e.LENSMCP_HEAP_SNAPSHOT_KEEP,3,1))),maxAgeMs:numberFrom(e.LENSMCP_HEAP_SNAPSHOT_MAX_AGE_H,24,0)*36e5}}p(heapSnapshotPolicy,"heapSnapshotPolicy"),i(heapSnapshotPolicy,"heapSnapshotPolicy");function availableSystemMemory(e=process.platform){try{if(e==="darwin"){const t=(0,node_child_process_1.spawnSync)("vm_stat",[],{encoding:"utf8",timeout:2e3}).stdout??"",s=Number(/page size of (\d+) bytes/.exec(t)?.[1]??4096),a=i(r=>Number(new RegExp(`Pages ${r}:\\s+(\\d+)`).exec(t)?.[1]??0),"pages"),o=a("free")+a("inactive")+a("speculative")+a("purgeable");if(o>0)return{bytes:o*s,source:"vm_stat free+inactive+speculative+purgeable"}}else if(e==="linux"){const t=/MemAvailable:\s+(\d+) kB/.exec(fs.readFileSync("/proc/meminfo","utf8"));if(t)return{bytes:Number(t[1])*1024,source:"/proc/meminfo MemAvailable"}}return{bytes:os.freemem(),source:"os.freemem()"}}catch{return}}p(availableSystemMemory,"availableSystemMemory"),i(availableSystemMemory,"availableSystemMemory");function heapSnapshotRefusal(e,t){if(t.live>e.maxHeapBytes){const s=Math.ceil(t.live/MB/256)*256;return`the pod's live heap is ${Math.round(t.live/MB)} MB, over the ${Math.round(e.maxHeapBytes/MB)} MB bound \u2014 a snapshot this size needs several times that in memory while it is written. Raise the bound for this one snapshot with \`--max-mb ${s}\` (the free-memory check still applies); the default is LENSMCP_HEAP_SNAPSHOT_MAX_MB, read when the pod starts`}if(t.available){const s=Math.max(e.minFreeBytes,e.freeFactor*t.live);if(t.available.bytes<s)return`only ${Math.round(t.available.bytes/MB)} MB of memory is available (${t.available.source}) and a snapshot of a ${Math.round(t.live/MB)} MB heap needs about ${Math.round(s/MB)} MB \u2014 free some memory first, or lower the floor with LENSMCP_HEAP_SNAPSHOT_MIN_FREE_MB / LENSMCP_HEAP_SNAPSHOT_FREE_FACTOR`}}p(heapSnapshotRefusal,"heapSnapshotRefusal"),i(heapSnapshotRefusal,"heapSnapshotRefusal");function heapSnapshotDir(e=process.env,t=process.cwd()){return e.LENSMCP_HEAP_DIR?path.resolve(e.LENSMCP_HEAP_DIR):e.LENSMCP_EVENT_FILE?path.join(path.dirname(path.resolve(e.LENSMCP_EVENT_FILE)),"heap"):path.join(t,".lensmcp","heap")}p(heapSnapshotDir,"heapSnapshotDir"),i(heapSnapshotDir,"heapSnapshotDir");function heapSnapshotFileName(e,t,s,a,o){const r=(e||"service").replace(/[^A-Za-z0-9_.-]/g,"_"),n=new Date(o).toISOString().replace(/[:.]/g,"-");return`${r}-pod${t}-pid${s}-g${a}-${n}.heapsnapshot`}p(heapSnapshotFileName,"heapSnapshotFileName"),i(heapSnapshotFileName,"heapSnapshotFileName");function ensureHeapDir(e){fs.mkdirSync(e,{recursive:!0,mode:448});const t=fs.lstatSync(e);if(!t.isDirectory())throw new Error(`${e} is not a directory`);(t.mode&63)!==0&&fs.chmodSync(e,448)}p(ensureHeapDir,"ensureHeapDir"),i(ensureHeapDir,"ensureHeapDir");function pruneHeapSnapshots(e,t,s=Date.now()){const a=[];let o;try{o=fs.readdirSync(e).filter(r=>r.endsWith(".heapsnapshot")).map(r=>{const n=path.join(e,r),h=fs.lstatSync(n);return h.isFile()?{file:n,mtime:h.mtimeMs}:void 0}).filter(r=>r!==void 0).sort((r,n)=>n.mtime-r.mtime)}catch{return a}return o.forEach((r,n)=>{if(n>=t.keep-1||s-r.mtime>t.maxAgeMs)try{fs.unlinkSync(r.file),a.push(r.file)}catch{}}),a}p(pruneHeapSnapshots,"pruneHeapSnapshots"),i(pruneHeapSnapshots,"pruneHeapSnapshots");function writeHeapSnapshot(e){const t=Date.now(),s=fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_EXCL|(fs.constants.O_NOFOLLOW??0),a=fs.openSync(e,s,384),o=new inspector.Session;let r=0,n;return new Promise((h,S)=>{const u=i(c=>{o.removeAllListeners("HeapProfiler.addHeapSnapshotChunk");try{o.disconnect()}catch{}try{fs.closeSync(a)}catch{}const l=c??n;if(l){try{fs.unlinkSync(e)}catch{}S(l);return}h({bytes:r,ms:Date.now()-t})},"finish");try{o.connect(),o.on("HeapProfiler.addHeapSnapshotChunk",c=>{if(!n)try{r+=fs.writeSync(a,c.params.chunk)}catch(l){n=l}}),o.post("HeapProfiler.takeHeapSnapshot",{reportProgress:!1},c=>u(c))}catch(c){u(c)}})}p(writeHeapSnapshot,"writeHeapSnapshot"),i(writeHeapSnapshot,"writeHeapSnapshot");
|
package/inspector-secret.d.ts
CHANGED
|
@@ -37,3 +37,9 @@ export declare function forStream(stream: StdioStream, underGateway: boolean, te
|
|
|
37
37
|
* `/json/list` for the target. That opt-out already warns that its ports are reachable by every account.
|
|
38
38
|
*/
|
|
39
39
|
export declare function inspectorPublishExecArgv(fixedPorts: boolean): string[];
|
|
40
|
+
/**
|
|
41
|
+
* The TCP port out of a `ws://host:port/uuid` inspector URL, or `null`. Here rather than in main.devserver.ts, which
|
|
42
|
+
* starts a devserver the moment it is loaded: a spec that required it for this one function booted a parent and
|
|
43
|
+
* its pods inside the jest process, and they kept the run from ever exiting.
|
|
44
|
+
*/
|
|
45
|
+
export declare function inspectorPortOf(url: string | undefined): number | null;
|