@lensmcp/cluster 1.19.0 → 1.20.0
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/basic-ssl.js +1 -1
- package/build-scope-patterns.js +1 -1
- package/create-webpack-dev.js +1 -1
- package/create-webpack-prod.js +1 -1
- package/executors/build/build.impl.js +1 -1
- package/executors/gateway/gateway-errors.d.ts +2 -2
- package/executors/gateway/gateway-errors.js +1 -1
- package/executors/gateway/gateway.impl.js +1 -1
- package/executors/gateway/gateway.lib.d.ts +1 -1
- package/executors/gateway/gateway.lib.js +1 -1
- package/executors/gateway/health-check.js +1 -1
- package/executors/gateway/jwks-verify.js +1 -1
- package/executors/gateway/main.prod-gateway.js +2 -2
- package/executors/gateway/main.rollout.js +4 -4
- package/executors/gateway/manifest.js +1 -1
- package/executors/gateway/metrics.js +1 -1
- package/executors/gateway/otel-tracing.js +1 -1
- package/executors/gateway/prod-gateway.lib.js +1 -1
- package/executors/gateway/prod-runtime/access-log.js +1 -1
- package/executors/gateway/prod-runtime/app.js +1 -1
- package/executors/gateway/prod-runtime/auth.js +1 -1
- package/executors/gateway/prod-runtime/cors.js +1 -1
- package/executors/gateway/prod-runtime/edge.js +1 -1
- package/executors/gateway/prod-runtime/handler.js +1 -1
- package/executors/gateway/prod-runtime/hooks.js +1 -1
- package/executors/gateway/prod-runtime/observability.js +1 -1
- package/executors/gateway/prod-runtime/rollout.js +1 -1
- package/executors/gateway/prod-runtime/routing.js +1 -1
- package/executors/gateway/prod-runtime/server.js +1 -1
- package/executors/gateway/prod-runtime/trust.js +1 -1
- package/executors/gateway/prod-runtime/upgrade.js +2 -2
- package/executors/gateway/prod-runtime/upstream.js +1 -1
- package/executors/gateway/providers-prod.js +1 -1
- package/executors/gateway/rate-limit.js +2 -2
- package/executors/gateway/registry-source.js +1 -1
- package/executors/gateway/rollout-ops.js +2 -2
- package/executors/gateway/runtime/auth.js +1 -1
- package/executors/gateway/runtime/chooser.js +7 -7
- package/executors/gateway/runtime/control.js +1 -1
- package/executors/gateway/runtime/dev-auth.js +1 -1
- package/executors/gateway/runtime/discovery.d.ts +5 -1
- package/executors/gateway/runtime/discovery.js +1 -1
- package/executors/gateway/runtime/edge.d.ts +1 -1
- package/executors/gateway/runtime/edge.js +1 -1
- package/executors/gateway/runtime/handler.js +1 -1
- package/executors/gateway/runtime/hmr-reload.d.ts +2 -2
- package/executors/gateway/runtime/hmr-reload.js +1 -1
- package/executors/gateway/runtime/hooks.js +1 -1
- package/executors/gateway/runtime/lens-children.d.ts +3 -7
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +11 -3
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/observability.js +2 -2
- package/executors/gateway/runtime/pod-env.js +2 -2
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/route-registry.js +1 -1
- package/executors/gateway/runtime/scope.js +1 -1
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/service-keys.js +1 -1
- package/executors/gateway/runtime/types.d.ts +57 -1
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/upgrade.js +1 -1
- package/executors/gateway/runtime/workspace-registry.js +1 -1
- package/executors/gateway/ssrf-guard.js +1 -1
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/trust.impl.js +3 -3
- package/gateway.js +1 -1
- package/index.js +1 -1
- package/main.devserver.js +5 -5
- package/package.json +4 -4
- package/tsgo-check-plugin.js +4 -4
- package/typecheck-bus.js +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var R=Object.defineProperty;var l=(i,c)=>R(i,"name",{value:c,configurable:!0});var H=Object.defineProperty,u=l((i,c)=>H(i,"name",{value:c,configurable:!0}),"u"),S=Object.defineProperty,r=u((i,c)=>S(i,"name",{value:c,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createRollout=createRollout;const node_crypto_1=require("node:crypto"),manifest_1=require("../manifest"),edge_1=require("./edge");function createRollout(i){const c=i.clientIpHeader?.toLowerCase(),f=i.clientIpTrustedProxies??[],C=r(e=>{if(!e)return!1;const t=e.startsWith("::ffff:")?e.slice(7):e;return f.length===0?t==="127.0.0.1"||e==="::1":f.some(o=>(0,manifest_1.matchCidr)(t,o))},"peerTrusted"),m=r((e,t)=>{if(c&&C(e.socket?.remoteAddress??void 0)){const o=(0,edge_1.hdr)(e.headers[c]);if(o)return o}return t},"clientIpOf"),b=(i.attributeHeaders??[]).map(e=>e.toLowerCase()),p=i.uidHeader?.toLowerCase(),d=!!i.cookie,k=i.cookie?.name??"lensmcp_did",a=i.cookie?.secret,h=i.cookie?.domain,I=i.cookie?.maxAge??31536e3;d&&!a&&console.warn("[gateway] device cookie has no secret \u2014 values are UNSIGNED (set cookie.secret to prevent bucket-grinding).");const v=r(e=>(0,node_crypto_1.createHmac)("sha256",a).update(e).digest("base64url"),"signId"),x=r(e=>a?`${e}.${v(e)}`:e,"signedCookie"),_=r(e=>{if(!a)return e||void 0;const t=e.lastIndexOf(".");if(t<0)return;const o=e.slice(0,t),n=Buffer.from(e.slice(t+1)),s=Buffer.from(v(o));return n.length===s.length&&(0,node_crypto_1.timingSafeEqual)(n,s)?o:void 0},"verifyCookie"),O=r((e,t)=>{if(e)for(const o of e.split(";")){const n=o.indexOf("=");if(n>0&&o.slice(0,n).trim()===t)return decodeURIComponent(o.slice(n+1).trim())}},"parseCookie");return{clientIpOf:m,buildAttrs:r((e,t,o,n)=>{const s={...o??{}};s.ip=m(e,t),s.method=(e.method??"GET").toUpperCase(),s.path=(e.url??"/").split("?")[0],n&&(s.device=n);for(const g of b){const y=(0,edge_1.hdr)(e.headers[g]);y!==void 0&&(s["header."+g]=y)}return s},"buildAttrs"),resolveStickyKey:r(e=>{if(d){const t=O(e.headers.cookie,k),o=t?_(t):void 0;if(o)return{key:o,mint:!1}}if(p){const t=(0,edge_1.hdr)(e.headers[p]);if(t)return{key:t,mint:!1}}return d?{key:"d_"+(0,node_crypto_1.randomBytes)(12).toString("base64url"),mint:!0}:{mint:!1}},"resolveStickyKey"),mintCookieHeader:r(e=>{let t=`${k}=${encodeURIComponent(x(e))}; Path=/; Max-Age=${I}; HttpOnly; SameSite=Lax; Secure`;return h&&(t+=`; Domain=${h}`),t},"mintCookieHeader")}}l(createRollout,"createRollout"),u(createRollout,"createRollout"),r(createRollout,"createRollout");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var l=(e,s)=>m(e,"name",{value:s,configurable:!0});var d=Object.defineProperty,u=l((e,s)=>d(e,"name",{value:s,configurable:!0}),"u"),h=Object.defineProperty,r=u((e,s)=>h(e,"name",{value:s,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createRouteTable=createRouteTable;const manifest_1=require("../manifest");function createRouteTable(e,s){const o=[],c=new Map;let i=e.manifests.list();const f=r(a=>{i=a;const t=(0,manifest_1.buildRouteTable)(a);o.length=0,o.push(...t),c.clear();for(const n of a)c.set(n.service,n)},"apply"),p=r(a=>{if(f(a),e.healthChecker){const t=new Set;for(const n of o)n.upstream&&t.add(n.upstream);for(const n of e.pods?.endpoints?.()??[])t.add(n);e.healthChecker.track([...t])}s.emit("info",`gateway routes: ${o.length} on ${i.length} services`,"gateway-up",{kind:"gateway-up",routes:o.map(t=>({host:t.host??"(default)",service:t.service,internal:!!t.internal}))})},"rebuild");f(i);const b=e.manifests.watch?.(a=>p(a));return{routes:o,manifestByService:c,rebuild:p,manifests:r(()=>i,"manifests"),stop:r(()=>b?.(),"stop")}}l(createRouteTable,"createRouteTable"),u(createRouteTable,"createRouteTable"),r(createRouteTable,"createRouteTable");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var H=Object.defineProperty;var d=(e,a)=>H(e,"name",{value:a,configurable:!0});var B=Object.defineProperty,l=d((e,a)=>B(e,"name",{value:a,configurable:!0}),"l"),x=Object.defineProperty,n=l((e,a)=>x(e,"name",{value:a,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.startProdGateway=startProdGateway;const observability_1=require("./observability"),routing_1=require("./routing"),rollout_1=require("./rollout"),upstream_1=require("./upstream"),auth_1=require("./auth"),hooks_1=require("./hooks"),cors_1=require("./cors"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),app_1=require("./app");async function startProdGateway(e){const a=e.ports?.length?e.ports:[e.tls?8443:8080],P=e.authenticate??(t=>{if(t==="jwt")throw new Error("jwt route but no authenticator configured")}),j=(e.versionHeader??"x-lensmcp-version").toLowerCase(),C=!!e.accessLog,O=new Set((e.corsCredentialOrigins??[]).map(t=>t.toLowerCase())),R=new Map(Object.entries(e.serviceKeys??{}).map(([t,i])=>[i,t])),h={},r=(0,observability_1.createObservability)(e,()=>h.current.routes),o=(0,routing_1.createRouteTable)(e,r);h.current=o;const s={opts:e,table:o,keyToProject:R,versionHeader:j,accessLog:C,corsCredentialOrigins:O},w=(0,rollout_1.createRollout)(e),y=(0,upstream_1.createUpstream)(e,r),b=(0,auth_1.createEdgeAuth)(s,r,w,P),u=(0,hooks_1.createHooks)(e.hooks??{}),c=new WeakMap,g=new WeakMap,m=new WeakMap,q=(0,cors_1.createCors)(s,c),L=(0,handler_1.createHandler)({rt:s,obs:r,cors:q,auth:b,upstream:y,rollout:w,hooks:u,reqState:c,spanByReq:g,ctxByReq:m}),M=(0,upgrade_1.createUpgrade)({rt:s,auth:b,upstream:y,hooks:u}),{buildApp:S}=(0,app_1.createApp)({rt:s,obs:r,cors:q,hooks:u,handler:L,upgrade:M,reqState:c,spanByReq:g,ctxByReq:m}),G=r.startFlusher();o.rebuild(o.manifests());const v=[],k=[];for(const t of a){const i=await S();await i.listen({port:t,host:"0.0.0.0"});const p=i.server.address(),_=typeof p=="object"&&p?p.port:t;k.push(_),console.log(`[gateway] listening on ${e.tls?"https":"http"}://0.0.0.0:${_}`),v.push(i)}e.healthChecker?.start();let f=!1;return{ports:k,routes:n(()=>o.routes,"routes"),status:r.buildStatusSnapshot,stop:n(async()=>{f||(f=!0,clearInterval(G),o.stop(),e.healthChecker?.stop(),e.rateLimiter?.stop(),e.metrics?.stop(),r.emit("info","gateway down","gateway-down",{kind:"gateway-down"}),await Promise.all(v.map(t=>t.close())))},"stop")}}d(startProdGateway,"startProdGateway"),l(startProdGateway,"startProdGateway"),n(startProdGateway,"startProdGateway");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var c=Object.defineProperty;var a=(t,e)=>c(t,"name",{value:e,configurable:!0});var d=Object.defineProperty,p=a((t,e)=>d(t,"name",{value:e,configurable:!0}),"p"),x=Object.defineProperty,r=p((t,e)=>x(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.stampIdentity=exports.stripTrust=exports.HOP_BY_HOP=exports.TRUST_HEADERS=void 0,exports.TRUST_HEADERS=["x-internal-token","x-api-key-id","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"],exports.HOP_BY_HOP=["connection","proxy-connection","keep-alive"];const stripTrust=r(t=>{for(const e of exports.TRUST_HEADERS)delete t.headers[e];for(const e of exports.HOP_BY_HOP)delete t.headers[e]},"stripTrust");exports.stripTrust=stripTrust;const stampIdentity=r((t,e)=>{const i=r((u,s)=>{(typeof s=="string"&&s!==""||typeof s=="number")&&(t.headers[u]=String(s))},"set");i("x-user-id",e.sub),i("x-tenant-id",e.tid);const o=e.roles;Array.isArray(o)&&o.length&&(t.headers["x-user-roles"]=o.map(String).join(","));const n=e.perms;Array.isArray(n)&&n.length&&(t.headers["x-user-permissions"]=n.map(String).join(","))},"stampIdentity");exports.stampIdentity=stampIdentity;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var j=Object.defineProperty;var v=(d,s)=>j(d,"name",{value:s,configurable:!0});var U=Object.defineProperty,g=v((d,s)=>U(d,"name",{value:s,configurable:!0}),"g"),S=Object.defineProperty,m=g((d,s)=>S(d,"name",{value:s,configurable:!0}),"m");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),manifest_1=require("../manifest"),edge_1=require("./edge"),trust_1=require("./trust");function createUpgrade(d){const{rt:s,auth:b,upstream:$,hooks:p}=d,_=m(async(e,r,h)=>{(0,trust_1.stripTrust)(e);const A=(e.headers.host||"").split(":")[0],w=(0,edge_1.canonicalPath)(e.url??"/");if(w===void 0){r.destroy();return}const o=(0,manifest_1.matchRoute)(s.table.routes,A,e.url??"/");if(!o){r.destroy();return}const t=p.any?p.upgradeContext(e,r,void 0):void 0;t&&(t.route=o);const i=o.internal?void 0:s.opts.identify?.(e);let f;if(o.internal){const a=b.internal(e,o,void 0);if(!a.ok){r.destroy();return}f=a.caller,t&&(t.caller=f)}else{if(!b.public(e,o,w,i,e.socket.remoteAddress??"",void 0).ok){r.destroy();return}if(t&&(t.claims=i),t&&p.has("afterAuth")&&(await p.run("afterAuth",t),t.responded()))return}if(o.prependPrefix){const a=e.url??"/",n=o.prependPrefix;a!==n&&!a.startsWith(n+"/")&&(e.url=n+a)}if(t&&p.has("onUpgrade")&&(await p.run("onUpgrade",t),t.responded()))return;const P=(0,edge_1.hdr)(e.headers[s.versionHeader]),y=await $.resolveUpstream(o,void 0,P);if(!y||!("url"in y)){r.destroy();return}const x=new URL(y.url),c=net.connect(Number(x.port)||(x.protocol==="https:"?443:80),x.hostname,()=>{const a=new Set(["x-internal-token","x-api-key-id","x-api-key","x-user-id","x-tenant-id","x-user-roles","x-user-permissions","connection","proxy-connection","keep-alive"]),n=[`${e.method} ${e.url} HTTP/1.1`,"Connection: Upgrade"];for(let u=0;u<e.rawHeaders.length;u+=2){const l=e.rawHeaders[u];a.has(l.toLowerCase())||n.push(`${l}: ${e.rawHeaders[u+1]}`)}f&&n.push(`x-api-key-id: ${f}`);const k=s.opts.serviceKeys?.[o.service];if(k&&n.push(`x-internal-token: ${k}`),i){i.sub&&n.push(`x-user-id: ${String(i.sub)}`),i.tid&&n.push(`x-tenant-id: ${String(i.tid)}`);const u=i.roles;Array.isArray(u)&&u.length&&n.push(`x-user-roles: ${u.map(String).join(",")}`);const l=i.perms;Array.isArray(l)&&l.length&&n.push(`x-user-permissions: ${l.map(String).join(",")}`)}c.write(n.join(`\r
|
|
2
2
|
`)+`\r
|
|
3
3
|
\r
|
|
4
|
-
`),h?.length&&
|
|
4
|
+
`),h?.length&&c.write(h),r.pipe(c),c.pipe(r)});c.on("error",()=>r.destroy()),r.on("error",()=>c.destroy())},"run");return(e,r,h)=>{_(e,r,h)}}v(createUpgrade,"createUpgrade"),g(createUpgrade,"createUpgrade"),m(createUpgrade,"createUpgrade");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var c=(e,r)=>m(e,"name",{value:r,configurable:!0});var l=Object.defineProperty,p=c((e,r)=>l(e,"name",{value:r,configurable:!0}),"p"),u=Object.defineProperty,s=p((e,r)=>u(e,"name",{value:r,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpstream=createUpstream;function createUpstream(e,r){return{resolveUpstream:s(async(t,o,i)=>{if(t.upstream)return{url:t.upstream};const n={...i?{version:i}:{},...e.healthChecker?{isHealthy:s(v=>e.healthChecker.isHealthy(v),"isHealthy")}:{}};let a=e.pods?.pick(t.service,n);return!a&&e.pods?.ensureUp&&(r.step(o,"ensure-up",{project:t.service}),await e.pods.ensureUp(t.service)&&(a=e.pods.pick(t.service,n))),a},"resolveUpstream")}}c(createUpstream,"createUpstream"),p(createUpstream,"createUpstream"),s(createUpstream,"createUpstream");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var S=Object.defineProperty;var y=(s,o)=>S(s,"name",{value:o,configurable:!0});var b=Object.defineProperty,p=y((s,o)=>b(s,"name",{value:o,configurable:!0}),"p"),w=Object.defineProperty,n=p((s,o)=>w(s,"name",{value:o,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.staticManifestProvider=staticManifestProvider,exports.fileManifestProvider=fileManifestProvider,exports.rolloutPodProvider=rolloutPodProvider,exports.endpointsPodProvider=endpointsPodProvider;const node_fs_1=require("node:fs"),manifest_1=require("./manifest"),toSpec=n(s=>Array.isArray(s)?{rules:[],cohorts:s}:{rules:s.rules??[],cohorts:s.cohorts??[]},"toSpec");function staticManifestProvider(s){return{list:n(()=>s,"list")}}y(staticManifestProvider,"staticManifestProvider"),p(staticManifestProvider,"staticManifestProvider"),n(staticManifestProvider,"staticManifestProvider");function parseManifests(s){let o;try{o=JSON.parse(s)}catch{return}const a=Array.isArray(o)?o:o?.manifests;if(!Array.isArray(a))return;const f=a.filter(i=>!!i&&typeof i=="object"&&typeof i.service=="string"&&Array.isArray(i.mounts));return f.length>0?f:void 0}y(parseManifests,"parseManifests"),p(parseManifests,"parseManifests"),n(parseManifests,"parseManifests");function fileManifestProvider(s,o={}){const a=o.onError??(()=>{});let f=[],i=-1;const t=new Set,e=n(()=>{if(!(0,node_fs_1.existsSync)(s))return;let l;try{l=(0,node_fs_1.statSync)(s).mtimeMs}catch{return}if(l===i)return;let h;try{h=(0,node_fs_1.readFileSync)(s,"utf8")}catch(v){a(v);return}const d=parseManifests(h);if(!d){a(new Error(`[gateway] rejecting invalid manifest file ${s} \u2014 keeping last-good`));return}i=l,f=d;for(const v of t)try{v(f)}catch(c){a(c)}},"reload");e();let r;try{r=(0,node_fs_1.watch)(s,{persistent:!1},()=>e())}catch{}const u=setInterval(e,o.pollMs??1e3);return u.unref?.(),{list:n(()=>f,"list"),watch:n(l=>(t.add(l),()=>t.delete(l)),"watch"),stop:n(()=>{try{r?.close()}catch{}clearInterval(u)},"stop")}}y(fileManifestProvider,"fileManifestProvider"),p(fileManifestProvider,"fileManifestProvider"),n(fileManifestProvider,"fileManifestProvider");function rolloutPodProvider(s){const o=new Map,a=n(t=>({cohorts:t.cohorts.map(e=>({...e,urls:e.urls.slice()})),select:(0,manifest_1.smoothWeightedSelector)(t.cohorts.map(e=>e.weight)),rr:t.cohorts.map(()=>-1),rules:t.rules??[]}),"build"),f=n(t=>{for(const[e,r]of Object.entries(t))o.set(e,a(toSpec(r)));for(const e of[...o.keys()])t[e]||o.delete(e)},"load");f(s);const i=n(t=>t.cohorts.findIndex(e=>e.urls.length>0),"firstNonEmpty");return{pick(t,e){const r=o.get(t);if(!r||r.cohorts.length===0)return;const u=e?.isHealthy,l=n((c,P)=>{const g=r.cohorts[c];if(!g||g.urls.length===0)return;const m=g.urls.length;for(let M=0;M<m;M++){r.rr[c]=(r.rr[c]+1)%m;const k=g.urls[r.rr[c]];if(!P||P(k))return k}},"pickUrl"),h=e?.version?r.cohorts.findIndex(c=>c.version===e.version&&c.urls.length>0):-1;if(h>=0){const c=l(h,u)??l(h);return c!==void 0?{url:c,version:r.cohorts[h].version}:void 0}let d=r.select();(!r.cohorts[d]||r.cohorts[d].urls.length===0)&&(d=i(r));let v=l(d,u);if(v===void 0&&u)for(let c=0;c<r.cohorts.length;c++){const P=l(c,u);if(P!==void 0){d=c,v=P;break}}return v===void 0&&(v=l(d)),v!==void 0?{url:v,version:r.cohorts[d].version}:void 0},endpoints(){const t=new Set;for(const e of o.values())for(const r of e.cohorts)for(const u of r.urls)t.add(u);return[...t]},resolveVersion(t,e){const r=o.get(t);return r&&r.rules.length?(0,manifest_1.pickTargetedVersion)(r.rules,e):void 0},bucket(t,e){const r=o.get(t);if(!r||r.cohorts.length===0)return;const u=r.cohorts[(0,manifest_1.bucketByWeight)(`${e}:${t}`,r.cohorts.map(l=>l.weight))];return u?{version:u.version}:void 0},ensureUp:n(async()=>!0,"ensureUp"),drop(t,e){const r=o.get(t);if(r&&"url"in e)for(const u of r.cohorts)u.urls=u.urls.filter(l=>l!==e.url)},update:f}}y(rolloutPodProvider,"rolloutPodProvider"),p(rolloutPodProvider,"rolloutPodProvider"),n(rolloutPodProvider,"rolloutPodProvider");function endpointsPodProvider(s){const o=n(i=>Object.fromEntries(Object.entries(i).map(([t,e])=>[t,[{version:"_flat",weight:1,urls:e}]])),"toCohorts"),a=rolloutPodProvider(o(s)),f=n(i=>i&&"url"in i?{url:i.url}:i,"unversion");return{...a,pick:n((i,t)=>f(a.pick(i,t)),"pick"),resolveVersion:n(()=>{},"resolveVersion"),bucket:n(()=>{},"bucket"),update:n(i=>a.update(o(i)),"update")}}y(endpointsPodProvider,"endpointsPodProvider"),p(endpointsPodProvider,"endpointsPodProvider"),n(endpointsPodProvider,"endpointsPodProvider");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var g=Object.defineProperty;var d=(r,e)=>g(r,"name",{value:e,configurable:!0});var A=Object.defineProperty,u=d((r,e)=>A(r,"name",{value:e,configurable:!0}),"u"),R=Object.defineProperty,a=u((r,e)=>R(r,"name",{value:e,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createRateLimiter=createRateLimiter,exports.createRedisRateLimiter=createRedisRateLimiter;function createRateLimiter(r){const e=Math.max(1,r.limit),i=Math.max(1,r.windowMs),l=r.now??Date.now,c=e/i,s=new Map,m=a((o,n)=>Math.min(e,o.tokens+(n-o.last)*c),"refilled"),f=setInterval(()=>{const o=l();for(const[n,t]of s)m(t,o)>=e&&s.delete(n)},i);return f.unref?.(),{check(o){const n=l();let t=s.get(o);return t||(t={tokens:e,last:n},s.set(o,t)),t.tokens=m(t,n),t.last=n,t.tokens>=1?(t.tokens-=1,{allowed:!0,remaining:Math.floor(t.tokens),retryAfterMs:0}):{allowed:!1,remaining:0,retryAfterMs:Math.ceil((1-t.tokens)/c)}},stop(){clearInterval(f)}}}d(createRateLimiter,"createRateLimiter"),u(createRateLimiter,"createRateLimiter"),a(createRateLimiter,"createRateLimiter");const BUCKET_LUA=`
|
|
2
2
|
local b = redis.call('HMGET', KEYS[1], 't', 's')
|
|
3
3
|
local cap = tonumber(ARGV[1])
|
|
4
4
|
local refill = tonumber(ARGV[2])
|
|
@@ -15,4 +15,4 @@ redis.call('HSET', KEYS[1], 't', tokens, 's', now)
|
|
|
15
15
|
redis.call('PEXPIRE', KEYS[1], ttl)
|
|
16
16
|
local retry = 0
|
|
17
17
|
if ok == 0 then retry = math.ceil((1 - tokens) / refill) end
|
|
18
|
-
return {ok, math.floor(tokens), retry}`,RL_TIMEOUT=Symbol("rl-timeout"),RL_FAIL=Symbol("rl-fail");function createRedisRateLimiter(r,e){const i=Math.max(1,e.limit),
|
|
18
|
+
return {ok, math.floor(tokens), retry}`,RL_TIMEOUT=Symbol("rl-timeout"),RL_FAIL=Symbol("rl-fail");function createRedisRateLimiter(r,e){const i=Math.max(1,e.limit),l=Math.max(1,e.windowMs),c=e.prefix??"lensmcp:gw:rl:",s=Math.max(1,e.timeoutMs??100),m=e.failOpen!==!1,f=i/l,o=Math.ceil(l)+1e3,n=a(()=>m?{allowed:!0,remaining:i,retryAfterMs:0}:{allowed:!1,remaining:0,retryAfterMs:l},"onFail");return{async check(t){let w;const h=Promise.resolve(r.eval(BUCKET_LUA,1,`${c}${t}`,i,f,o)).then(M=>M,()=>RL_FAIL),b=new Promise(M=>{w=setTimeout(()=>M(RL_TIMEOUT),s)}),k=await Promise.race([h,b]);if(clearTimeout(w),k===RL_TIMEOUT||k===RL_FAIL)return n();const[p,y,L]=k;return p===1?{allowed:!0,remaining:Number(y)||0,retryAfterMs:0}:{allowed:!1,remaining:0,retryAfterMs:Number(L)||l}},stop(){}}}d(createRedisRateLimiter,"createRedisRateLimiter"),u(createRedisRateLimiter,"createRedisRateLimiter"),a(createRedisRateLimiter,"createRedisRateLimiter");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var d=(e,t)=>m(e,"name",{value:t,configurable:!0});var g=Object.defineProperty,u=d((e,t)=>g(e,"name",{value:t,configurable:!0}),"u"),y=Object.defineProperty,s=u((e,t)=>y(e,"name",{value:t,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.changeChannel=exports.rolloutKey=exports.manifestKey=exports.DEFAULT_REGISTRY_PREFIX=void 0,exports.redisRegistrySource=redisRegistrySource,exports.createRegistryProviders=createRegistryProviders;const providers_prod_1=require("./providers-prod");exports.DEFAULT_REGISTRY_PREFIX="lensmcp:gw";const manifestKey=s((e,t)=>`${e}:manifest:${t}`,"manifestKey");exports.manifestKey=manifestKey;const rolloutKey=s((e,t)=>`${e}:rollout:${t}`,"rolloutKey");exports.rolloutKey=rolloutKey;const changeChannel=s(e=>`${e}:changed`,"changeChannel");exports.changeChannel=changeChannel;function parseManifest(e){try{const t=JSON.parse(e);return t&&typeof t.service=="string"&&Array.isArray(t.mounts)?t:void 0}catch{return}}d(parseManifest,"parseManifest"),u(parseManifest,"parseManifest"),s(parseManifest,"parseManifest");function redisRegistrySource(e,t={}){const o=t.prefix??exports.DEFAULT_REGISTRY_PREFIX;let a;const c=`${o}:rollout:`;return{async read(){const i=await e.keys(`${o}:manifest:*`),f=i.length?(await e.mget(...i)).filter(r=>!!r).map(parseManifest).filter(r=>!!r):[],n=await e.keys(`${c}*`),l={};if(n.length){const r=await e.mget(...n);n.forEach((p,h)=>{const v=r[h];if(v)try{l[p.slice(c.length)]=JSON.parse(v)}catch{}})}return{manifests:f,endpoints:l}},async watch(i){return a=e.duplicate(),await a.subscribe((0,exports.changeChannel)(o)),a.on("message",()=>i()),async()=>{try{await a?.quit()}catch{}}},stop(){a?.quit().catch(()=>{})}}}d(redisRegistrySource,"redisRegistrySource"),u(redisRegistrySource,"redisRegistrySource"),s(redisRegistrySource,"redisRegistrySource");function createRegistryProviders(e,t={}){const o=t.onError??(()=>{});let a=[];const c=new Set,i=(0,providers_prod_1.rolloutPodProvider)({});let f,n;const l=s(async()=>{let r;try{r=await e.read()}catch(p){o(p);return}a=r.manifests,i.update(r.endpoints);for(const p of c)try{p(a)}catch(h){o(h)}},"refresh");return{manifests:{list:s(()=>a,"list"),watch:s(r=>(c.add(r),()=>c.delete(r)),"watch")},pods:i,async start(){if(await l(),e.watch)try{f=await e.watch(()=>{l()})}catch(r){o(r)}n=setInterval(()=>{l()},t.pollMs??5e3),n.unref?.()},stop(){f?.(),n&&clearInterval(n),e.stop?.()}}}d(createRegistryProviders,"createRegistryProviders"),u(createRegistryProviders,"createRegistryProviders"),s(createRegistryProviders,"createRegistryProviders");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}u(summarizeRollout,"summarizeRollout"),s(summarizeRollout,"summarizeRollout");function ruleSummary(
|
|
1
|
+
"use strict";var m=Object.defineProperty;var c=(n,e)=>m(n,"name",{value:e,configurable:!0});var d=Object.defineProperty,u=c((n,e)=>d(n,"name",{value:e,configurable:!0}),"u"),h=Object.defineProperty,s=u((n,e)=>h(n,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.applyRolloutOp=applyRolloutOp,exports.summarizeRollout=summarizeRollout,exports.parseRolloutCommand=parseRolloutCommand;const RULE_OPS=new Set(["eq","ne","in","nin","prefix","suffix","contains","regex","exists","gt","lt","cidr"]),clone=s(n=>JSON.parse(JSON.stringify(n)),"clone");function asSpec(n,e){if(e===void 0)return{rules:[],cohorts:[]};if(Array.isArray(e)){if(e.length&&typeof e[0]=="string")throw new Error(`service "${n}" is a flat endpoint list; convert it to cohorts first (e.g. add-cohort)`);return{rules:[],cohorts:e}}return{rules:e.rules??[],cohorts:e.cohorts??[]}}c(asSpec,"asSpec"),u(asSpec,"asSpec"),s(asSpec,"asSpec");const findCohort=s((n,e)=>n.cohorts.find(o=>o.version===e),"findCohort");function reqWeight(n){if(!Number.isFinite(n)||n<0)throw new Error(`weight must be a number \u2265 0 (got ${n})`);return n}c(reqWeight,"reqWeight"),u(reqWeight,"reqWeight"),s(reqWeight,"reqWeight");function applyRolloutOp(n,e){const o=clone(n),r=asSpec(e.service,o[e.service]);switch(e.kind){case"add-cohort":{if(findCohort(r,e.version))throw new Error(`cohort "${e.version}" already exists on "${e.service}" \u2014 use set-weight`);if(!e.urls.length)throw new Error("add-cohort needs at least one upstream url");r.cohorts.push({version:e.version,weight:reqWeight(e.weight),urls:e.urls.slice()});break}case"set-weight":{const t=findCohort(r,e.version);if(!t)throw new Error(`no cohort "${e.version}" on "${e.service}"`);t.weight=reqWeight(e.weight);break}case"remove-cohort":{if(!findCohort(r,e.version))throw new Error(`no cohort "${e.version}" on "${e.service}"`);r.cohorts=r.cohorts.filter(t=>t.version!==e.version);break}case"promote":{if(!findCohort(r,e.version))throw new Error(`no cohort "${e.version}" on "${e.service}"`);for(const t of r.cohorts)t.weight=t.version===e.version?100:0;break}case"abort":{const t=findCohort(r,e.version);if(!t)throw new Error(`no cohort "${e.version}" on "${e.service}"`);t.weight=0;break}case"add-rule":{validateRule(e.rule),r.rules=[...(r.rules??[]).filter(t=>!e.rule.name||t.name!==e.rule.name),e.rule];break}case"remove-rule":{const t=(r.rules??[]).length;if(r.rules=(r.rules??[]).filter(i=>i.name!==e.name),r.rules.length===t)throw new Error(`no rule named "${e.name}" on "${e.service}"`);break}}return o[e.service]=r.rules&&r.rules.length?{rules:r.rules,cohorts:r.cohorts}:r.cohorts,o}c(applyRolloutOp,"applyRolloutOp"),u(applyRolloutOp,"applyRolloutOp"),s(applyRolloutOp,"applyRolloutOp");function validateRule(n){if(!n||typeof n.version!="string"||!n.version)throw new Error("rule needs a non-empty version");if(!n.when||typeof n.when!="object")throw new Error("rule needs a `when` condition");const e=s(o=>{const r=o;if("all"in r||"any"in r||"not"in r){for(const t of["all","any"])Array.isArray(r[t])&&r[t].forEach(e);r.not&&e(r.not);return}if(typeof r.attr!="string"||!RULE_OPS.has(r.op))throw new Error(`invalid rule condition (attr/op): ${JSON.stringify(r)}`)},"walk");e(n.when)}c(validateRule,"validateRule"),u(validateRule,"validateRule"),s(validateRule,"validateRule");function summarizeRollout(n,e){const o=e?[e]:Object.keys(n);if(!o.length)return"(no services in rollout config)";const r=[];for(const t of o){const i=n[t];if(i===void 0){r.push(`${t}: (not present)`);continue}if(Array.isArray(i)&&i.length&&typeof i[0]=="string"){r.push(`${t}: flat LB \u2192 ${i.join(", ")}`);continue}const l=asSpec(t,i),v=l.cohorts.reduce((a,f)=>a+Math.max(0,f.weight),0)||1;r.push(`${t}:`);for(const a of l.cohorts)r.push(` \u2022 ${a.version.padEnd(16)} weight ${String(a.weight).padStart(3)} (${(Math.max(0,a.weight)/v*100).toFixed(0)}%) ${a.urls.length} url(s)`);for(const a of l.rules??[])r.push(` \u25B8 rule ${(a.name??"(unnamed)").padEnd(16)} \u2192 ${a.version} ${ruleSummary(a.when)}`)}return r.join(`
|
|
2
|
+
`)}c(summarizeRollout,"summarizeRollout"),u(summarizeRollout,"summarizeRollout"),s(summarizeRollout,"summarizeRollout");function ruleSummary(n){const e=n;return"all"in e?"("+e.all.map(ruleSummary).join(" AND ")+")":"any"in e?"("+e.any.map(ruleSummary).join(" OR ")+")":"not"in e?"NOT "+ruleSummary(e.not):`${e.attr} ${e.op}${e.value!==void 0?" "+JSON.stringify(e.value):""}`}c(ruleSummary,"ruleSummary"),u(ruleSummary,"ruleSummary"),s(ruleSummary,"ruleSummary");function parseRolloutCommand(n){const[e,...o]=n,r=s((t,i)=>{if(o.length<t)throw new Error(`usage: ${e} ${i}`)},"need");switch(e){case"status":return{status:o[0]};case"add-cohort":return r(4,"<service> <version> <weight> <url> [url...]"),{op:{kind:"add-cohort",service:o[0],version:o[1],weight:Number(o[2]),urls:o.slice(3)}};case"set-weight":return r(3,"<service> <version> <weight>"),{op:{kind:"set-weight",service:o[0],version:o[1],weight:Number(o[2])}};case"remove-cohort":return r(2,"<service> <version>"),{op:{kind:"remove-cohort",service:o[0],version:o[1]}};case"promote":return r(2,"<service> <version>"),{op:{kind:"promote",service:o[0],version:o[1]}};case"abort":return r(2,"<service> <version>"),{op:{kind:"abort",service:o[0],version:o[1]}};case"add-rule":return r(6,"<service> <name> <attr> <op> <value> <version>"),{op:{kind:"add-rule",service:o[0],rule:{name:o[1],when:{attr:o[2],op:o[3],value:o[4]},version:o[5]}}};case"remove-rule":return r(2,"<service> <name>"),{op:{kind:"remove-rule",service:o[0],name:o[1]}};default:throw new Error(`unknown command "${e??""}". commands: status, add-cohort, set-weight, remove-cohort, promote, abort, add-rule, remove-rule`)}}c(parseRolloutCommand,"parseRolloutCommand"),u(parseRolloutCommand,"parseRolloutCommand"),s(parseRolloutCommand,"parseRolloutCommand");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var g=Object.defineProperty;var c=(r,a)=>g(r,"name",{value:a,configurable:!0});var m=Object.defineProperty,l=c((r,a)=>m(r,"name",{value:a,configurable:!0}),"l"),f=Object.defineProperty,n=l((r,a)=>f(r,"name",{value:a,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEdgeAuth=createEdgeAuth;const manifest_1=require("../manifest"),dev_auth_1=require("./dev-auth");function createEdgeAuth(r,a,p){const{traceStep:o}=a;return{internal:n((e,h,t)=>{const u=String(e.headers["x-api-key"]??""),i=r.keyToProject.get(u);return i?(e.headers["x-api-key-id"]=i,e.__lensmcpCaller=i,delete e.headers["x-api-key"],o(t,"auth",{mode:"x-api-key",ok:!0,caller:i}),{ok:!0,caller:i}):(o(t,"auth",{mode:"x-api-key",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."})},"internal"),public:n((e,h,t)=>{const u=h.auth;if(u){const i=(e.url??"/").split("?")[0],k=(e.method??"GET").toUpperCase(),s=(0,manifest_1.authRuleFor)(u,i,k);if(s.mode==="jwt"){const v=String(e.headers.host??"").split(":")[0].toLowerCase(),d=(0,dev_auth_1.verifyDevJwt)(e,p?.for(v));return d?s.rule&&!(0,manifest_1.evalRule)(s.rule,(0,dev_auth_1.devAttrs)(e,d))?(o(t,"authz",{ok:!1}),{ok:!1,status:403,reason:"forbidden",detail:"Forbidden: insufficient permission"}):((0,dev_auth_1.stampIdentity)(e,d),o(t,"auth",{mode:"jwt",ok:!0}),{ok:!0,claims:d}):(o(t,"auth",{mode:"jwt",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Unauthorized: invalid or missing token"})}return o(t,"auth",{mode:s.mode}),{ok:!0}}return o(t,"auth",{mode:"public",unconfigured:!0}),{ok:!0}},"publicAuth")}}c(createEdgeAuth,"createEdgeAuth"),l(createEdgeAuth,"createEdgeAuth"),n(createEdgeAuth,"createEdgeAuth");
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var i=(a,r)=>f(a,"name",{value:r,configurable:!0});var l=Object.defineProperty,n=i((a,r)=>l(a,"name",{value:r,configurable:!0}),"n"),p=Object.defineProperty,t=n((a,r)=>p(a,"name",{value:r,configurable:!0}),"t");Object.defineProperty(exports,"__esModule",{value:!0}),exports.renderChooserPage=renderChooserPage,exports.serveChooser=serveChooser;const workspace_registry_1=require("./workspace-registry"),esc=t(a=>a.replace(/[&<>"']/g,r=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[r]),"esc");function renderChooserPage(a,r){const o=a.map(e=>({...e,live:e.key===r||(0,workspace_registry_1.isWorkspaceLive)(e)})).sort((e,s)=>Number(s.live)-Number(e.live)||e.key.localeCompare(s.key)),d=o.length===0?'<p class="empty">No workspaces registered yet. Start a gateway in a workspace (<code>lensmcp gateway start</code>) and it will appear here.</p>':o.map(e=>{const s=e.key===r?'<span class="badge here">running here</span>':e.live?'<span class="badge live">live</span>':'<span class="badge off">offline</span>',g=e.live?'<span class="open">Open dashboard \u2192</span>':'<span class="hint">start its gateway to open</span>',c=`<div class="card-main">
|
|
2
2
|
<div class="key">${esc(e.key)} ${s}</div>
|
|
3
3
|
<div class="root">${esc(e.root)}</div>
|
|
4
4
|
</div>
|
|
5
|
-
<div class="card-action">${
|
|
6
|
-
${
|
|
5
|
+
<div class="card-action">${g}</div>`;return e.live?`<a class="card is-live" href="${esc(e.base)}/">
|
|
6
|
+
${c}
|
|
7
7
|
</a>`:`<div class="card is-off">
|
|
8
|
-
${
|
|
8
|
+
${c}
|
|
9
9
|
</div>`}).join(`
|
|
10
10
|
`);return`<!doctype html>
|
|
11
11
|
<html lang="en"><head>
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
<body><div class="wrap">
|
|
43
43
|
<header>
|
|
44
44
|
<h1>LensMCP workspaces</h1>
|
|
45
|
-
<p class="sub">Pick a workspace to open its dashboard. ${
|
|
45
|
+
<p class="sub">Pick a workspace to open its dashboard. ${o.filter(e=>e.live).length} live \xB7 ${o.length} known.</p>
|
|
46
46
|
</header>
|
|
47
47
|
<div class="list">
|
|
48
|
-
${
|
|
48
|
+
${d}
|
|
49
49
|
</div>
|
|
50
50
|
<footer>Served by the gateway on <code>lensmcp.local</code>. This list updates as gateways start and stop.</footer>
|
|
51
|
-
</div></body></html>`}n(renderChooserPage,"renderChooserPage"),t(renderChooserPage,"renderChooserPage");function serveChooser(a,
|
|
51
|
+
</div></body></html>`}i(renderChooserPage,"renderChooserPage"),n(renderChooserPage,"renderChooserPage"),t(renderChooserPage,"renderChooserPage");function serveChooser(a,r){const o=renderChooserPage((0,workspace_registry_1.readWorkspaces)(),r);a.writeHead(200,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),a.end(o)}i(serveChooser,"serveChooser"),n(serveChooser,"serveChooser"),t(serveChooser,"serveChooser");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var k=Object.defineProperty;var l=(t,r)=>k(t,"name",{value:r,configurable:!0});var v=Object.defineProperty,d=l((t,r)=>v(t,"name",{value:r,configurable:!0}),"d"),w=Object.defineProperty,y=d((t,r)=>w(t,"name",{value:r,configurable:!0}),"y");Object.defineProperty(exports,"__esModule",{value:!0}),exports.controlSocketPath=controlSocketPath,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery");function controlSocketPath(){const t=process.env.LENSMCP_HOME||os.homedir();return path.join(t,".lensmcp","control.sock")}l(controlSocketPath,"controlSocketPath"),d(controlSocketPath,"controlSocketPath"),y(controlSocketPath,"controlSocketPath");function readJsonBody(t){return new Promise((r,c)=>{let s="";t.on("data",u=>{s+=u,s.length>1e6&&c(new Error("body too large"))}),t.on("end",()=>{try{r(s?JSON.parse(s):{})}catch(u){c(u)}}),t.on("error",c)})}l(readJsonBody,"readJsonBody"),d(readJsonBody,"readJsonBody"),y(readJsonBody,"readJsonBody");function startControlServer(t){const{rt:r}=t,c=controlSocketPath();fs.mkdirSync(path.dirname(c),{recursive:!0});try{fs.unlinkSync(c)}catch{}const s=y((n,i,a)=>{n.writeHead(i,{"content-type":"application/json"}),n.end(JSON.stringify(a))},"send"),u=http.createServer((n,i)=>{(async()=>{try{const a=(n.url??"").split("?")[0];if(n.method==="GET"&&a==="/list")return s(i,200,{workspaces:r.registry.keys().map(e=>{const o=r.registry.get(e);return{key:e,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0}})});if(n.method==="GET"&&a==="/status")return s(i,200,{daemon:{pid:process.pid,wsKey:r.wsKey},workspaces:r.registry.keys().map(e=>{const o=r.registry.get(e);return{key:e,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0,isDaemon:e===r.wsKey}}),services:r.registry.allServices().map(e=>({project:e.project,wsKey:e.wsKey,host:e.decl.host,state:e.state}))});if(n.method==="POST"&&a==="/register"){const e=await readJsonBody(n);if(!e.wsKey||!e.root)return s(i,400,{error:"wsKey and root are required"});const o=e.projects??{},p=(0,discovery_1.projectSourceRoots)(e.root,o,e.projectDependencies),{routes:g,services:h}=(0,discovery_1.discoverRoutes)(e.root,o,void 0,e.wsKey,p),S={wsKey:e.wsKey,root:e.root,routes:g,services:h};return r.registry.register(S),await t.onRegister?.(S),r.rebuildRoutes(),s(i,200,{ok:!0,wsKey:e.wsKey,routes:g.length,services:h.length,workspaces:r.registry.keys()})}if(n.method==="POST"&&a==="/unregister"){const e=await readJsonBody(n);if(!e.wsKey)return s(i,400,{error:"wsKey is required"});const o=r.registry.unregister(e.wsKey);if(o){for(const p of o.services)t.killService(p,"workspace unregistered");t.onUnregister?.(o)}return r.rebuildRoutes(),s(i,200,{ok:!0,removed:!!o,workspaces:r.registry.keys()})}s(i,404,{error:"not found"})}catch(a){s(i,500,{error:a.message})}})()});return u.listen(c),{socketPath:c,close:y(()=>{u.close();try{fs.unlinkSync(c)}catch{}},"close")}}l(startControlServer,"startControlServer"),d(startControlServer,"startControlServer"),y(startControlServer,"startControlServer");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var f=(e,r)=>m(e,"name",{value:r,configurable:!0});var y=Object.defineProperty,a=f((e,r)=>y(e,"name",{value:r,configurable:!0}),"a"),h=Object.defineProperty,u=a((e,r)=>h(e,"name",{value:r,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.devAttrs=void 0,exports.verifyDevJwt=verifyDevJwt,exports.stampIdentity=stampIdentity;const node_crypto_1=require("node:crypto");function bearerToken(e){const r=Array.isArray(e.headers.authorization)?e.headers.authorization[0]:e.headers.authorization,t=String(r??"");return t.startsWith("Bearer ")?t.slice(7):void 0}f(bearerToken,"bearerToken"),a(bearerToken,"bearerToken"),u(bearerToken,"bearerToken");function verifyDevJwt(e,r){const t=bearerToken(e);if(!t)return;const[n,s,p]=t.split(".");if(!n||!s||!p)return;if(r)return r.verify(t);let o;try{o=JSON.parse(Buffer.from(n,"base64url").toString()).alg}catch{return}if(typeof o=="string"&&o!=="HS256"){if(process.env.LENSMCP_GW_DEV_JWT_INSECURE_DECODE!=="1")return;try{const i=JSON.parse(Buffer.from(s,"base64url").toString());return typeof i.exp=="number"&&Date.now()/1e3>i.exp?void 0:i}catch{return}}const l=process.env.AUTH_JWT_SECRET??"tetros-dev-secret",v=(0,node_crypto_1.createHmac)("sha256",l).update(`${n}.${s}`).digest("base64url"),c=Buffer.from(p),d=Buffer.from(v);if(!(c.length!==d.length||!(0,node_crypto_1.timingSafeEqual)(c,d)))try{const i=JSON.parse(Buffer.from(s,"base64url").toString());return typeof i.exp=="number"&&Date.now()/1e3>i.exp?void 0:i}catch{return}}f(verifyDevJwt,"verifyDevJwt"),a(verifyDevJwt,"verifyDevJwt"),u(verifyDevJwt,"verifyDevJwt");function stampIdentity(e,r){const t=u((p,o)=>{o!=null&&o!==""&&(e.headers[p]=String(o))},"set");t("x-user-id",r.sub),t("x-tenant-id",r.tid);const n=r.roles;Array.isArray(n)&&n.length&&(e.headers["x-user-roles"]=n.map(String).join(","));const s=r.perms;Array.isArray(s)&&s.length&&(e.headers["x-user-permissions"]=s.map(String).join(","))}f(stampIdentity,"stampIdentity"),a(stampIdentity,"stampIdentity"),u(stampIdentity,"stampIdentity");const devAttrs=u((e,r)=>({...r,ip:e.socket.remoteAddress??"",method:(e.method??"GET").toUpperCase(),path:(e.url??"/").split("?")[0]}),"devAttrs");exports.devAttrs=devAttrs;
|
|
@@ -10,7 +10,11 @@ export declare function lensPortFromUpstream(upstream?: string): number | undefi
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function discoverRoutes(rootDir: string, projects: Record<string, {
|
|
12
12
|
root: string;
|
|
13
|
-
}>, sockBaseDir?: string, wsKey?: string): {
|
|
13
|
+
}>, sockBaseDir?: string, wsKey?: string, sourceRootsByProject?: Readonly<Record<string, string[]>>): {
|
|
14
14
|
routes: Route[];
|
|
15
15
|
services: ServiceCtl[];
|
|
16
16
|
};
|
|
17
|
+
/** Resolve each project's own root plus every transitive Nx dependency root. */
|
|
18
|
+
export declare function projectSourceRoots(workspaceRoot: string, projects: Record<string, {
|
|
19
|
+
root: string;
|
|
20
|
+
}>, dependencies?: Readonly<Record<string, readonly string[]>>): Record<string, string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var w=Object.defineProperty;var l=(e,o)=>w(e,"name",{value:o,configurable:!0});var b=Object.defineProperty,a=l((e,o)=>b(e,"name",{value:o,configurable:!0}),"a"),S=Object.defineProperty,u=a((e,o)=>S(e,"name",{value:o,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.pickSock=pickSock,exports.lensPortFromUpstream=lensPortFromUpstream,exports.discoverRoutes=discoverRoutes,exports.projectSourceRoots=projectSourceRoots;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),manifest_1=require("../manifest"),scope_1=require("./scope");function pickSock(e,o=1500){const p=Date.now();if(p-e.scannedAt>o){try{e.socks=fs.readdirSync(e.dir).filter(s=>/^child-\d+\.sock$/.test(s)).map(s=>path.join(e.dir,s))}catch{e.socks=[]}e.scannedAt=p}if(e.socks.length!==0)return e.idx=(e.idx+1)%e.socks.length,e.socks[e.idx]}l(pickSock,"pickSock"),a(pickSock,"pickSock"),u(pickSock,"pickSock");function lensPortFromUpstream(e){if(!e)return;const o=/:(\d+)/.exec(e);return o?Number(o[1]):void 0}l(lensPortFromUpstream,"lensPortFromUpstream"),a(lensPortFromUpstream,"lensPortFromUpstream"),u(lensPortFromUpstream,"lensPortFromUpstream");function discoverRoutes(e,o,p=os.tmpdir(),s="",d={}){const f=u(c=>s?path.join(p,s,`${c}-devserver`):path.join(p,`${c}-devserver`),"sockDirFor"),i=[],v=[],r=[];let n;for(const[c,x]of Object.entries(o)){let t;try{const h=JSON.parse(fs.readFileSync(path.join(e,x.root,"project.json"),"utf8"));t=h.cluster??h.davnx}catch{continue}if(!t)continue;let m,k,j,g;if(t.service)t.lens&&console.warn(`[gateway] ${c}: cluster.lens is ignored for pod (service) decls \u2014 backends are instrumented by serve-hmr.`),m={dir:f(t.service),socks:[],idx:-1,scannedAt:0},k={project:c,decl:t,root:e,wsKey:s,pool:m,state:"down",lastUsed:Date.now(),inflight:0,lastScaleAt:0},v.push(k);else if(t.lens){const h=t.port??lensPortFromUpstream(t.upstream)??5173;j=`http://localhost:${h}`,g={project:c,...s?{workspaceKey:s}:{},projectRoot:path.join(e,x.root),port:h,...d[c]?.length?{sourceRoots:d[c]}:{}}}else if(j=t.upstream??(t.port?`http://localhost:${t.port}`:void 0),!j){console.warn(`[gateway] ${c}: no service/upstream/port \u2014 skipped.`);continue}const y={project:c,prependPrefix:t.prependPrefix,target:j,pool:m,svc:k,prefix:t.path,auth:t.auth,lens:g};t.host&&(i.push({...y,host:t.host}),r.push(t.host),m&&t.internalHost!==!1&&i.push({...y,host:typeof t.internalHost=="string"?t.internalHost:`internal.${t.host}`,internal:!0})),t.default&&(n={...y,prefix:void 0})}return n&&(n.apex=(0,scope_1.baseDomainOf)(r),i.push(n)),(0,manifest_1.sortBySpecificity)(i),{routes:i,services:v}}l(discoverRoutes,"discoverRoutes"),a(discoverRoutes,"discoverRoutes"),u(discoverRoutes,"discoverRoutes");function projectSourceRoots(e,o,p={}){const s={};for(const d of Object.keys(o)){const f=new Set,i=u(r=>{if(!f.has(r)){f.add(r);for(const n of p[r]??[])i(n)}},"visit");i(d);const v=[...f].map(r=>o[r]?.root).filter(r=>typeof r=="string").map(r=>path.resolve(e,r)).filter(r=>{const n=path.relative(e,r);return n===""||n!==".."&&!n.startsWith(`..${path.sep}`)&&!path.isAbsolute(n)});s[d]=[...new Set(v)]}return s}l(projectSourceRoots,"projectSourceRoots"),a(projectSourceRoots,"projectSourceRoots"),u(projectSourceRoots,"projectSourceRoots");
|
|
@@ -10,7 +10,7 @@ import { type GatewayErrorReason } from '../gateway-errors';
|
|
|
10
10
|
* `text/plain`) + an `x-request-id` header — the SAME localized contract a service returns, so a browser
|
|
11
11
|
* app renders `t(key)` and surfaces the trace id. Mirrors the prod gateway's `sendError`.
|
|
12
12
|
*/
|
|
13
|
-
export declare const sendError: (res: http.ServerResponse, req: http.IncomingMessage, status: number, reason: GatewayErrorReason, detail?: string) =>
|
|
13
|
+
export declare const sendError: (res: http.ServerResponse, req: http.IncomingMessage, status: number, reason: GatewayErrorReason, detail?: string, metadata?: Readonly<Record<string, unknown>>, headers?: http.OutgoingHttpHeaders) => string;
|
|
14
14
|
/**
|
|
15
15
|
* The gateway OWNS these headers — strip any client-supplied copy at ingress so identity + trust can
|
|
16
16
|
* only originate from the gateway (mirrors the prod gateway's stripTrust). Applied at the very top of
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var O=Object.defineProperty;var a=(r,e)=>O(r,"name",{value:e,configurable:!0});var T=Object.defineProperty,n=a((r,e)=>T(r,"name",{value:e,configurable:!0}),"n"),i=Object.defineProperty,s=n((r,e)=>i(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.GATEWAY_OWNED_HEADERS=exports.sendError=void 0;const gateway_errors_1=require("../gateway-errors"),sendError=s((r,e,o,c,E,d,l)=>{const t=(0,gateway_errors_1.ensureRequestId)(e);return r.headersSent||r.writeHead(o,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":t,...l??{}}),r.end((0,gateway_errors_1.gatewayErrorBody)(o,c,t,E,d)),t},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"];const cors=s((r,e)=>{const o=String(r.headers.origin??"*");e.setHeader("access-control-allow-origin",o),e.setHeader("access-control-allow-credentials","true"),e.setHeader("vary","Origin")},"cors");exports.cors=cors;const corsPreflight=s((r,e,o)=>{(0,exports.cors)(r,e),e.writeHead(204,{"access-control-allow-methods":o,"access-control-allow-headers":String(r.headers["access-control-request-headers"]??"*")}),e.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var j=Object.defineProperty;var p=(l,i)=>j(l,"name",{value:i,configurable:!0});var E=Object.defineProperty,d=p((l,i)=>E(l,"name",{value:i,configurable:!0}),"d"),v=Object.defineProperty,h=d((l,i)=>v(l,"name",{value:i,configurable:!0}),"h");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),gateway_errors_1=require("../gateway-errors"),discovery_1=require("./discovery"),edge_1=require("./edge"),observability_1=require("./observability"),chooser_1=require("./chooser");function createHandler(l){const{rt:i,obs:u,auth:f,proxy:w,hooks:a}=l,m=h(e=>(0,manifest_1.matchRoute)(i.routes,(e.headers.host||"").split(":")[0],e.url??"/"),"match"),y=h((e,s)=>{if((0,edge_1.cors)(e,s),e.method==="OPTIONS"){s.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),s.end();return}const n=(e.headers.host||"").split(":")[0],o=i.routes.filter(t=>t.host&&(0,manifest_1.hostMatches)(t.host,n)&&t.prefix),r=(e.url??"/").split("?")[0];if((r==="/"||r==="/index.html"||r==="")&&i.routes.some(t=>t.host&&(0,manifest_1.hostMatches)(t.host,n)&&t.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(s,i.wsKey);return}if(o.length>0&&(r==="/health"||r==="/healthz")){const t=o.map(c=>({project:c.project,prefix:c.prefix,pods:c.pool?((0,discovery_1.pickSock)(c.pool,0),c.pool.socks.length):void 0,state:c.svc?.state??(c.target?"up":"unknown")})),g=t.every(c=>c.state!=="down"||!0);s.writeHead(g?200:503,{"content-type":"application/json"}),s.end(JSON.stringify({status:"ok",host:n,services:t},null,2));return}if(o.length>0&&r==="/.well-known/services"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({host:n,services:o.map(t=>({prefix:t.prefix,project:t.project,internal:!!t.internal}))},null,2));return}(0,edge_1.sendError)(s,e,404,"not_found",o.length>0?`No service mounted at ${r}. Mounted on ${n}: ${[...new Set(o.map(t=>t.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),_=h(async(e,s)=>{if(!e.headers.host){const r=e.authority??e.headers[":authority"];r&&(e.headers.host=r)}for(const r of edge_1.GATEWAY_OWNED_HEADERS)delete e.headers[r];(0,gateway_errors_1.ensureRequestId)(e);const n=u.beginTrace(e);n&&(u.traceStep(n,"received",{host:n.host,method:n.method,url:n.url}),e.__lensmcpTrace=n);const o=a.httpContext(e,s,n);try{if(a.has("onRequest")&&(await a.run("onRequest",o),o.responded()))return;const r=m(e);if(!r){y(e,s);return}if(o.route=r,u.traceStep(n,"route-match",{project:r.project,...r.prefix?{prefix:r.prefix}:{},...r.host?{routeHost:r.host}:{},...r.internal?{internal:!0}:{}}),e.method==="OPTIONS"){(0,edge_1.cors)(e,s),s.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),s.end();return}if((0,edge_1.cors)(e,s),a.has("beforeAuth")&&(await a.run("beforeAuth",o),o.responded()))return;if(r.internal){const t=f.internal(e,r,n);if(!t.ok){u.finishTrace(n,r.project,t.status),(0,edge_1.sendError)(s,e,t.status,t.reason,t.detail);return}o.caller=t.caller}else{const t=f.public(e,r,n);if(!t.ok){u.finishTrace(n,r.project,t.status),(0,edge_1.sendError)(s,e,t.status,t.reason,t.detail);return}if(o.claims=t.claims,a.has("afterAuth")&&(await a.run("afterAuth",o),o.responded()))return}if(r.pool&&i.serviceKeys[r.project]&&(e.headers["x-internal-token"]=i.serviceKeys[r.project]),a.has("beforeForward")&&(await a.run("beforeForward",o),o.responded()))return;a.has("onResponse")&&s.on("close",()=>{o.status=s.statusCode??0,a.run("onResponse",o)}),w.forward(r,e,s)}catch(r){o.error=r,a.has("onError")&&await a.run("onError",o),o.responded()||(0,edge_1.sendError)(s,e,500,"internal",r.message)}},"runPipeline");return(e,s)=>{_(e,s)}}p(createHandler,"createHandler"),d(createHandler,"createHandler"),h(createHandler,"createHandler");
|
|
@@ -35,7 +35,7 @@ export declare function encodeWsTextFrame(payload: string): Buffer;
|
|
|
35
35
|
* `close`/`error`, so a long-lived gateway does not accumulate dead handles (a tab is opened and closed far
|
|
36
36
|
* more often than a pod is recycled). No-op for a project with no lens frontend.
|
|
37
37
|
*/
|
|
38
|
-
export declare function registerHmrClient(rt: GatewayRuntime,
|
|
38
|
+
export declare function registerHmrClient(rt: GatewayRuntime, frontendKey: string, socket: HmrClientSocket): void;
|
|
39
39
|
/**
|
|
40
40
|
* Push `full-reload` to every live tab of `project`. Returns how many sockets were written, so the caller can
|
|
41
41
|
* log it (and a test can assert it without a browser).
|
|
@@ -45,4 +45,4 @@ export declare function registerHmrClient(rt: GatewayRuntime, project: string, s
|
|
|
45
45
|
* proxied one. A write failure is swallowed per-socket: a tab that vanished mid-broadcast must never take
|
|
46
46
|
* down the recycle (or the daemon).
|
|
47
47
|
*/
|
|
48
|
-
export declare function broadcastFullReload(rt: GatewayRuntime,
|
|
48
|
+
export declare function broadcastFullReload(rt: GatewayRuntime, frontendKey: string): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var i=(o,e)=>u(o,"name",{value:e,configurable:!0});var a=Object.defineProperty,l=i((o,e)=>a(o,"name",{value:e,configurable:!0}),"l"),c=Object.defineProperty,f=l((o,e)=>c(o,"name",{value:e,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.VITE_FULL_RELOAD_PAYLOAD=void 0,exports.encodeWsTextFrame=encodeWsTextFrame,exports.registerHmrClient=registerHmrClient,exports.broadcastFullReload=broadcastFullReload,exports.VITE_FULL_RELOAD_PAYLOAD='{"type":"full-reload"}';function encodeWsTextFrame(o){const e=Buffer.from(o,"utf8"),r=e.length;if(r<126)return Buffer.concat([Buffer.from([129,r]),e]);if(r<=65535){const t=Buffer.alloc(4);return t[0]=129,t[1]=126,t.writeUInt16BE(r,2),Buffer.concat([t,e])}const n=Buffer.alloc(10);return n[0]=129,n[1]=127,n.writeBigUInt64BE(BigInt(r),2),Buffer.concat([n,e])}i(encodeWsTextFrame,"encodeWsTextFrame"),l(encodeWsTextFrame,"encodeWsTextFrame"),f(encodeWsTextFrame,"encodeWsTextFrame");function registerHmrClient(o,e,r){const n=o.hmrClients??=new Map;let t=n.get(e);t||n.set(e,t=new Set),t.add(r);const s=f(()=>{t.delete(r),t.size===0&&n.delete(e)},"drop");r.once("close",s),r.once("error",s)}i(registerHmrClient,"registerHmrClient"),l(registerHmrClient,"registerHmrClient"),f(registerHmrClient,"registerHmrClient");function broadcastFullReload(o,e){const r=o.hmrClients?.get(e);if(!r||r.size===0)return 0;const n=encodeWsTextFrame(exports.VITE_FULL_RELOAD_PAYLOAD);let t=0;for(const s of r)if(!s.destroyed)try{s.write(n),t+=1}catch{}return t}i(broadcastFullReload,"broadcastFullReload"),l(broadcastFullReload,"broadcastFullReload"),f(broadcastFullReload,"broadcastFullReload");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var c=(o,e)=>p(o,"name",{value:e,configurable:!0});var u=Object.defineProperty,i=c((o,e)=>u(o,"name",{value:e,configurable:!0}),"i"),a=Object.defineProperty,n=i((o,e)=>a(o,"name",{value:e,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHooks=createHooks;const edge_1=require("./edge"),TERMINAL_PHASES=new Set(["onResponse","onError"]);function createHooks(o){return{run:n(async(e,r)=>{const t=o[e];if(!t||t.length===0)return;const d=TERMINAL_PHASES.has(e);for(const s of t){if(!d&&r.responded())return;await s(r)}},"run"),has:n(e=>(o[e]?.length??0)>0,"has"),httpContext:n((e,r,t)=>({req:e,res:r,socket:void 0,trace:t,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n((d,s,v)=>(0,edge_1.sendError)(r,e,d,s,v),"sendError"),responded:n(()=>r.writableEnded||r.headersSent,"responded")}),"httpContext"),upgradeContext:n((e,r,t)=>({req:e,res:void 0,socket:r,trace:t,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n(()=>{try{r.destroy()}catch{}},"sendError"),responded:n(()=>r.destroyed,"responded")}),"upgradeContext")}}c(createHooks,"createHooks"),i(createHooks,"createHooks"),n(createHooks,"createHooks");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SpawnChild } from '@lensmcp/nx-plugin/lens-frontend';
|
|
2
2
|
import type { WorkspaceFragment } from './route-registry';
|
|
3
|
-
import type { GatewayRuntime, GatewayRuntimeOptions } from './types';
|
|
3
|
+
import type { GatewayRuntime, GatewayRuntimeOptions, LensWatcherHealth } from './types';
|
|
4
4
|
import { type Observability } from './observability';
|
|
5
5
|
export declare function probePortFree(port: number): Promise<boolean>;
|
|
6
6
|
/** True when something is LISTENING on `port` on loopback — the liveness signal the exit handler cannot
|
|
@@ -17,9 +17,7 @@ export declare function probePortListening(port: number, timeoutMs?: number): Pr
|
|
|
17
17
|
* timeout, bad JSON): the caller treats `null` as "no testimony" and keeps the legacy kill-first path,
|
|
18
18
|
* so a mixed-version cluster degrades to exactly the old behavior rather than a stuck pod.
|
|
19
19
|
*/
|
|
20
|
-
export declare function probeWatcherHealth(port: number, timeoutMs?: number): Promise<
|
|
21
|
-
lastFsSetEventAt: number;
|
|
22
|
-
} | null>;
|
|
20
|
+
export declare function probeWatcherHealth(port: number, timeoutMs?: number): Promise<LensWatcherHealth | null>;
|
|
23
21
|
/**
|
|
24
22
|
* Poll until `port` is actually BINDABLE, up to `timeoutMs`. Resolves `true` when it frees, `false` on
|
|
25
23
|
* timeout (the caller then spawns anyway and lets the normal heal handle the fallout).
|
|
@@ -65,7 +63,5 @@ export interface LensChildren {
|
|
|
65
63
|
}
|
|
66
64
|
export declare function createLensChildren(rt: GatewayRuntime, obs: Observability, options: GatewayRuntimeOptions, deps?: {
|
|
67
65
|
isPortFree?: (port: number) => Promise<boolean>;
|
|
68
|
-
probeWatcherHealth?: (port: number) => Promise<
|
|
69
|
-
lastFsSetEventAt: number;
|
|
70
|
-
} | null>;
|
|
66
|
+
probeWatcherHealth?: (port: number) => Promise<LensWatcherHealth | null>;
|
|
71
67
|
}): LensChildren;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var Z=Object.defineProperty;var L=(n,p)=>Z(n,"name",{value:p,configurable:!0});var X=Object.defineProperty,l=L((n,p)=>X(n,"name",{value:p,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(n,p){return new Promise(c=>{const h=net.createServer();h.unref(),h.once("error",d=>{const E=d.code??"";c(E==="EADDRINUSE"||E==="EACCES"?"in-use":"unavailable")}),h.listen(p===void 0?{port:n}:{port:n,host:p},()=>h.close(()=>c("free")))})}L(tryBind,"tryBind"),l(tryBind,"tryBind");async function probePortFree(n){for(const p of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(n,p)==="in-use")return!1;return!0}L(probePortFree,"probePortFree"),l(probePortFree,"probePortFree");function probeConnect(n,p,c){return new Promise(h=>{let d=!1;const E=net.createConnection({host:n,port:p});E.unref();const C=setTimeout(()=>P(!1),c);C.unref?.();function P(R){d||(d=!0,clearTimeout(C),E.destroy(),h(R))}L(P,"y"),l(P,"finish"),E.once("connect",()=>P(!0)),E.once("error",()=>P(!1))})}L(probeConnect,"probeConnect"),l(probeConnect,"probeConnect");async function probePortListening(n,p=1e3){return await probeConnect("127.0.0.1",n,p)?!0:probeConnect("::1",n,p)}L(probePortListening,"probePortListening"),l(probePortListening,"probePortListening");async function probeWatcherHealth(n,p=1500){try{const c=new AbortController,h=setTimeout(()=>c.abort(),p);h.unref?.();try{const d=await fetch(`http://127.0.0.1:${n}/__lensmcp/watcher-health`,{signal:c.signal});if(!d.ok)return null;const E=await d.json();return typeof E?.lastFsSetEventAt=="number"?{lastFsSetEventAt:E.lastFsSetEventAt}:null}finally{clearTimeout(h)}}catch{return null}}L(probeWatcherHealth,"probeWatcherHealth"),l(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=l(n=>new Promise(p=>{setTimeout(p,n).unref?.()}),"sleepUnref");async function waitForPortFree(n,p,c={}){const h=c.isPortFree??probePortFree,d=Math.max(1,c.intervalMs??types_1.PORT_FREE_POLL_MS),E=Date.now()+Math.max(0,p);for(;;){if(await h(n))return!0;const C=E-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(d,C))}}L(waitForPortFree,"waitForPortFree"),l(waitForPortFree,"waitForPortFree");function viteArgPort(n){const p=l(c=>Number.isInteger(c)&&c>0&&c<65536?c:void 0,"valid");for(let c=0;c<n.length;c+=1){const h=n[c];if(h==="--port")return p(Number(n[c+1]));if(h.startsWith("--port="))return p(Number(h.slice(7)))}}L(viteArgPort,"viteArgPort"),l(viteArgPort,"viteArgPort");function createLensChildren(n,p,c,h){const{emit:d}=p,E=h?.isPortFree??probePortFree,C=h?.probeWatcherHealth??probeWatcherHealth,P=[],R=new Map,v=new Map,O=new Map,H=new Map,F=new Set;let $;const A=new Map,y=new Set,k=new Set,x=new Set,T=new Set,W=l((t,a,s)=>{if(T.has(t))return;T.add(t);const e=setInterval(()=>{(async()=>{if(n.stopped()||y.has(t)){clearInterval(e),T.delete(t);return}await E(a)&&(clearInterval(e),T.delete(t),console.log(`[gateway] lens child ${t}: :${a} freed \u2014 respawning.`),s())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);e.unref?.()},"parkUntilPortFree"),N=l(async(t,a)=>{let s=t;for(;a.has(s)||!await E(s);)s+=1;return a.add(s),s!==t&&console.warn(`[gateway] port ${t} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${s} instead.`),s},"claimFreePort"),b=l((t,a,s,e,r=!1,o,i,u)=>{const w=Date.now(),g=(0,node_child_process_1.spawn)(a,s,{cwd:i??n.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,n.root,i??n.root),...e??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});P.push(g),A.set(t,g);const m=t==="vite"||t.endsWith(":vite"),_=m?viteArgPort(s):u;if(m){const M=typeof e?.LENSMCP_EVENT_FILE=="string"?e.LENSMCP_EVENT_FILE:void 0;v.set(g,{project:e?.LENSMCP_PROJECT??"app",spawnedAt:w,port:_,probeFails:0,listening:!1,...M&&M!==n.eventFile?{bus:M}:{}})}return g.on("exit",(M,S)=>{const j=P.indexOf(g);if(j>=0&&P.splice(j,1),v.delete(g),n.stopped()||y.has(t))return;const D=Date.now()-w,f=R.get(t)??{attempts:0,portConflicts:0};(async()=>{const K=_!==void 0&&!await E(_),I=(0,lifecycle_1.isPortConflictExit)({code:M,signal:S,ranMs:D,portHeld:K},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(I&&f.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)f.portConflicts+=1,R.set(t,f),console.warn(`[gateway] lens child ${t} lost the :${_} bind race (EADDRINUSE, code=${M}); waiting for the port, then retrying (port-retry ${f.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(I){f.portConflicts=0,R.set(t,f),console.warn(`[gateway] lens child ${t}: :${_} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),d("warning",`lens child parked: ${t} (:${_} held externally)`,`cluster-lens-child:${t}`,{kind:"lens-child-parked",label:t,port:_}),W(t,_,()=>b(t,a,s,e,r,o,i,u));return}else{f.portConflicts=0,D>=HEAL_HEALTHY_MS&&(f.attempts=0);const q=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**f.attempts);f.attempts+=1,R.set(t,f),console.warn(`[gateway] lens child ${t} exited (code=${M} sig=${S}); auto-healing in ${Math.round(q/1e3)}s (attempt ${f.attempts}).`),await sleepUnref(q)}n.stopped()||y.has(t)||(_!==void 0&&!await waitForPortFree(_,types_1.PORT_FREE_WAIT_MS,{isPortFree:E,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${t}: :${_} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(n.stopped()||y.has(t))&&b(t,a,s,e,r,o,i,u))})().catch(()=>{})}),g},"spawnManagedChild");let B=!1;const Y=l(async(t,a)=>{if(!B){B=!0;try{for(const[s,e]of[...v]){if(n.stopped())return;if(e.port===void 0||s.exitCode!==null||s.signalCode!==null)continue;if(await probePortListening(e.port,types_1.LENS_PROBE_TIMEOUT_MS)){e.probeFails=0,e.listening||(e.listening=!0,d("info",`lens app up: ${e.project} (vite :${e.port} listening)`,`cluster-lens-app:${e.project}`,{kind:"service-up",project:e.project,port:e.port},void 0,e.bus));continue}const r=e.listening?t.graceMs:Math.max(t.graceMs,t.neverListenedGraceMs);if(Date.now()-e.spawnedAt<r){e.probeFails=0;continue}if(!a)continue;e.probeFails+=1;const o=(0,lifecycle_1.lensChildWedged)({spawnedAt:e.spawnedAt,probeFails:e.probeFails,hasListened:e.listening},O.get(e.project)??0,Date.now(),t);if(!o)continue;const i=e.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(r/1e3)}s boot allowance`;O.set(e.project,Date.now()),e.probeFails=0,e.listening=!1,console.error(`[gateway] wedge recycle: lens app ${e.project} is ALIVE but NOT LISTENING on :${e.port} after ${Math.max(2,t.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),d("warning",`wedge recycle: ${e.project} (${o})`,`cluster-lens-app:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o,port:e.port},void 0,e.bus);try{s.kill("SIGKILL")}catch{}}}finally{B=!1}}},"probeLensChildren"),V=l(()=>{if($||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const t={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};$=setInterval(()=>{if(n.stopped()||v.size===0||(G(),!types_1.POD_RECYCLE_ENABLED))return;const a=n.sourceSetChangedAt??0;if(a<=0)return;const s=Date.now();for(const[e,r]of v){const o=n.sourceSetChangedFor?.get(r.project)??a,i=O.get(r.project)??0;(0,lifecycle_1.lensFrontendStale)(r.spawnedAt,i,o,s,t)&&((H.get(r.project)??0)>=o||F.has(r.project)||(F.add(r.project),(async()=>{try{const u=types_1.LENS_WATCHER_PROBE_ENABLED&&r.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await C(r.port),o,Math.min(types_1.POD_STALE_SCAN_MS,15e3)+types_1.POD_RECYCLE_SETTLE_MS):"unknown";if(u==="heard"){H.set(r.project,o),console.log(`[gateway] stale source set: lens app ${r.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),d("info",`stale check: ${r.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${r.project}`,{kind:"pod-stale-verified",project:r.project},void 0,r.bus);return}if(e.exitCode!==null||e.signalCode!==null)return;O.set(r.project,Date.now()),r.listening=!1,console.log(`[gateway] zombie recycle: lens app ${r.project} (stale-source-set) \u2014 watcher ${u==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),d("warning",`zombie recycle: ${r.project} (stale-source-set)`,`cluster-lens-app:${r.project}`,{kind:"pod-recycle",project:r.project,reason:"stale-source-set",watcher:u},void 0,r.bus);const w=(0,hmr_reload_1.broadcastFullReload)(n,r.project);w>0&&console.log(`[gateway] \u2192 told ${w} open tab(s) to reload (stale optimize-dep generation)`);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${r.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{e.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{F.delete(r.project)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),$.unref?.()},"startLensSweeper"),z=l(async()=>{const t=[...new Map(n.routes.filter(e=>e.lens).map(e=>[e.project,e.lens])).values()],a=c.dashboard!==!1,s=c.mcp!==!1&&t.length>0;if(a){const e=(0,lens_frontend_1.findDashboardBundle)(n.root);if(!e)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const r=(0,scope_1.readLensScope)(n.root),o=await N(c.dashboardPort??r.dashboardPort,k),i=c.lensHost??"lensmcp.local",u=r.basePath;n.registry.appendRoutes(n.wsKey,[{host:i,prefix:u,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),n.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:r.key,root:n.root,base:u,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${o} (routed at https://${i}${u}/)`),d("info",`lens dashboard up: ${i}${u} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:o,base:u}),b("lens-dashboard",process.execPath,[e],{LENSMCP_EVENT_FILE:n.eventFile,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:u},!1,void 0,void 0,o)}}if(s){const e=(0,lens_frontend_1.findMcpBundle)(n.root);if(!e)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const r=(0,scope_1.readLensScope)(n.root),o=Number(process.env.LENSMCP_PORT??r.mcpHttpPort),i=l(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${o} (${e})`),b("lens-mcp",process.execPath,[e],{LENSMCP_EVENT_FILE:n.eventFile,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(o),LENSMCP_WORKSPACE_KEY:r.key,LENSMCP_WORKSPACE_ROOT:n.root},!1,void 0,void 0,o)},"spawnMcp");await E(o)?i():(console.warn(`[gateway] lens MCP port :${o} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),d("info",`lens MCP served externally on :${o} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-mcp-external",port:o}),W("lens-mcp",o,i))}}for(const e of t){if(e.started)continue;e.started=!0;const r=await N(e.port,x);if(r!==e.port){for(const o of n.routes)o.lens?.project===e.project&&(o.lens.port=r,o.target=`http://localhost:${r}`);e.port=r}try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:n.root,project:e.project,eventFile:n.eventFile,port:e.port,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project},log:l(o=>console.log(o),"log")},b),d("info",`lens app started: ${e.project} (vite :${e.port})`,`cluster-lens-app:${e.project}`,{kind:"lens-app-up",project:e.project,port:e.port})}catch(o){console.error(`[gateway] lens app ${e.project}: ${o.message}`)}}V()},"bootSingletonsAndApps"),U=l(async t=>{const a=(0,lens_frontend_1.findDashboardBundle)(n.root);if(!a)return;const s=(0,scope_1.readLensScope)(t.root),e=c.lensHost??"lensmcp.local",r=s.basePath,o=await N(s.dashboardPort,k),i=`lens-dashboard:${t.wsKey}`,u=path.join(t.root,".lensmcp","events.jsonl");return y.delete(i),n.registry.appendRoutes(t.wsKey,[{host:e,prefix:r,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),(0,workspace_registry_1.registerWorkspace)({key:t.wsKey,root:t.root,base:r,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${t.wsKey}' \u2192 http://localhost:${o} (routed at https://${e}${r}/)`),d("info",`lens dashboard up: ${e}${r} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:e,port:o,base:r}),b(i,process.execPath,[a],{LENSMCP_EVENT_FILE:u,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:r,LENSMCP_WS_KEY:t.wsKey},!1,void 0,t.root,o),()=>{y.add(i),k.delete(o),(0,workspace_registry_1.unregisterWorkspace)(t.wsKey);try{A.get(i)?.kill("SIGTERM")}catch{}A.delete(i)}},"hostWorkspaceDashboard"),J=l(async t=>{const a=new Map;for(const o of t.routes){if(!o.lens)continue;const i=a.get(o.lens.project)??[];i.push(o),a.set(o.lens.project,i)}if(a.size===0)return()=>{};const s=path.join(t.root,".lensmcp","events.jsonl"),e=[],r=[];for(const[o,i]of a){const u=i[0].lens;if(u.started)continue;const w=await N(u.port,x);r.push(w);for(const S of i)S.lens.started=!0,S.lens.port=w,S.target=`http://localhost:${w}`;const g=`${t.wsKey}:${o}:`,m=`${g}vite`,_=`${g}browser-capture`;e.push(m,_),y.delete(m),y.delete(_);const M=l((S,j,D,f,K,I)=>b(S,j,D,f,K,I,t.root),"spawnInRoot");console.log(`[gateway] hosting lens app '${o}' of '${t.wsKey}' \u2192 vite :${w}`);try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:u.projectRoot,workspaceRoot:t.root,project:o,eventFile:s,port:w,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:o},labelPrefix:g,log:l(S=>console.log(S),"log")},M),d("info",`lens app started: ${t.wsKey}/${o} (vite :${w})`,`cluster-lens-app:${t.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:w},void 0,s)}catch(S){console.error(`[gateway] lens app ${t.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of e){y.add(o);try{A.get(o)?.kill("SIGTERM")}catch{}A.delete(o)}for(const o of r)x.delete(o)}},"hostWorkspaceApps"),Q=l(async(t=2500)=>{$&&(clearInterval($),$=void 0);const a=[...P];for(const r of a)try{r.kill("SIGTERM")}catch{}const s=l(r=>r.exitCode===null&&r.signalCode===null,"alive"),e=Date.now()+t;for(;Date.now()<e&&a.some(s);)await new Promise(r=>setTimeout(r,100));for(const r of a)if(s(r))try{r.kill("SIGKILL")}catch{}},"reapAll"),G=l(()=>Y({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:b,bootSingletonsAndApps:z,hostWorkspaceDashboard:U,hostWorkspaceApps:J,probeLensFrontends:G,reapAll:Q}}L(createLensChildren,"createLensChildren"),l(createLensChildren,"createLensChildren");
|
|
1
|
+
"use strict";var ve=Object.defineProperty;var C=(s,u)=>ve(s,"name",{value:u,configurable:!0});var ye=Object.defineProperty,M=C((s,u)=>ye(s,"name",{value:u,configurable:!0}),"M"),_e=Object.defineProperty,l=M((s,u)=>_e(s,"name",{value:u,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,u){return new Promise(f=>{const y=net.createServer();y.unref(),y.once("error",p=>{const E=p.code??"";f(E==="EADDRINUSE"||E==="EACCES"?"in-use":"unavailable")}),y.listen(u===void 0?{port:s}:{port:s,host:u},()=>y.close(()=>f("free")))})}C(tryBind,"tryBind"),M(tryBind,"tryBind"),l(tryBind,"tryBind");async function probePortFree(s){for(const u of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,u)==="in-use")return!1;return!0}C(probePortFree,"probePortFree"),M(probePortFree,"probePortFree"),l(probePortFree,"probePortFree");function probeConnect(s,u,f){return new Promise(y=>{let p=!1;const E=net.createConnection({host:s,port:u});E.unref();const b=setTimeout(()=>S(!1),f);b.unref?.();function S(O){p||(p=!0,clearTimeout(b),E.destroy(),y(O))}C(S,"S"),M(S,"S"),l(S,"finish"),E.once("connect",()=>S(!0)),E.once("error",()=>S(!1))})}C(probeConnect,"probeConnect"),M(probeConnect,"probeConnect"),l(probeConnect,"probeConnect");async function probePortListening(s,u=1e3){return await probeConnect("127.0.0.1",s,u)?!0:probeConnect("::1",s,u)}C(probePortListening,"probePortListening"),M(probePortListening,"probePortListening"),l(probePortListening,"probePortListening");async function probeWatcherHealth(s,u=1500){try{const f=new AbortController,y=setTimeout(()=>f.abort(),u);y.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const E=await p.json();if(typeof E?.lastFsSetEventAt!="number")return null;const b=Array.isArray(E.recentEvents)?E.recentEvents.filter(S=>S&&typeof S.path=="string"&&typeof S.at=="number"&&typeof S.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(S.kind)):[];return{schemaVersion:E.schemaVersion===2?2:1,ready:E.ready!==!1,sequence:typeof E.sequence=="number"?E.sequence:0,lastFsSetEventAt:E.lastFsSetEventAt,fsSetEvents:typeof E.fsSetEvents=="number"?E.fsSetEvents:0,recentEvents:b}}finally{clearTimeout(y)}}catch{return null}}C(probeWatcherHealth,"probeWatcherHealth"),M(probeWatcherHealth,"probeWatcherHealth"),l(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=l(s=>new Promise(u=>{setTimeout(u,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,u,f={}){const y=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),E=Date.now()+Math.max(0,u);for(;;){if(await y(s))return!0;const b=E-Date.now();if(b<=0)return!1;await sleepUnref(Math.min(p,b))}}C(waitForPortFree,"waitForPortFree"),M(waitForPortFree,"waitForPortFree"),l(waitForPortFree,"waitForPortFree");function viteArgPort(s){const u=l(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const y=s[f];if(y==="--port")return u(Number(s[f+1]));if(y.startsWith("--port="))return u(Number(y.slice(7)))}}C(viteArgPort,"viteArgPort"),M(viteArgPort,"viteArgPort"),l(viteArgPort,"viteArgPort");function createLensChildren(s,u,f,y){const{emit:p}=u,E=y?.isPortFree??probePortFree,b=y?.probeWatcherHealth??probeWatcherHealth,S=[],O=new Map,A=new Map,j=new Map,G=new Map,x=new Set;let D;const k=new Map,$=new Set,W=new Set,N=new Set,L=new Map,F=new WeakMap,B=new Set,J=l((e,r,n)=>{if(B.has(e))return;B.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||$.has(e)){clearInterval(t),B.delete(e);return}await E(r)&&(clearInterval(t),B.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=l(async(e,r)=>{let n=e;for(;r.has(n)||!await E(n);)n+=1;return r.add(n),n!==e&&console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${n} instead.`),n},"claimFreePort"),ie=l((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),T=l((e,r,n,t,o=!1,a,i,c)=>{const g=Date.now(),d=(0,node_child_process_1.spawn)(r,n,{cwd:i??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,i??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});S.push(d),k.set(e,d),ie(e,d,t);const h=e==="vite"||e.endsWith(":vite"),R=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,w=R&&R!==s.eventFile?R:void 0,_=h?viteArgPort(n):c;h&&A.set(d,{key:t?.LENSMCP_FRONTEND_KEY??`${i??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??s.root,spawnedAt:g,port:_,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...w?{bus:w}:{}});const v=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return v&&_!==void 0&&(p("info",`lens child starting: ${v} (:${_})`,`cluster-lens-child:${v}`,{kind:"lens-child-starting",project:v,label:e,pid:d.pid,port:_},void 0,w),(async()=>{const m=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<m;){if(d.exitCode!==null||d.signalCode!==null||$.has(e))return;if(await probePortListening(_,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${v} (:${_})`,`cluster-lens-child:${v}`,{kind:"lens-child-up",project:v,label:e,pid:d.pid,port:_},void 0,w);return}await sleepUnref(150)}})().catch(()=>{})),d.on("exit",(m,z)=>{const oe=S.indexOf(d);oe>=0&&S.splice(oe,1),k.get(e)===d&&k.delete(e);const ne=A.get(d);A.delete(d);const we=$.delete(e);if(s.stopped()||we||(v&&p("warning",`lens child exited: ${v}`,`cluster-lens-child:${v}`,{kind:"lens-child-exit",project:v,label:e,pid:d.pid,code:m,signal:z},void 0,w),ne&&ce(d,ne)))return;const re=Date.now()-g,P=O.get(e)??{attempts:0,portConflicts:0};(async()=>{const Se=_!==void 0&&!await E(_),se=(0,lifecycle_1.isPortConflictExit)({code:m,signal:z,ranMs:re,portHeld:Se},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(se&&P.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)P.portConflicts+=1,O.set(e,P),console.warn(`[gateway] lens child ${e} lost the :${_} bind race (EADDRINUSE, code=${m}); waiting for the port, then retrying (port-retry ${P.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(se){P.portConflicts=0,O.set(e,P),console.warn(`[gateway] lens child ${e}: :${_} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${_} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:_}),J(e,_,()=>T(e,r,n,t,o,a,i,c));return}else{P.portConflicts=0,re>=HEAL_HEALTHY_MS&&(P.attempts=0);const ae=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**P.attempts);P.attempts+=1,O.set(e,P),console.warn(`[gateway] lens child ${e} exited (code=${m} sig=${z}); auto-healing in ${Math.round(ae/1e3)}s (attempt ${P.attempts}).`),await sleepUnref(ae)}s.stopped()||$.has(e)||(_!==void 0&&!await waitForPortFree(_,types_1.PORT_FREE_WAIT_MS,{isPortFree:E,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${_} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||$.has(e))&&T(e,r,n,t,o,a,i,c))})().catch(()=>{})}),d},"spawnManagedChild"),X=l(e=>{const r=`${e.wsKey}:${e.project}`,n=L.get(r);if(n)return n;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,F.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,F.set(e.frontend,t),t},"registerFrontend"),Q=l((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;$.delete(a),$.delete(i);const c=l((h,R,w,_,v,m)=>T(h,R,w,_,v,m,e.workspaceRoot),"spawnInRoot"),g=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n},labelPrefix:o,log:l(h=>console.log(h),"log")},c),d={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:g?.vite??k.get(a),capture:k.get(i)};return e.generations.add(d),d},"spawnFrontendGeneration"),K=l((e,r)=>{if(!e)return;L.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)if($.add(t),!(!o||o.exitCode!==null||o.signalCode!==null)){try{o.kill("SIGTERM")}catch{}setTimeout(()=>{if(o.exitCode===null&&o.signalCode===null)try{o.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}r&&N.delete(e.port)},"terminateGeneration"),le=l(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await b(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),H=l(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),Y=l(e=>!e.disposed&&!s.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),q=l((e,r)=>{if(!Y(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const c=await I(e.desiredPort,N);if(a=c,!Y(e))return N.delete(c),!1;o=Q(e,c,"replacement"),e.candidate=o;const g=await H(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:g},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const d=await le(o);if(!Y(e))return K(o,!0),e.candidate=void 0,!1;if(!d){K(o,!0),e.candidate=void 0;const w=await H(n);return e.state=w?"ready":"failed",w&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:w},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const w of e.routes)w.target=`http://localhost:${o.port}`,w.lens&&(w.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const h=o.vite?A.get(o.vite):void 0;h&&(h.role="active",h.listening=!0);const R=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${R>0?`; reloading ${R} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:R},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const w=`${n.labelPrefix}browser-capture`;$.add(w);try{n.capture?.kill("SIGTERM")}catch{}setTimeout(()=>K(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(c){K(o,!0),!o&&a!==void 0&&N.delete(a),e.candidate=void 0;const g=await H(n);e.state=g?"ready":"failed",g&&(e.reason=void 0,e.startedAt=void 0);const d=c.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${d}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:g,error:d},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),Z=l((e,r)=>{e.active=Q(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),ce=l((e,r)=>{const n=L.get(r.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${r.generation} :${r.port}; starting an alternate-port replacement.`),q(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=F.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const n=F.get(e);return n?q(n,r):Promise.resolve(!1)}}:void 0;let U=!1,ee=0;const de=l(()=>{U||Date.now()-ee<types_1.POD_STALE_SCAN_MS||(U=!0,ee=Date.now(),(async()=>{for(const e of[...L.values()]){if(s.stopped())return;const r=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,(0,scope_1.readSourceSetExclude)(e.workspaceRoot));if(L.get(e.key)!==e)continue;const n=e.sourceSnapshot;if(e.sourceSnapshot=r,!n)continue;const t=(0,lifecycle_1.diffSourceSets)(n,r,Date.now());t&&(e.sourceChange=t,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:t.added,removed:t.removed,truncated:t.truncated},void 0,e.eventFile===s.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{U=!1}))},"scanFrontendSourceSets");let V=!1;const pe=l(async(e,r)=>{if(!V){V=!0;try{for(const[n,t]of[...A]){if(s.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const c=L.get(t.key);c?.active?.vite===n&&(c.state="ready",c.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},j.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;j.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus);try{n.kill("SIGKILL")}catch{}}}finally{V=!1}}},"probeLensChildren"),ue=l(()=>{if(D||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};D=setInterval(()=>{if(s.stopped()||A.size===0||(te(),!types_1.POD_RECYCLE_ENABLED))return;de();const r=Date.now();for(const[n,t]of A){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=j.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,r,e)&&((G.get(t.key)??0)>=a||x.has(t.key)||(x.add(t.key),(async()=>{try{const c=o?.sourceChange,g=c&&!c.truncated?[...c.added,...c.removed].map(w=>path.resolve(t.workspaceRoot,w)):[],d=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await b(t.port),a,Math.min(types_1.POD_STALE_SCAN_MS,15e3)+types_1.POD_RECYCLE_SETTLE_MS,g):"unknown";if(d==="heard"){G.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(n.exitCode!==null||n.signalCode!==null)return;j.set(t.key,Date.now());const h=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&h){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${d==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:h.wsKey,project:t.project,reason:"stale-source-set",watcher:d,added:c?.added,removed:c?.removed},void 0,t.bus),await q(h,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${d==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:d},void 0,t.bus);const R=(0,hmr_reload_1.broadcastFullReload)(s,t.key);R>0&&console.log(`[gateway] \u2192 told ${R} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(n.exitCode===null&&n.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{x.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),fe=l(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,n=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(s.root),a=await I(f.dashboardPort??o.dashboardPort,W),i=f.lensHost??"lensmcp.local",c=o.basePath;s.registry.appendRoutes(s.wsKey,[{host:i,prefix:c,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:s.root,base:c,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${c}/)`),p("info",`lens dashboard up: ${i}${c} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:c}),T("lens-dashboard",process.execPath,[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:c},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=l(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),T("lens-mcp",process.execPath,[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await E(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),J("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(g=>g.lens?.project===t.project),i=await I(t.port,N);for(const g of a)g.lens&&(g.lens.port=i),g.target=`http://localhost:${i}`;t.port=i;const c=X({wsKey:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:o});try{Z(c,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(g){N.delete(i),c.state="failed",console.error(`[gateway] lens app ${t.project}: ${g.message}`)}}ue()},"bootSingletonsAndApps"),Ee=l(async e=>{const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const n=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,W),i=`lens-dashboard:${e.wsKey}`,c=path.join(e.root,".lensmcp","events.jsonl");return $.delete(i),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o}),T(i,process.execPath,[r],{LENSMCP_EVENT_FILE:c,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey},!1,void 0,e.root,a),()=>{$.add(i),W.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);try{k.get(i)?.kill("SIGTERM")}catch{}k.delete(i)}},"hostWorkspaceDashboard"),ge=l(async e=>{const r=new Map;for(const o of e.routes){if(!o.lens)continue;const a=r.get(o.lens.project)??[];a.push(o),r.set(o.lens.project,a)}if(r.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of r){const i=a[0].lens;if(i.started)continue;const c=await I(i.port,N),g=i.port;for(const h of a)h.lens.started=!0,h.lens.port=c,h.target=`http://localhost:${c}`;const d=X({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:g});t.push(d),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${c}`);try{Z(d,c),p("info",`lens app started: ${e.wsKey}/${o} (vite :${c})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:c},void 0,n)}catch(h){N.delete(c),d.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${h.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])K(a,!0);for(const a of o.routes)a.lens&&F.delete(a.lens);F.delete(o.frontend),j.delete(o.key),G.delete(o.key),x.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),he=l(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const r=[...S];for(const o of r)try{o.kill("SIGTERM")}catch{}const n=l(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of r)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),te=l(()=>pe({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:T,bootSingletonsAndApps:fe,hostWorkspaceDashboard:Ee,hostWorkspaceApps:ge,probeLensFrontends:te,reapAll:he}}C(createLensChildren,"createLensChildren"),M(createLensChildren,"createLensChildren"),l(createLensChildren,"createLensChildren");
|