Repository navigation
fix(util): hand pacote a real npm so git plugins with a build step install - #62817
JoshRob297 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
This fixes the reported failure when running from source. On the base, a git+file:// plugin with a prepare script fails with git dep preparation failed. With this PR it installs when npm is on PATH. Without npm on PATH it still fails with the same error. I did not test a compiled binary or Windows. test/npm.test.ts passes.
Public API: unchanged. Npm.resolveNpmBin is a new export of the internal @opencode/util package.
Concerns:
- Scripts now run despite
ignoreScripts: true. Once a working npm is found, pacote's Git preparation runsnpm install --forceinside the clone without--ignore-scripts. That runs the plugin's ownpreinstall/install/postinstall/preparescripts and its dependencies' install scripts. I confirmed it: apreparescript wrote a file into the installed plugin, even thoughNpm.reifypassesignoreScripts: true. Upstream pacote 22 goes the other way and skips this step whenignoreScriptsis set, which is what #60201 backports. Whether Git plugins may run code during install is a policy decision for maintainers. If they may, it should be deliberate and visible, not a side effect of the binary lookup. - Returning a
.jspath is unsafe (inline). - The fallback stays silent. With no npm on
PATH, or on Windows, where barenpmcannot be spawned without a shell (#59504), users still get the baregit dep preparation failedwith no hint that npm is required.
| // into Bun's embedded filesystem, so the child was opencode itself — which runs its | ||
| // own CLI and never that script — and every Git plugin with a build step died with | ||
| // "git dep preparation failed". Only trust a path that exists outside the bundle. | ||
| const exec = env.npm_execpath |
There was a problem hiding this comment.
pacote runs any npmBin ending in .js as process.execPath <npmBin> .... In the compiled binary, process.execPath is opencode itself. So when opencode is started from an npm run script, npm_execpath is a real, existing npm-cli.js, this branch returns it, and the binary re-executes itself exactly as before. A .js path is only safe when process.execPath is node. Always returning "npm" would avoid this. It would also make the $bunfs check, the existsSync injection and the new unit test unnecessary, shrinking the change to a single npmBin: "npm".
|
Typecheck, since the PR body only claimed the test run:
Confirmed that |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
…and say what a git install costs
|
Thanks — all three are fair, and two are fixed in the branch. 1. Scripts run despite 2. Returning a 3. The fallback stays silent. Fixed the dead end: when the preparation fails and no npm is on
|
There was a problem hiding this comment.
The new commits keep the fix working from source. With npm on PATH, a git+file:// plugin with a prepare script installs. Without npm, the install now fails with a clear hint instead of the bare git dep preparation failed. test/npm.test.ts passes and packages/util type-checks.
Public API: unchanged. npmOnPath and scriptCapable are new exports of the internal @opencode/util package.
The new check only partly fixes the self-re-run problem from my earlier comment: it still misses the Node build (inline). The new install-scripts warning only goes to the log file and fires for every Git plugin, so whether Git plugins may run install scripts still needs a maintainer's decision.
| * too, compiled or not. | ||
| */ | ||
| export function scriptCapable() { | ||
| if (typeof Bun !== "undefined") return Bun.embeddedFiles.length === 0 |
There was a problem hiding this comment.
This still misses the Node build. The release also ships a Node single-executable build (packages/cli/script/build-node.ts, the build-node job in release.yml). There Bun is undefined and process.versions.node is set, so this returns true. But process.execPath is the opencode binary, which runs its own entry point and ignores the script argument. So when npm_execpath is a real npm-cli.js (opencode started from npm run), the Git preparation re-executes opencode exactly as before. For Bun, embeddedFiles.length === 0 only works because the release build happens to embed files. It does not tell you whether this is a compiled binary. Always passing npmBin: "npm" avoids the whole question and makes scriptCapable and the .js branch unnecessary.
|
You're right about the Node build, and it makes the
So I took the suggestion in both of your comments:
Verified on
That leaves the policy question from your first review, which I'm not going to decide in a PR: with a working npm, pacote's Git preparation runs the plugin's install scripts even though |
There was a problem hiding this comment.
The latest commit fixes both earlier points about the .js npm path. pacote now always gets the plain npm name, so neither the Bun binary nor the Node single-executable build can start opencode again in place of npm. The repro from source passes: a git+file:// plugin with a prepare script installs. test/npm.test.ts passes and packages/util type-checks.
Public API: unchanged. resolveNpmBin and npmOnPath are new exports of the internal @opencode/util package.
One small follow-up: the wrapper function and its test can be dropped (inline).
| * every Git plugin with a build step. Handing pacote the bare name lets the system npm do | ||
| * the work, and `npmOnPath()` turns a missing npm into a readable error. | ||
| */ | ||
| export function resolveNpmBin() { |
There was a problem hiding this comment.
Now that this always returns "npm", the exported function and its Npm.resolveNpmBin test add nothing; the test only checks a constant. Setting npmBin: "npm" directly in reify, with this explanation as a comment there, keeps the change minimal. The test can be dropped.
Issue for this PR
Closes #49704
Type of change
What does this PR do?
opencode plugin add github:...fails withgit dep preparation failedfor any Git dependency that builds itself inprepare/build.pacote builds a Git dependency by running
npm installinside the clone, and takes the binary fromnpmBin. When that name ends in.js,pacote/lib/util/npm.jsspawnsprocess.execPathwith the script as its first argument. In the compiled binarynpm_execpathis/$bunfs/bin/npm-cli.js, so the child process is opencode itself: it runs its own CLI, never that script, and nothing gets installed.strace -f -e trace=execveover the command shows 23gitexecs, zeronpmexecs, and one re-exec of the opencode binary with thenpm-cli.jsargument. No npm debug log is written, which fits npm never running.The change gives pacote a real npm:
resolveNpmBin()ignores annpm_execpaththat lives inside the bundle or does not exist, and returns"npm", which pacote resolves throughPATH. Arborist forwards the option. Machines without npm onPATHkeep the current behaviour.I did not build the compiled binary to test this end to end. The failure and the fix were reproduced at the pacote/arborist level, which is where the defect lives.
Related: #60201 skips the preparation instead. That removes the error but leaves packages that build in
prepareinstalled without their built entry point — measured here:dist/absent withignoreScripts: true, present with a real npm.How did you verify your code works?
bun test test/npm.test.tsinpackages/core: 19 pass, including the newNpm.resolveNpmBincase.bun run typecheckfrom the repository root: 33 tasks, no type errors.[email protected]:npmBin: "/$bunfs/bin/npm-cli.js"→git dep preparation failed; the same call withnpmBin: "npm"gets past preparation.numman-ali/opencode-openai-codex-auth,griffinmartin/opencode-claude-auth,tickernelz/opencode-mem,slkiser/opencode-quota.Checklist
Added by the opencode bot (AI-generated).
Related: #59504, #60201