bun-workspaces 1.11.0 → 1.11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-workspaces",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "A monorepo management tool for Bun, with a CLI and API to enhance Bun's native workspaces.",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -33,7 +33,14 @@ const registerListeners = () => {
33
33
  const handleSignal = () => {
34
34
  runAllHandlers(signal);
35
35
  process.off(signal, handleSignal);
36
- process.kill(0, signal);
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
  }
@@ -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.kill(exit),
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 ${codeOrSignal}`,
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, options);
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 };