@lenne.tech/cli 1.50.0 → 1.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @lt-check-wrapper 3.
|
|
2
|
+
// @lt-check-wrapper 3.14.1
|
|
3
3
|
/**
|
|
4
4
|
* Quiet, report-driven wrapper around the project `check` pipeline.
|
|
5
5
|
*
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* scripts/check-wrapper-version.test.mjs fails when it drifts from
|
|
34
34
|
* package.json. In a generated project it keeps the release it came from.
|
|
35
35
|
*/
|
|
36
|
-
import { execFileSync,
|
|
36
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
37
37
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
38
38
|
import { dirname, join } from 'node:path';
|
|
39
39
|
import { fileURLToPath } from 'node:url';
|
|
@@ -430,7 +430,21 @@ const IDLE_TIMEOUT_MS = (() => {
|
|
|
430
430
|
const RUNNING = new Set();
|
|
431
431
|
|
|
432
432
|
/**
|
|
433
|
-
*
|
|
433
|
+
* Whether a number may become a kill target at all.
|
|
434
|
+
*
|
|
435
|
+
* A spawn that failed leaves `child.pid` undefined. On POSIX that only made `pgrep` and
|
|
436
|
+
* `process.kill` throw, but on Windows it became `taskkill /PID undefined /T /F` — a kill
|
|
437
|
+
* command built from a word that is not a pid. And the small numbers are not processes you
|
|
438
|
+
* own: POSIX pid 1 is init/launchd, and 0 and -1 are the group and the kill(2) broadcast to
|
|
439
|
+
* every process of the user (the shape that rebooted a Mac on 2026-09-23). Windows reserves
|
|
440
|
+
* 0 (System Idle) and 4 (System). Nothing a check step spawns can have any of these pids.
|
|
441
|
+
*/
|
|
442
|
+
export function isKillablePid(pid, platform = process.platform) {
|
|
443
|
+
return Number.isInteger(pid) && pid > (platform === 'win32' ? 4 : 1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* How to kill a process tree on this platform, or null when `pid` must not be touched.
|
|
434
448
|
*
|
|
435
449
|
* Windows has neither `pgrep` nor signals: `process.kill(pid, 'SIGTERM')` there ends the one
|
|
436
450
|
* process and orphans its children, and `taskkill /T` alone was measured to leave the tree
|
|
@@ -445,46 +459,85 @@ const RUNNING = new Set();
|
|
|
445
459
|
* Split out as a pure function so both branches can be tested from either platform.
|
|
446
460
|
*/
|
|
447
461
|
export function killTreePlan(pid, signal, platform = process.platform) {
|
|
462
|
+
if (!isKillablePid(pid, platform)) return null;
|
|
448
463
|
return platform === 'win32' ? { args: ['/PID', String(pid), '/T', '/F'], command: 'taskkill' } : { signal };
|
|
449
464
|
}
|
|
450
465
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Kill `pid` and its whole process tree, with every effect injected.
|
|
468
|
+
*
|
|
469
|
+
* Injecting the platform alone is not enough: the effects would still run for real, against
|
|
470
|
+
* this machine, with whatever pid the test chose. So all four are required and nothing
|
|
471
|
+
* defaults — a test cannot reach a real `taskkill`, `pgrep` or `process.kill` by leaving one
|
|
472
|
+
* out. `killTree` below is the only caller that passes the real ones.
|
|
473
|
+
*
|
|
474
|
+
* - `run(command, args)` executes the Windows plan.
|
|
475
|
+
* - `childrenOf(pid)` returns the direct children of `pid` (POSIX).
|
|
476
|
+
* - `signal(pid, sig)` delivers `sig` to `pid` (POSIX).
|
|
477
|
+
*
|
|
478
|
+
* POSIX order is leaves first, so a parent cannot respawn a child that was just killed, and
|
|
479
|
+
* each pid is signalled at most once. Pids a lookup returns pass the same check as the root —
|
|
480
|
+
* a stray 1 in `pgrep` output must not become a signal to init.
|
|
481
|
+
*/
|
|
482
|
+
export function killTreeWith(pid, sig, { childrenOf, platform, run, signal }) {
|
|
483
|
+
for (const [name, fn] of Object.entries({ childrenOf, run, signal })) {
|
|
484
|
+
if (typeof fn !== 'function') throw new TypeError(`killTreeWith: \`${name}\` must be injected`);
|
|
485
|
+
}
|
|
486
|
+
if (typeof platform !== 'string') throw new TypeError('killTreeWith: `platform` must be injected');
|
|
487
|
+
const plan = killTreePlan(pid, sig, platform);
|
|
488
|
+
if (!plan) return;
|
|
457
489
|
if (plan.command) {
|
|
458
490
|
try {
|
|
459
|
-
|
|
491
|
+
run(plan.command, plan.args);
|
|
460
492
|
} catch {
|
|
461
493
|
/* already gone, or taskkill refused — nothing further to try */
|
|
462
494
|
}
|
|
463
495
|
return;
|
|
464
496
|
}
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
497
|
+
const seen = new Set();
|
|
498
|
+
const order = [];
|
|
499
|
+
const collect = (p) => {
|
|
500
|
+
if (seen.has(p) || !isKillablePid(p, platform)) return;
|
|
501
|
+
seen.add(p);
|
|
502
|
+
let children = [];
|
|
469
503
|
try {
|
|
470
|
-
|
|
471
|
-
.toString()
|
|
472
|
-
.trim();
|
|
504
|
+
children = childrenOf(p);
|
|
473
505
|
} catch {
|
|
474
506
|
/* no children */
|
|
475
507
|
}
|
|
476
|
-
|
|
508
|
+
for (const c of children) collect(c);
|
|
509
|
+
order.push(p);
|
|
477
510
|
};
|
|
478
|
-
collect(
|
|
479
|
-
for (const
|
|
511
|
+
collect(pid);
|
|
512
|
+
for (const p of order) {
|
|
480
513
|
try {
|
|
481
|
-
|
|
514
|
+
signal(p, plan.signal);
|
|
482
515
|
} catch {
|
|
483
516
|
/* already gone */
|
|
484
517
|
}
|
|
485
518
|
}
|
|
486
519
|
}
|
|
487
520
|
|
|
521
|
+
/** Direct children of `pid` via `pgrep -P`; empty when there are none (pgrep exits 1). */
|
|
522
|
+
function pgrepChildren(pid) {
|
|
523
|
+
const out = execFileSync('pgrep', ['-P', String(pid)], { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
524
|
+
.toString()
|
|
525
|
+
.trim();
|
|
526
|
+
return out ? out.split('\n').map(Number) : [];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Best-effort kill of a child's whole process tree (sh → pnpm → vitest →
|
|
530
|
+
// fork workers). Killing only the direct child orphans the tree — exactly the
|
|
531
|
+
// zombie workers a deadlock leaves behind.
|
|
532
|
+
function killTree(child, signal = 'SIGTERM') {
|
|
533
|
+
killTreeWith(child.pid, signal, {
|
|
534
|
+
childrenOf: pgrepChildren,
|
|
535
|
+
platform: process.platform,
|
|
536
|
+
run: (command, args) => execFileSync(command, args, { stdio: 'ignore' }),
|
|
537
|
+
signal: (pid, sig) => process.kill(pid, sig),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
488
541
|
// idleTimeoutMs > 0 arms the no-output watchdog for this child; 0 (the default)
|
|
489
542
|
// runs it unwatched. Only callers that KNOW the child streams progress (test
|
|
490
543
|
// steps) should pass a timeout — see runGroup.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "26365f7c8acaa98bc48f360886055d0fe45c593d",
|
|
3
3
|
"files": {
|
|
4
4
|
"build-test-gate.mjs": "865bf1b9dd28151f53b9f2dfe4c3c46d29b9303fb1c2f497ce0e3136bcd37d71",
|
|
5
|
-
"check.mjs": "
|
|
5
|
+
"check.mjs": "6be5506f5fb63f53c65ea438c67d810de1afe1f3299828ef4531c37cc2457d4b",
|
|
6
6
|
"lib/ansi.mjs": "f997ee6e9bf1c7ee54ab612d4040fcc778064ec160eae19bf5c3960a75a6e2ff",
|
|
7
7
|
"lib/audit-report.mjs": "503825c3342dec1ee5d8d70e5af21b73e798faee320426b2492190cde0801df2",
|
|
8
8
|
"lib/workspace-packages.mjs": "ccdd133622a88186c924bc8271d35cf1f8357dc26d4a607998ca5e91fce5bf88"
|
|
9
9
|
},
|
|
10
|
-
"ref": "v3.
|
|
10
|
+
"ref": "v3.14.1",
|
|
11
11
|
"repository": "lenneTech/lt-monorepo",
|
|
12
|
-
"version": "3.
|
|
12
|
+
"version": "3.14.1"
|
|
13
13
|
}
|