Repository navigation
chore: replace import-local and cross-spawn with Node.js built-ins - #4889
Conversation
The CLI used one function of each package, both are a few lines with node:module and node:child_process. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
🦋 Changeset detectedLatest commit: 7c0b979 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
This PR is packaged and the instant preview is available (3942417). Install it locally:
npm i -D webpack-cli@https://pkg.pr.new/webpack-cli@3942417
yarn add -D webpack-cli@https://pkg.pr.new/webpack-cli@3942417
pnpm add -D webpack-cli@https://pkg.pr.new/webpack-cli@3942417 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4889 +/- ##
==========================================
+ Coverage 95.08% 95.24% +0.16%
==========================================
Files 14 14
Lines 5874 5950 +76
Branches 894 915 +21
==========================================
+ Hits 5585 5667 +82
+ Misses 288 282 -6
Partials 1 1
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the
Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review.
|
|
The generated project installs the published Generated by Claude Code |
webpack-cli@7.3.0 depends on rechoir 1, which requires Node.js 22.15, and Yarn 1 fails the install on Node.js 20. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

Minor · Preserve cross-spawn’s .cmd shim escaping on Windows. · webpack-cli.ts:646-663
packages/webpack-cli/src/webpack-cli.ts:646-663
Minor · Functional Correctness · Quick win Preserve cross-spawn’s
.cmdshim escaping on Windows.On Windows, when
npm,yarn, orpnpmresolves through anode_modules/.bin/*.cmdshim, the helper escapes arguments once beforecmd.exeparses the joined command. The shim then parses the forwarded arguments again. Cross-spawn applies a second metacharacter escape for this case.Without that second escape, a package spec containing
&,", or another cmd metacharacter can be split or corrupted. The package installation can then fail or receive the wrong package spec.Use cross-spawn’s command resolution and conditional second escape for
.cmdshims. Do not apply the second escape unconditionally.

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
84ff2eef-9034-4e5a-9642-54e6ad215bb3
Files selected for processing: 1-
test/create-webpack-app/test.utils.js
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.
-
Run .exe and .com files without a shell and escape the arguments twice for the .cmd shims of packages, they pass them to another shell. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
|
The finding about
The escaping gives the same output as Generated by Claude Code |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

Minor · Package-manager probes always succeed, even when the tool is missing. · webpack-cli.ts:736
packages/webpack-cli/src/webpack-cli.ts:736
Minor · Functional Correctness · Quick win Package-manager probes always succeed, even when the tool is missing.
spawnSyncdoes not throw when the executable is missing. It returns a result object, and that object is always truthy. Theif (await this.#spawnSync(...))checks therefore pass on the first probe. The comments above them say an error is thrown, but none is.
- When no lockfile exists,
getDefaultPackageManagerreturns"npm"even if npm is not installed.- The
yarnandpnpmprobes and the "No package manager found." exit are never reached.On non-Windows platforms, a missing binary shows up as
error.code === "ENOENT". On the Windows shell path, a missing command shows up as a non-zerostatus. A start failure withEACCESorEPERMmeans the binary exists but could not be launched, so do not report it as "not installed".
cross-spawnalso returned a result object, so this behavior existed before. This PR touches these exact lines. Add a helper that checkserrorandstatus, then use it in all three probes. Updatetest/api/get-default-package-manager.test.jsto match.Proposed fix (npm probe shown, repeat for yarn and pnpm)
- if (await this.#spawnSync("npm", ["--version"])) { + const result = await this.#spawnSync("npm", ["--version"]); + + if (!result.error && result.status === 0) { return "npm"; }Based on learnings: check that a spawn failure has code
ENOENTbefore inferring "not installed". Do not treatEACCES/EPERMas missing.Also applies to: 746-746, 756-756
Source: Learnings

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
08bb82f7-a427-43f1-ac04-1ace33b0e8a1
Files selected for processing: 3-
.cspell.jsonpackages/webpack-cli/src/webpack-cli.tstest/api/install-package.test.js
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.
-
spawnSync returns a result instead of throwing, so the CLI always chose npm without a lock file, even when npm was missing. Check the error and the exit status, so yarn and pnpm are found too. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
|
The finding that the package-manager probes always succeed ( The tests now cover falling back to yarn and to pnpm, and a missing package manager is simulated with an Generated by Claude Code |
getDefaultPackageManager and installPackage are async, so spawn the commands and look up their files on Windows without blocking. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
2b450a86-c283-456e-82ce-4675b2b210ff
Files selected for processing: 4-
.changeset/detect-missing-package-managers.mdpackages/webpack-cli/src/webpack-cli.tstest/api/get-default-package-manager.test.jstest/api/install-package.test.js
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.
-
The result of the package manager was ignored, so the CLI continued as if the package was installed. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
webpack-cliused one function from each package. Both are now short code on top ofnode:moduleandnode:child_process. This removes up to 16 packages from installs:import-local,pkg-dir,find-up,locate-path,p-locate,p-limit,p-try,path-exists,resolve-cwdandresolve-from, pluscross-spawn,path-key,shebang-command,shebang-regex,whichandisexe.import-local→runLocalCLI()inbin/cli.jsIt keeps the original logic:
webpack-cli/bin/cli.jsfrom the real path of the current directory. This is whatresolve-cwddid. A missing directory falls back to the resolved path, and other errors skip the lookup.<cwd>/node_modules. The Windows checks for case and for different drives are kept.WEBPACK_CLI_SKIP_IMPORT_LOCALstill skips it.The package name and
bin/cli.jspath are fixed instead of found throughpkg-dir, because this file is alwayswebpack-cli/bin/cli.js.cross-spawn→#spawn()getDefaultPackageManagerandinstallPackageare async, so#spawnruns commands with the asyncchild_process.spawnand resolves with the exit status, or with the error when the command fails to start. It doesn't block the waycross-spawn'ssyncdid.cross-spawndid.cross-spawnandwhich:PATH, with the extensions fromPATHEXT, usingfs.promises.stat..exeand.comfiles run directly, without a shell.npm.cmd, run throughcmd.exe /d /s /c "…"(shell: true), withcross-spawn's argument escaping..cmdshims innode_modules/.binpass the arguments on to another shell, so the arguments are escaped twice.cross-spawn's own escape function on 11 inputs, both single and double, and all 22 results were identical. The inputs included quotes, backslashes and%,&and^.shell: true.Not copied:
ENOENTerror:cross-spawnadds one on Windows when a command is missing. The new code doesn't need it, because it treats a non-zero exit status as missing (see below).cross-spawnlooks these up as a last resort and reads their shebang. Package managers always have a.cmdor.exefile on Windows, so this case doesn't come up here.Fix: detect missing package managers
spawnSync(andcross-spawnbefore it) returned a result instead of throwing, sogetDefaultPackageManageralways chosenpmwhen there was no lock file, even ifnpmwasn't installed. Theyarnandpnpmfallbacks and the "No package manager found." exit never ran. A package manager now counts only when its--versionstarts without an error and exits with status0. The probes ignore the output (stdio: "ignore"). This change has its own patch changeset.Checked with real processes on Linux. With no lock file:
npmavailable, it picksnpm;pnpmonPATH, it pickspnpm;Tests
import-local: the test mocked the package in Jest, but the CLI runs in a child process, so the mock never applied. It now installs a fake localwebpack-cliin a temporary directory and checks that the CLI runs it, and thatWEBPACK_CLI_SKIP_IMPORT_LOCALskips it. The same tests also pass with the originalbin/cli.js.cross-spawn: the package-manager tests now mocknode:child_processspawnwith fake processes that exit or fail to start. They check the shell command on Windows. New tests run the Windows branch on any OS and cover a.cmdfile, anode_modules/.binshim and an.exe, plus the fallbacks to yarn and pnpm.create-webpack-app: the tests now setYARN_IGNORE_ENGINES. The publishedwebpack-cli@7.3.0depends onrechoir@1, which requires Node.js 22.15, so Yarn 1 failed to install it on Node.js 20.🤖 Generated with Claude Code
https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
Summary by CodeRabbit