lensmcp 1.16.18 → 1.16.19
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/lib/cli.js +50 -11
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -330,15 +330,37 @@ function runGateway(ctx, args, out, err) {
|
|
|
330
330
|
}
|
|
331
331
|
if (healed)
|
|
332
332
|
out(`healed: found an orphaned gateway (pid ${pid}) the pid file had lost — stopping it.`);
|
|
333
|
-
//
|
|
333
|
+
// GRACEFUL first: SIGTERM the whole process group (the daemon's handler reaps its children — pods +
|
|
334
|
+
// lens vites/dashboard/mcp — and exits, so `nx run` deregisters and the next `start` won't hang on the
|
|
335
|
+
// nx run-lock). Then WAIT for the port to actually free.
|
|
334
336
|
killGatewayTree(pid);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
let hardKilled = false;
|
|
338
|
+
if (!waitForPortFree(6000)) {
|
|
339
|
+
// A child wedged (vite ignores SIGTERM) and is still holding :443. Escalate to a GROUP SIGKILL — that
|
|
340
|
+
// reaps the wedged vite AND its forks — then SIGKILL any straggler still on the port.
|
|
341
|
+
hardKilled = true;
|
|
342
|
+
killGatewayTree(pid, 'SIGKILL');
|
|
343
|
+
for (const p of pidsOnPort443()) {
|
|
344
|
+
try {
|
|
345
|
+
process.kill(p, 'SIGKILL');
|
|
346
|
+
}
|
|
347
|
+
catch { /* already gone */ }
|
|
348
|
+
}
|
|
349
|
+
waitForPortFree(3000);
|
|
350
|
+
}
|
|
351
|
+
// A differently-parented lens gateway may still hold :443 (a desync tree) — force it too.
|
|
338
352
|
const still = gatewayOnPort443(cwd);
|
|
339
|
-
if (still !== undefined && still !== pid)
|
|
340
|
-
killGatewayTree(still);
|
|
341
|
-
|
|
353
|
+
if (still !== undefined && still !== pid) {
|
|
354
|
+
killGatewayTree(still, 'SIGKILL');
|
|
355
|
+
hardKilled = true;
|
|
356
|
+
}
|
|
357
|
+
rmFile(pidFile);
|
|
358
|
+
// A hard SIGKILL means the `nx run gateway:serve` process died WITHOUT deregistering from the nx daemon,
|
|
359
|
+
// so the next `start` would block on "Waiting for gateway:serve in another nx process". Clear the stale
|
|
360
|
+
// nx daemon state (best-effort) so a subsequent start is clean. The graceful path skips this (fast).
|
|
361
|
+
if (hardKilled)
|
|
362
|
+
refreshNxGraph(cwd, ctx.env, () => { });
|
|
363
|
+
out(`gateway stopped (pid ${pid})${hardKilled ? ' — forced (a child ignored SIGTERM)' : ''}.`);
|
|
342
364
|
return { exitCode: 0 };
|
|
343
365
|
};
|
|
344
366
|
const status = () => {
|
|
@@ -519,20 +541,37 @@ function reconcileGateway(pidFile, cwd) {
|
|
|
519
541
|
}
|
|
520
542
|
return { healed: false };
|
|
521
543
|
}
|
|
522
|
-
/** Best-effort kill of a detached gateway process GROUP (the leader is its own group via `detached`).
|
|
523
|
-
|
|
544
|
+
/** Best-effort kill of a detached gateway process GROUP (the leader is its own group via `detached`).
|
|
545
|
+
* `signal` defaults to SIGTERM (graceful — the daemon's handler reaps its children + exits, so `nx run`
|
|
546
|
+
* deregisters cleanly); a caller escalates to SIGKILL when a wedged child (vite ignores SIGTERM) keeps the
|
|
547
|
+
* port held. Killing the GROUP (`-pid`) reaps the daemon + `nx run` + every child + their forks at once. */
|
|
548
|
+
function killGatewayTree(pid, signal = 'SIGTERM') {
|
|
524
549
|
try {
|
|
525
|
-
process.kill(-pid,
|
|
550
|
+
process.kill(-pid, signal);
|
|
526
551
|
}
|
|
527
552
|
catch {
|
|
528
553
|
try {
|
|
529
|
-
process.kill(pid,
|
|
554
|
+
process.kill(pid, signal);
|
|
530
555
|
}
|
|
531
556
|
catch {
|
|
532
557
|
/* already gone */
|
|
533
558
|
}
|
|
534
559
|
}
|
|
535
560
|
}
|
|
561
|
+
/** Poll (via lsof) until :443 is free or the timeout elapses. Returns true once free. Unix-only; on a
|
|
562
|
+
* platform without lsof `pidsOnPort443()` returns [] so this resolves free immediately (unchanged path). */
|
|
563
|
+
function waitForPortFree(timeoutMs) {
|
|
564
|
+
const deadline = Date.now() + timeoutMs;
|
|
565
|
+
for (;;) {
|
|
566
|
+
if (pidsOnPort443().length === 0)
|
|
567
|
+
return true;
|
|
568
|
+
if (Date.now() >= deadline)
|
|
569
|
+
return false;
|
|
570
|
+
// Busy-wait in ~150ms slices (a stop/restart command, not a hot path) — spawnSync('sleep') keeps it
|
|
571
|
+
// synchronous so the CLI result reflects the real port state before returning.
|
|
572
|
+
spawnSync('sleep', ['0.15']);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
536
575
|
function rmFile(path) {
|
|
537
576
|
try {
|
|
538
577
|
if (existsSync(path))
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "lensmcp",
|
|
3
3
|
"displayName": "LensMCP",
|
|
4
4
|
"description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
|
|
5
|
-
"version": "1.16.
|
|
5
|
+
"version": "1.16.19",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Antoon",
|
|
8
8
|
"email": "davidmantoon@gmail.com"
|