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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-workspaces",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
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
  }
@@ -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 { findWorkspaces, sortWorkspaces } from "../../../workspaces/index.mjs";
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.map((workspace) => workspace.name)
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
- options.workspacePatterns?.length === 1 &&
447
- !options.workspacePatterns[0].includes("*") &&
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(options?.workspacePatterns?.[0])}`
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.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 };