From e217733db578c01071fd740124fe0cb0ccf83522 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 10:48:56 +0000 Subject: [PATCH 1/2] chore: upgrade bufout to v1.0.0 and drop defaultMaxListeners bumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bufout v1.0.0 keeps the number of listeners on the process and on the output streams constant regardless of how many children are spawned concurrently: a single shared exit/SIGINT listener is attached only while children are running, and every child pipes into one shared pass-through per destination stream. That removes the reason the CLIs raised EventEmitter.defaultMaxListeners to 100, so those assignments (and the now-unused node:events / node:stream imports) are gone and Node's default limit applies again, restoring the leak warning it exists to give. Verified with 80 concurrent children in both "inherit" and "buffered" mode, plus the SpawnFailure flush path, at the default limit of 10: no MaxListenersExceededWarning, and process listener counts return to zero. The public API is unchanged from 0.3.x — the major bump reflects the 1.0.0 milestone, not a breaking change to spawn/SpawnFailure. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BAjP89a9VA9EtQsVBxGcto --- .changeset/bufout-one-point-oh.md | 16 ++++++++++++++++ packages/cli-utils/package.json | 2 +- packages/cmake-rn/src/cli.ts | 4 ---- packages/ferric/src/run.ts | 5 ----- packages/host/src/node/cli/program.ts | 4 ---- pnpm-lock.yaml | 10 +++++----- 6 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 .changeset/bufout-one-point-oh.md diff --git a/.changeset/bufout-one-point-oh.md b/.changeset/bufout-one-point-oh.md new file mode 100644 index 00000000..83ec2f32 --- /dev/null +++ b/.changeset/bufout-one-point-oh.md @@ -0,0 +1,16 @@ +--- +"@react-native-node-api/cli-utils": patch +"react-native-node-api": patch +"cmake-rn": patch +"ferric-cli": patch +--- + +Upgrade `bufout` to v1.0.0, which keeps the number of listeners on the process +and the output streams constant regardless of how many children are spawned +concurrently: a single shared `exit`/`SIGINT` listener is attached only while +children are running, and every child pipes into one shared pass-through per +destination stream. + +That removes the reason for the CLIs to raise `EventEmitter.defaultMaxListeners` +to 100, so those assignments are gone and Node's default limit again applies — +restoring the leak warning it exists to give. diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json index 25cbaeef..f6fb1122 100644 --- a/packages/cli-utils/package.json +++ b/packages/cli-utils/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@commander-js/extra-typings": "^14.0.0", - "bufout": "^0.3.2", + "bufout": "^1.0.0", "chalk": "^5.4.1", "commander": "^14.0.1", "ora": "^8.2.0", diff --git a/packages/cmake-rn/src/cli.ts b/packages/cmake-rn/src/cli.ts index b94e2fa2..a2b07e74 100644 --- a/packages/cmake-rn/src/cli.ts +++ b/packages/cmake-rn/src/cli.ts @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import path from "node:path"; import fs from "node:fs"; -import { EventEmitter } from "node:events"; import { chalk, @@ -22,9 +21,6 @@ import { import { Platform } from "./platforms/types.js"; import { getCcachePath } from "./ccache.js"; -// We're attaching a lot of listeners when spawning in parallel -EventEmitter.defaultMaxListeners = 100; - const verboseOption = new Option( "--verbose", "Print more output during the build", diff --git a/packages/ferric/src/run.ts b/packages/ferric/src/run.ts index 01311284..7b14eb8b 100644 --- a/packages/ferric/src/run.ts +++ b/packages/ferric/src/run.ts @@ -1,8 +1,3 @@ -import EventEmitter from "node:events"; - import { program } from "./program.js"; -// We're attaching a lot of listeners when spawning in parallel -EventEmitter.defaultMaxListeners = 100; - program.parseAsync(process.argv).catch(console.error); diff --git a/packages/host/src/node/cli/program.ts b/packages/host/src/node/cli/program.ts index e3c63904..77d8b8f4 100644 --- a/packages/host/src/node/cli/program.ts +++ b/packages/host/src/node/cli/program.ts @@ -1,6 +1,5 @@ import assert from "node:assert/strict"; import path from "node:path"; -import { EventEmitter } from "node:stream"; import { Command, @@ -28,9 +27,6 @@ import { linkModules, pruneLinkedModules, ModuleLinker } from "./link-modules"; import { ensureXcodeBuildPhase, createAppleLinker } from "./apple"; import { linkAndroidDir } from "./android"; -// We're attaching a lot of listeners when spawning in parallel -EventEmitter.defaultMaxListeners = 100; - export const program = new Command("react-native-node-api").addCommand( vendorHermes, ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa6ec795..6a99748a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,8 +144,8 @@ importers: specifier: ^14.0.0 version: 14.0.0(commander@14.0.3) bufout: - specifier: ^0.3.2 - version: 0.3.4 + specifier: ^1.0.0 + version: 1.0.0 chalk: specifier: ^5.4.1 version: 5.6.2 @@ -2618,8 +2618,8 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bufout@0.3.4: - resolution: {integrity: sha512-m8iGxYUvWLdQ9CQ9Sjnmr8hJHlpXfRQn2CV3eI5b107MWQqAe/K/pqsCGmczkSy3r7E1HW5u5z86z2aBYbwwxQ==} + bufout@1.0.0: + resolution: {integrity: sha512-ZCFKJOWLZqZKitcDUTIsJucC9EhOiYl/wb7Fg1TfzlFz1FhjrykZAkn5Gb7JyIsW5ViZjDXL8tr2bXReM/u/XQ==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -7367,7 +7367,7 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bufout@0.3.4: {} + bufout@1.0.0: {} bytes@3.1.2: {} From debfe4cc7a1b9b367febcee1bcb672fd850db63a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 10:50:33 +0000 Subject: [PATCH 2/2] ci: trigger label-gated jobs The Check workflow only runs on opened/synchronize/reopened, so the Apple/Android/Ferric jobs gated on labels never evaluated the labels added after the pull request was opened. This empty commit fires a synchronize event so they run. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BAjP89a9VA9EtQsVBxGcto