@lensmcp/cluster 1.16.13 → 1.16.15
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.d.ts.map +1 -1
- package/basic-ssl.js +10 -5
- package/executors/gateway/runtime/lens-children.d.ts.map +1 -1
- package/executors/gateway/runtime/lens-children.js +4 -0
- package/executors/gateway/runtime/lifecycle.d.ts.map +1 -1
- package/executors/gateway/runtime/lifecycle.js +10 -1
- package/executors/gateway/runtime/server.d.ts.map +1 -1
- package/executors/gateway/runtime/server.js +23 -2
- package/package.json +3 -3
package/basic-ssl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-ssl.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/basic-ssl.ts"],"names":[],"mappings":"AA6BA,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAKnD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAgB,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"basic-ssl.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/basic-ssl.ts"],"names":[],"mappings":"AA6BA,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAKnD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAgB,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,MAAM,CA0CzG;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMrD"}
|
package/basic-ssl.js
CHANGED
|
@@ -35,6 +35,15 @@ exports.CA_CERT_FILE = 'lensmcp-local-ca.crt';
|
|
|
35
35
|
const LEGACY_CA_CERT_FILE = 'davnx-local-ca.crt';
|
|
36
36
|
const CA_KEY_FILE = '_ca-key.pem';
|
|
37
37
|
function getCertificateSync(cacheDir, name = 'lensmcp.dev', domains = []) {
|
|
38
|
+
// Resolve the CA FIRST: loadOrCreateCa mints a fresh CA (and sweeps every OLD-CA leaf) whenever the CA
|
|
39
|
+
// cert is missing/expired — e.g. after a rebrand renamed the CA file. Doing this BEFORE the leaf-cache read
|
|
40
|
+
// is what makes a CA change actually take effect: otherwise a stale, old-CA-signed leaf short-circuits the
|
|
41
|
+
// mint and the rebrand never applies (the bug that needed a manual cache clear). On a normal run the CA is
|
|
42
|
+
// unchanged, nothing is swept, and the SAN-keyed leaf cache below still fast-paths.
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
const forge = require('node-forge');
|
|
45
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
46
|
+
const ca = loadOrCreateCa(cacheDir, forge);
|
|
38
47
|
// The SAN set is part of the cache key: adding a gateway hostname must
|
|
39
48
|
// mint a fresh leaf, not serve a cached one that lacks the new SAN.
|
|
40
49
|
const sanKey = crypto
|
|
@@ -46,16 +55,12 @@ function getCertificateSync(cacheDir, name = 'lensmcp.dev', domains = []) {
|
|
|
46
55
|
try {
|
|
47
56
|
const stat = fs.statSync(leafPath);
|
|
48
57
|
if (Date.now() - stat.ctimeMs < LEAF_MAX_AGE_MS) {
|
|
49
|
-
return fs.readFileSync(leafPath, 'utf8');
|
|
58
|
+
return fs.readFileSync(leafPath, 'utf8'); // reached only when the CA was NOT just re-minted (a mint sweeps leaves)
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
catch {
|
|
53
62
|
/* no cache yet */
|
|
54
63
|
}
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
56
|
-
const forge = require('node-forge');
|
|
57
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
58
|
-
const ca = loadOrCreateCa(cacheDir, forge);
|
|
59
64
|
const pem = createLeafCertificate(forge, ca, name, domains);
|
|
60
65
|
fs.writeFileSync(leafPath, pem);
|
|
61
66
|
// Stale leaves and the pre-1.3.4 self-signed cache just rot here — sweep
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lens-children.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lens-children.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAgB,MAAM,SAAS,CAAC;AAQnF,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAM7E,MAAM,WAAW,YAAY;IAC3B,iBAAiB,EAAE,UAAU,CAAC;IAC9B;iFAC6E;IAC7E,qBAAqB,IAAI,IAAI,CAAC;IAC9B;;6GAEyG;IACzG,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC9E,wDAAwD;IACxD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"lens-children.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lens-children.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAgB,MAAM,SAAS,CAAC;AAQnF,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAM7E,MAAM,WAAW,YAAY;IAC3B,iBAAiB,EAAE,UAAU,CAAC;IAC9B;iFAC6E;IAC7E,qBAAqB,IAAI,IAAI,CAAC;IAC9B;;6GAEyG;IACzG,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC9E,wDAAwD;IACxD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,GAAG,YAAY,CAkNvH"}
|
|
@@ -206,6 +206,9 @@ function createLensChildren(rt, obs, options) {
|
|
|
206
206
|
const eventFile = path.join(fragment.root, '.lensmcp', 'events.jsonl'); // the registered workspace's OWN bus
|
|
207
207
|
removedLabels.delete(label); // a fresh registration after a prior unregister → allow auto-heal again
|
|
208
208
|
rt.registry.appendRoutes(fragment.wsKey, [{ host: lensHost, prefix: lensBase, project: observability_1.LENS_DASHBOARD_PROJECT, target: `http://localhost:${port}` }]);
|
|
209
|
+
// Publish the hosted workspace to the GLOBAL chooser registry (keyed on the DAEMON's pid — the daemon is
|
|
210
|
+
// what keeps it live), so `lensmcp.local/` lists it. Reaped on unregister below.
|
|
211
|
+
(0, workspace_registry_1.registerWorkspace)({ key: fragment.wsKey, root: fragment.root, base: lensBase, pid: process.pid, updatedAt: new Date().toISOString() });
|
|
209
212
|
console.log(`[gateway] hosting dashboard for workspace '${fragment.wsKey}' → http://localhost:${port} (routed at https://${lensHost}${lensBase}/)`);
|
|
210
213
|
emit('info', `lens dashboard up: ${lensHost}${lensBase} → :${port}`, 'cluster-lens-dashboard', { kind: 'lens-dashboard-up', host: lensHost, port, base: lensBase });
|
|
211
214
|
spawnManagedChild(label, process.execPath, [bundle], {
|
|
@@ -216,6 +219,7 @@ function createLensChildren(rt, obs, options) {
|
|
|
216
219
|
return () => {
|
|
217
220
|
removedLabels.add(label); // stop auto-heal from resurrecting it
|
|
218
221
|
usedDashboardPorts.delete(port);
|
|
222
|
+
(0, workspace_registry_1.unregisterWorkspace)(fragment.wsKey); // drop it from the chooser registry
|
|
219
223
|
try {
|
|
220
224
|
labeledChildren.get(label)?.kill('SIGTERM');
|
|
221
225
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4CrD;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4CrD;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAwQvF"}
|
|
@@ -166,7 +166,16 @@ function createServiceLayer(rt, obs) {
|
|
|
166
166
|
// means env vars DO reach the pod, so the east-west key + CA propagate.
|
|
167
167
|
// LENSMCP_WS_KEY (the SERVICE's owning workspace) namespaces its pod sock dir
|
|
168
168
|
// ($TMPDIR/<wsKey>/<service>-devserver) — the namespaced pool the daemon scans for this fragment.
|
|
169
|
-
|
|
169
|
+
// LENSMCP_EVENT_FILE points a REGISTERED workspace's pods at ITS OWN bus (<root>/.lensmcp/events.jsonl),
|
|
170
|
+
// so its dashboard shows its live traces, not the daemon's. The daemon's own services keep rt.eventFile
|
|
171
|
+
// exactly (svc.root === rt.root), so single-workspace behavior is unchanged.
|
|
172
|
+
env: {
|
|
173
|
+
...process.env,
|
|
174
|
+
NX_DAEMON: 'false',
|
|
175
|
+
...(svc.wsKey ? { LENSMCP_WS_KEY: svc.wsKey } : {}),
|
|
176
|
+
LENSMCP_EVENT_FILE: svc.wsKey === rt.wsKey ? rt.eventFile : path.join(svc.root, '.lensmcp', 'events.jsonl'),
|
|
177
|
+
...eastWestEnv(rt, svc.project),
|
|
178
|
+
},
|
|
170
179
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
171
180
|
});
|
|
172
181
|
svc.lastErrors = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAsB,MAAM,SAAS,CAAC;AAkDxH,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAsB,MAAM,SAAS,CAAC;AAkDxH,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA0OxB"}
|
|
@@ -140,6 +140,10 @@ async function startGateway(options, context) {
|
|
|
140
140
|
// REGISTERS its fragment here instead of fighting for :443, and the daemon HOSTS its services (spawns
|
|
141
141
|
// them, model A) + its dashboard (planning/multi-workspace-gateway.md P3). Harmless for a lone gateway.
|
|
142
142
|
const dashboardReapers = new Map();
|
|
143
|
+
// Assigned after the TLS servers are built (below) — re-mints the gateway cert with the CURRENT route
|
|
144
|
+
// hosts and hot-swaps it, so a workspace that registers AFTER boot gets its hostnames into the cert SANs
|
|
145
|
+
// (otherwise the browser sees ERR_CERT_COMMON_NAME_INVALID for the new workspace's hosts).
|
|
146
|
+
let refreshCert = () => { };
|
|
143
147
|
let controlServer;
|
|
144
148
|
try {
|
|
145
149
|
controlServer = (0, control_1.startControlServer)({
|
|
@@ -151,10 +155,12 @@ async function startGateway(options, context) {
|
|
|
151
155
|
const reap = lens.hostWorkspaceDashboard(fragment);
|
|
152
156
|
if (reap)
|
|
153
157
|
dashboardReapers.set(fragment.wsKey, reap);
|
|
158
|
+
refreshCert(); // the new workspace's hosts must join the gateway cert SANs
|
|
154
159
|
},
|
|
155
160
|
onUnregister: (fragment) => {
|
|
156
161
|
dashboardReapers.get(fragment.wsKey)?.();
|
|
157
162
|
dashboardReapers.delete(fragment.wsKey);
|
|
163
|
+
refreshCert();
|
|
158
164
|
},
|
|
159
165
|
});
|
|
160
166
|
}
|
|
@@ -214,12 +220,15 @@ async function startGateway(options, context) {
|
|
|
214
220
|
// --- TLS + listeners --------------------------------------------------------
|
|
215
221
|
let pem;
|
|
216
222
|
let caPath;
|
|
223
|
+
let mintCert;
|
|
217
224
|
if (https) {
|
|
218
225
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
219
226
|
const basicSsl = require('../../../basic-ssl');
|
|
220
227
|
const cacheDir = (0, service_keys_1.gatewayCacheDir)(context.root);
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
// SANs = the CURRENT route hosts. `rt.routes` is rebuilt live on register, so a re-mint (refreshCert
|
|
229
|
+
// below) automatically includes a newly-registered workspace's hostnames.
|
|
230
|
+
mintCert = () => basicSsl.getCertificateSync(cacheDir, 'gateway', rt.routes.map((r) => r.host).filter((h) => !!h));
|
|
231
|
+
pem = mintCert();
|
|
223
232
|
caPath = basicSsl.caCertPath(cacheDir);
|
|
224
233
|
}
|
|
225
234
|
const servers = [];
|
|
@@ -253,6 +262,18 @@ async function startGateway(options, context) {
|
|
|
253
262
|
});
|
|
254
263
|
servers.push(server);
|
|
255
264
|
}
|
|
265
|
+
// Now that the TLS servers exist, wire the live cert refresh: on a workspace register/unregister the
|
|
266
|
+
// control endpoint calls this to re-mint the gateway cert with the CURRENT route hosts and hot-swap it
|
|
267
|
+
// into every HTTP/2 secure server (setSecureContext), so a dynamically-registered workspace's hostnames
|
|
268
|
+
// are covered (no ERR_CERT_COMMON_NAME_INVALID).
|
|
269
|
+
if (mintCert) {
|
|
270
|
+
refreshCert = () => {
|
|
271
|
+
const fresh = mintCert();
|
|
272
|
+
for (const s of servers) {
|
|
273
|
+
s.setSecureContext?.({ key: fresh, cert: fresh });
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
256
277
|
console.log('[gateway] Routes:');
|
|
257
278
|
for (const r of routes) {
|
|
258
279
|
const dest = r.pool ? `pods@${r.pool.dir}` : r.target;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.15",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@lensmcp/node-instrumentation": "1.16.
|
|
69
|
-
"@lensmcp/nx-plugin": "1.16.
|
|
68
|
+
"@lensmcp/node-instrumentation": "1.16.15",
|
|
69
|
+
"@lensmcp/nx-plugin": "1.16.15",
|
|
70
70
|
"fork-ts-checker-webpack-plugin": "^9.0.0",
|
|
71
71
|
"glob": "^11.0.0",
|
|
72
72
|
"http-proxy": "^1.18.0",
|