From 49b52ad9b47b9db85d368729e1c85969b03c36e6 Mon Sep 17 00:00:00 2001 From: Even Stensberg Date: Wed, 2 Sep 2026 17:32:02 +0200 Subject: [PATCH 1/2] test: support execa v10 subprocess API Since execa v10 the subprocess is a plain promise instead of a `ChildProcess` instance, so Node.js APIs like `.on()` are only available through `subprocess.nodeChildProcess`. Co-Authored-By: Claude Opus 5 --- smoketests/helpers.js | 20 ++++++++++++-------- test/utils/test-utils.js | 10 ++++++++++ test/watch/stdin/stdin.test.js | 18 +++++++++--------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/smoketests/helpers.js b/smoketests/helpers.js index 3d410d2583c..ee37254ee0e 100644 --- a/smoketests/helpers.js +++ b/smoketests/helpers.js @@ -19,6 +19,10 @@ const swapPkgName = (current, isSubPackage = false) => { fs.renameSync(getPkgPath(current, isSubPackage), getPkgPath(next, isSubPackage)); }; +// Since execa v10 the subprocess is a plain promise, Node.js `ChildProcess` APIs +// like `.on()` are only available through `subprocess.nodeChildProcess` +const getChildProcess = (proc) => proc.nodeChildProcess ?? proc; + const CLI_ENTRY_PATH = path.resolve(ROOT_PATH, "./packages/webpack-cli/bin/cli.js"); const runTest = async (pkg, cliArgs = [], logMessage = undefined, isSubPackage = false) => { @@ -70,13 +74,13 @@ const runTest = async (pkg, cliArgs = [], logMessage = undefined, isSubPackage = } }); - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { swapPkgName(`.${pkg}`, isSubPackage); clearTimeout(timeout); resolve(hasPassed); }); - proc.on("error", () => { + getChildProcess(proc).on("error", () => { swapPkgName(`.${pkg}`, isSubPackage); clearTimeout(timeout); resolve(false); @@ -123,13 +127,13 @@ const runTestStdout = async ({ packageName, cliArgs, logMessage, isSubPackage, c console.log(` stderr: ${data}`); }); - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { swapPkgName(`.${packageName}`, isSubPackage); clearTimeout(timeout); resolve(hasPassed); }); - proc.on("error", () => { + getChildProcess(proc).on("error", () => { swapPkgName(`.${packageName}`, isSubPackage); clearTimeout(timeout); resolve(false); @@ -187,13 +191,13 @@ const runTestStdoutWithInput = async ({ console.log(` stderr: ${data}`); }); - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { swapPkgName(`.${packageName}`, isSubPackage); clearTimeout(timeout); resolve(hasPassed); }); - proc.on("error", () => { + getChildProcess(proc).on("error", () => { swapPkgName(`.${packageName}`, isSubPackage); clearTimeout(timeout); resolve(false); @@ -250,13 +254,13 @@ const runTestWithHelp = async (pkg, cliArgs = [], logMessage = undefined, isSubP } }); - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { swapPkgName(`.${pkg}`, isSubPackage); clearTimeout(timeout); resolve(hasPassed); }); - proc.on("error", () => { + getChildProcess(proc).on("error", () => { swapPkgName(`.${pkg}`, isSubPackage); clearTimeout(timeout); resolve(false); diff --git a/test/utils/test-utils.js b/test/utils/test-utils.js index 51b34f756bf..3dfbf2102a4 100644 --- a/test/utils/test-utils.js +++ b/test/utils/test-utils.js @@ -20,6 +20,15 @@ const hyphenToUpperCase = (name) => { return name.replaceAll(/-([a-z])/g, (g) => g[1].toUpperCase()); }; +// Since execa v10 the subprocess is a plain promise, Node.js `ChildProcess` APIs +// like `.on()` are only available through `subprocess.nodeChildProcess` +/** + * Get the underlying Node.js child process of an execa subprocess. + * @param {import("execa").ResultPromise} proc execa subprocess + * @returns {import("node:child_process").ChildProcess} child process + */ +const getChildProcess = (proc) => proc.nodeChildProcess ?? proc; + const processKill = (process) => { if (isWindows) { exec(`taskkill /pid ${process.pid} /T /F`); @@ -389,6 +398,7 @@ const uniqueDirectoryForTest = async () => { }; module.exports = { + getChildProcess, getWebpackCliArguments, hyphenToUpperCase, isWindows, diff --git a/test/watch/stdin/stdin.test.js b/test/watch/stdin/stdin.test.js index ac2d7433930..194ff1cc9c9 100644 --- a/test/watch/stdin/stdin.test.js +++ b/test/watch/stdin/stdin.test.js @@ -1,4 +1,4 @@ -const { processKill, runWatch } = require("../../utils/test-utils"); +const { getChildProcess, processKill, runWatch } = require("../../utils/test-utils"); describe("--watch-options-stdin", () => { it('should stop the process when stdin ends using "--watch" and "--watch-options-stdin" options', async () => { @@ -6,7 +6,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["--watch", "--watch-options-stdin"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -24,7 +24,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["watch", "--watch-options-stdin"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -42,7 +42,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["--config", "./watch.config.js"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -60,7 +60,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["--config", "./multi-watch.config.js"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -78,7 +78,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["serve", "--watch-options-stdin"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -96,7 +96,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["serve", "--stdin"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -114,7 +114,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["serve", "--config", "./serve.config.js"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); @@ -132,7 +132,7 @@ describe("--watch-options-stdin", () => { await runWatch(__dirname, ["--config", "./multi-watch.config.js"], { handler: (proc) => { - proc.on("exit", () => { + getChildProcess(proc).on("exit", () => { expect(semaphore).toBe(true); processKill(proc); From 824823576ee863eb3641788bb7367fc995f60530 Mon Sep 17 00:00:00 2001 From: Even Stensberg Date: Wed, 2 Sep 2026 17:33:31 +0200 Subject: [PATCH 2/2] chore: bump exca --- package-lock.json | 34 +++++++++++++++++++++++++--------- package.json | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ddebfa27b3..1afd37eb887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "del-cli": "^7.0.0", "eslint": "^10.9.1", "eslint-config-webpack": "^4.9.6", - "execa": "^9.6.1", + "execa": "^10.0.1", "get-port": "^7.2.0", "husky": "^9.1.7", "jest": "^30.4.2", @@ -11447,27 +11447,27 @@ } }, "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-10.0.1.tgz", + "integrity": "sha512-ge98qjkRK4IB7tL7Ju/6qmm5LHoH1eEMt5FNZrz3f4UIYhF28lggX20z3FaX1sgc67msLEn0N0BscOs29iuwyw==", "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", "figures": "^6.1.0", - "get-stream": "^9.0.0", + "get-stream": "^9.0.1", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", + "pretty-ms": "^9.3.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" + "which-command": "^0.1.0", + "yoctocolors": "^2.1.2" }, "engines": { - "node": "^18.19.0 || >=20.5.0" + "node": ">=22" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -20945,6 +20945,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-command": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/which-command/-/which-command-0.1.0.tgz", + "integrity": "sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==", + "dev": true, + "license": "MIT", + "bin": { + "which-command": "cli.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/which-command?sponsor=1" + } + }, "node_modules/which-typed-array": { "version": "1.1.22", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", diff --git a/package.json b/package.json index 3d515e67e01..c3744db6855 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "del-cli": "^7.0.0", "eslint": "^10.9.1", "eslint-config-webpack": "^4.9.6", - "execa": "^9.6.1", + "execa": "^10.0.1", "get-port": "^7.2.0", "husky": "^9.1.7", "jest": "^30.4.2",