From a0ed5cb69c42db078b8ac250f3dfc7a8e410ccf1 Mon Sep 17 00:00:00 2001 From: Colin McDonnell <3084745+colinhacks@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:43:53 -0700 Subject: [PATCH 1/5] run: signal every concurrent child on Ctrl-C, and make --color take effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A recursive run executes members on concurrent worker threads — concurrency defaults to min(4, cpus), so this is the default shape, not only --parallel. Four defects in that shape, all reported in #685. Ctrl-C reached exactly one child. The signal forwarder held a single AtomicI32 target, so each spawn overwrote the last and untrack_child() zeroed the slot when the first member finished. Interrupting a multi-server dev run signalled one script (which one was a race), orphaned the rest holding their ports, and left nub blocked waiting on children it could no longer signal. The target is now a set: each child adds its own group and removes only its own on exit. group_on_spawn is untouched, so the own-process-group behavior #26, #27 and #463 rely on still holds — pnpm's shared-group model is deliberately not copied. --color=always did nothing at all. Cli::color had no readers, and the pre-subcommand scan stripped the token before clap could see it, so a flag listed in `nub --help` was inert. It now records the choice, colors nub's own output, and exports FORCE_COLOR to each script: 1 for --color=always, 0 for --no-color, matching pnpm 10.15.1. Default auto still forces nothing, so prefixed output is unchanged unless asked. Two predicate bugs sat in the same place. NO_COLOR was never consulted on the stream-prefix path despite --help promising it, and FORCE_COLOR=0 read as "present, therefore on" — which would have inverted --no-color for a nested nub. Both now resolve through one color_enabled predicate. --reporter-hide-prefix stripped the label from nub's own Done/exit/error line too, so a run ended in identical unattributable `Done`s. pnpm keeps the label on its status line; nub now does the same. The flag still hides the child's per-line prefix, which is what CI annotation matchers need. Closes #685 --- crates/nub-cli/src/cli.rs | 161 ++++++++-- crates/nub-cli/src/pm_engine/mod.rs | 10 +- crates/nub-cli/tests/integration.rs | 139 +++++++++ crates/nub-core/src/node/spawn.rs | 275 ++++++++++++++---- site/content/docs/runner/run.mdx | 24 +- .../monorepo-deps/packages/core/package.json | 2 +- .../monorepo-deps/packages/utils/package.json | 2 +- 7 files changed, 527 insertions(+), 86 deletions(-) diff --git a/crates/nub-cli/src/cli.rs b/crates/nub-cli/src/cli.rs index 74d8709cc..fce87a6d7 100644 --- a/crates/nub-cli/src/cli.rs +++ b/crates/nub-cli/src/cli.rs @@ -4,7 +4,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::env; use std::path::{Path, PathBuf}; use std::sync::OnceLock; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use anyhow::{Context, Result, bail}; #[cfg(feature = "compile")] @@ -39,10 +39,23 @@ static SILENT: AtomicBool = AtomicBool::new(false); static REPORTER_NDJSON: AtomicBool = AtomicBool::new(false); /// `--reporter-hide-prefix`: drop the `