bun-workspaces 1.11.0 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -33,7 +33,14 @@ const registerListeners = () => {
|
|
|
33
33
|
const handleSignal = () => {
|
|
34
34
|
runAllHandlers(signal);
|
|
35
35
|
process.off(signal, handleSignal);
|
|
36
|
-
|
|
36
|
+
// Re-raise the signal on ourselves so we exit with the conventional
|
|
37
|
+
// 128 + signum code via the signal's default action. Descendant
|
|
38
|
+
// cleanup is handled per-child by the subprocess registry (see
|
|
39
|
+
// src/runScript/subprocesses.ts), which kills each tracked child's
|
|
40
|
+
// process group individually. Broadcasting here via `kill(0, signal)`
|
|
41
|
+
// would also signal anyone sharing our pgid (e.g. a vitest worker
|
|
42
|
+
// that imported this code), which can deadlock the host runner.
|
|
43
|
+
process.kill(process.pid, signal);
|
|
37
44
|
};
|
|
38
45
|
process.on(signal, handleSignal);
|
|
39
46
|
}
|
|
@@ -25,7 +25,11 @@ import {
|
|
|
25
25
|
import { createMultiProcessOutput } from "../../../runScript/output/multiProcessOutput.mjs";
|
|
26
26
|
import { checkIsRecursiveScript } from "../../../runScript/recursion.mjs";
|
|
27
27
|
import { resolveScriptShell } from "../../../runScript/scriptShellOption.mjs";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
findWorkspaces,
|
|
30
|
+
parseWorkspacePattern,
|
|
31
|
+
sortWorkspaces,
|
|
32
|
+
} from "../../../workspaces/index.mjs";
|
|
29
33
|
import { preventDependencyCycles } from "../../../workspaces/dependencyGraph/index.mjs";
|
|
30
34
|
import { PROJECT_ERRORS } from "../../errors.mjs";
|
|
31
35
|
import {
|
|
@@ -418,10 +422,9 @@ class _FileSystemProject extends ProjectBase {
|
|
|
418
422
|
);
|
|
419
423
|
}
|
|
420
424
|
const matchedWorkspaces = sortWorkspaces(
|
|
421
|
-
|
|
422
|
-
options.workspacePatterns
|
|
423
|
-
this.workspaces
|
|
424
|
-
).flatMap((pattern) => this.findWorkspacesByPattern(pattern)),
|
|
425
|
+
options.workspacePatterns
|
|
426
|
+
? this.findWorkspacesByPattern(...options.workspacePatterns)
|
|
427
|
+
: this.workspaces,
|
|
425
428
|
);
|
|
426
429
|
let workspaces = matchedWorkspaces
|
|
427
430
|
.filter(
|
|
@@ -442,13 +445,24 @@ class _FileSystemProject extends ProjectBase {
|
|
|
442
445
|
return (aScriptConfig.order ?? 0) - (bScriptConfig.order ?? 0);
|
|
443
446
|
});
|
|
444
447
|
if (!workspaces.length) {
|
|
448
|
+
const singlePattern =
|
|
449
|
+
options.workspacePatterns?.length === 1
|
|
450
|
+
? options.workspacePatterns[0]
|
|
451
|
+
: null;
|
|
452
|
+
const parsedSingle = singlePattern
|
|
453
|
+
? parseWorkspacePattern(singlePattern)
|
|
454
|
+
: null;
|
|
445
455
|
const isSingleMatchNotFound =
|
|
446
|
-
|
|
447
|
-
|
|
456
|
+
parsedSingle !== null &&
|
|
457
|
+
parsedSingle.target === "default" &&
|
|
458
|
+
!parsedSingle.isNegated &&
|
|
459
|
+
!parsedSingle.isRegex &&
|
|
460
|
+
!parsedSingle.isRootSelector &&
|
|
461
|
+
!parsedSingle.value.includes("*") &&
|
|
448
462
|
!matchedWorkspaces.length;
|
|
449
463
|
throw new PROJECT_ERRORS.ProjectWorkspaceNotFound(
|
|
450
464
|
isSingleMatchNotFound
|
|
451
|
-
? `Workspace name or alias not found: ${JSON.stringify(
|
|
465
|
+
? `Workspace name or alias not found: ${JSON.stringify(singlePattern)}`
|
|
452
466
|
: `No matching workspaces found with script ${JSON.stringify(options.script)}`,
|
|
453
467
|
);
|
|
454
468
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createProcessOutput,
|
|
4
4
|
} from "./output/index.mjs";
|
|
5
5
|
import { createScriptExecutor } from "./scriptExecution.mjs";
|
|
6
|
-
import { createSubprocess } from "./subprocesses.mjs";
|
|
6
|
+
import { createSubprocess, killSubprocessTree } from "./subprocesses.mjs";
|
|
7
7
|
|
|
8
8
|
const SIGNAL_MAP = {
|
|
9
9
|
130: "SIGINT",
|
|
@@ -79,7 +79,7 @@ const SIGNAL_MAP = {
|
|
|
79
79
|
output: processOutput,
|
|
80
80
|
exit,
|
|
81
81
|
metadata,
|
|
82
|
-
kill: (exit) => proc
|
|
82
|
+
kill: (exit) => killSubprocessTree(proc, exit ?? "SIGTERM"),
|
|
83
83
|
};
|
|
84
84
|
};
|
|
85
85
|
|
|
@@ -2,18 +2,54 @@ import { IS_WINDOWS, runOnExit } from "../internal/core/index.mjs";
|
|
|
2
2
|
import { logger } from "../internal/logger/index.mjs";
|
|
3
3
|
|
|
4
4
|
const SUBPROCESS_REGISTRY = {};
|
|
5
|
+
/**
|
|
6
|
+
* Kill a tracked subprocess together with any descendants it has spawned.
|
|
7
|
+
*
|
|
8
|
+
* On POSIX, subprocesses are spawned with `detached: true`, which makes each
|
|
9
|
+
* child the leader of its own process group (pgid === pid). Signalling
|
|
10
|
+
* `-pid` therefore delivers the signal to every descendant in that group,
|
|
11
|
+
* not just the direct child. This is required for the common case of a
|
|
12
|
+
* shell wrapping a temp script: a plain `subprocess.kill()` only reaches
|
|
13
|
+
* the shell, leaving any grandchild (e.g. `bun build`) orphaned and
|
|
14
|
+
* reparented to init when the shell exits.
|
|
15
|
+
*
|
|
16
|
+
* On Windows there's no equivalent process-group semantics, so we fall back
|
|
17
|
+
* to a direct kill via the Bun.Subprocess handle.
|
|
18
|
+
*/ const killSubprocessTree = (subprocess, signal) => {
|
|
19
|
+
if (IS_WINDOWS) {
|
|
20
|
+
subprocess.kill(signal);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
process.kill(-subprocess.pid, signal);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
const code = error.code;
|
|
27
|
+
if (code === "ESRCH") return;
|
|
28
|
+
if (code === "EPERM") {
|
|
29
|
+
try {
|
|
30
|
+
subprocess.kill(signal);
|
|
31
|
+
} catch (innerError) {
|
|
32
|
+
if (innerError.code !== "ESRCH") {
|
|
33
|
+
throw innerError;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
5
41
|
runOnExit((codeOrSignal) => {
|
|
6
42
|
Object.values(SUBPROCESS_REGISTRY).forEach((subprocess) => {
|
|
7
43
|
/**
|
|
8
44
|
* @todo Windows support for killing subprocesses is needed.
|
|
9
45
|
* subprocess.kill() will throw with not-implemented error
|
|
10
46
|
*/ if (!subprocess.killed && subprocess.exitCode === null && !IS_WINDOWS) {
|
|
47
|
+
const signal =
|
|
48
|
+
typeof codeOrSignal === "string" ? codeOrSignal : "SIGTERM";
|
|
11
49
|
logger.debug(
|
|
12
|
-
`Killing subprocess ${subprocess.pid} with signal ${
|
|
13
|
-
);
|
|
14
|
-
subprocess.kill(
|
|
15
|
-
typeof codeOrSignal === "string" ? codeOrSignal : "SIGTERM",
|
|
50
|
+
`Killing subprocess ${subprocess.pid} with signal ${signal}`,
|
|
16
51
|
);
|
|
52
|
+
killSubprocessTree(subprocess, signal);
|
|
17
53
|
}
|
|
18
54
|
});
|
|
19
55
|
});
|
|
@@ -23,7 +59,20 @@ runOnExit((codeOrSignal) => {
|
|
|
23
59
|
argv,
|
|
24
60
|
options,
|
|
25
61
|
) => {
|
|
26
|
-
const subprocess = Bun.spawn(argv,
|
|
62
|
+
const subprocess = Bun.spawn(argv, {
|
|
63
|
+
...options,
|
|
64
|
+
// On POSIX, each tracked subprocess becomes the leader of its own
|
|
65
|
+
// process group so the registry can kill its full descendant tree via
|
|
66
|
+
// `process.kill(-pid, signal)` (see killSubprocessTree). Scoping the
|
|
67
|
+
// kill per child keeps the blast radius off the parent's process group,
|
|
68
|
+
// which matters when bun-workspaces is loaded inside another runner
|
|
69
|
+
// (e.g. a vitest worker) that shares our pgid.
|
|
70
|
+
...(IS_WINDOWS
|
|
71
|
+
? {}
|
|
72
|
+
: {
|
|
73
|
+
detached: true,
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
27
76
|
logger.debug(`Subprocess spawned with pid ${subprocess.pid}`);
|
|
28
77
|
SUBPROCESS_REGISTRY[subprocess.pid] = subprocess;
|
|
29
78
|
subprocess.exited.finally(() => {
|
|
@@ -32,4 +81,4 @@ runOnExit((codeOrSignal) => {
|
|
|
32
81
|
return subprocess;
|
|
33
82
|
};
|
|
34
83
|
|
|
35
|
-
export { createSubprocess };
|
|
84
|
+
export { createSubprocess, killSubprocessTree };
|