feat: detect uv, pnpm and bun tool updates - #44
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chain is now brew → go → cargo → pipx → uv → pnpm → bun → npm. pnpm
and bun sit before npm because both keep their globals behind
node_modules paths: a bun global resolved to `npm install -g <pkg>`,
which installs a duplicate under npm's prefix while the bun copy keeps
shadowing it on PATH. uv goes next to pipx — the same technique one
ecosystem over.
uv reads <UV_TOOL_DIR>/<pkg>/bin/<bin> and yields `uv tool upgrade
<pkg>`; bun reads <BUN_INSTALL>/install/global/node_modules/<pkg> and
yields `bun add -g <pkg>`. pnpm needs a second signal: its globals are
not symlinks at all but cmd-shim shell scripts, so the package name
comes from the `# cmd-shim-target=` line Detect reads off the binary,
with the resolved path as the legacy-layout fallback. `add -g` rather
than `update -g` for both, since update honours the range saved at
install time and can refuse the major bump the card is offering.
Two npm-side consequences of the pnpm layout: npmPackage skips a
post-node_modules segment starting with a dot (.pnpm is the virtual
store, .bin the shim dir) and keeps scanning, and the npm step refuses
any path through /node_modules/.pnpm/, where offering `npm install -g`
would be the duplicate install again — the chain degrades to
ErrUnknownManager and the update_cmd hint instead.
managerDirs carries all five path-convention roots, cargo and pipx
included, and resolveManagerDirs expands symlinks in each. Detect
matches these roots against a symlink-resolved binary path, so a
relocated ~/.bun, a home on a secondary volume or /home under autofs
missed silently, and bun and legacy pnpm globals then fell through to
npm — the misdetection above, back again. Carrying cargo/pipx there also
takes homeDir() out of detectFromPath, so its "no I/O, no environment"
contract holds literally. The empty-root check moved into
underDir/segmentUnder, where it belongs: filepath.Rel("", "bin/exa")
succeeds, so a relative path reads as living under every disabled root,
and the guard was six hand-copied copies of one convention.
Shim reads reject a file over the 8 KiB cap instead of parsing it
truncated. A cut inside the marker line shortens the path, and
.../node_modules/typescript shortened to .../node_modules/types yields
types, a real package on npm — a confidently wrong `pnpm add -g` where
the rest of the chain degrades honestly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
npmPackage returned the scope itself when nothing followed it, so .../node_modules/@angular yielded `pnpm add -g @angular` — a wrong command where the chain owes an honest miss. Reachable in practice: the pnpm step feeds npmPackage a cmd-shim target, which is file content nothing validated. Also correct the README. It claimed a layout no check recognises always degrades to the update_cmd hint, "never a wrong command". That is not true and this branch is where it stops being true: pnpm and bun globals sit behind node_modules paths, so an unresolved root still reads as a plain npm install and the offer becomes `npm install -g <pkg>`. Say so, and say which env vars keep it from happening. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds uv, pnpm and bun to the update-detection chain, requested after the reddit announcement. Chain is now
brew → go → cargo → pipx → uv → pnpm → bun → npm.Along the way it fixes a misdetection that was already live: bun globals resolve into
install/global/node_modules/<pkg>/…, so the npm step claimed them and offerednpm install -g <pkg>— a duplicate copy under npm's prefix while the bun one keeps shadowing it onPATH.<UV_TOOL_DIR>/<pkg>/bin/<bin>→uv tool upgrade <pkg>EvalSymlinksresolves them to themselves and the path names nothing. The package comes from the# cmd-shim-target=lineDetectreads off the binary (bounded, only under$PNPM_HOME), with the resolved path as the legacy-layout fallback →pnpm add -g <pkg><BUN_INSTALL>/install/global/node_modules/<pkg>→bun add -g <pkg>pnpm and bun go before npm: both layouts carry
node_modulessegments npm claims on sight.add -grather thanupdate -g, sinceupdatehonours the range saved at install time and can refuse the major bump the card is offering.npmPackagenow skips.pnpm/.binservice segments, and the npm step refuses paths through/node_modules/.pnpm/— offeringnpm install -gthere is the duplicate install again, so the chain degrades toErrUnknownManager+ theupdate_cmdhint.Found in review
Three defects in the change itself, each measured before fixing:
# cmd-shim-target=line shortens the path, and…/node_modules/typescriptshortened to…/node_modules/typesyieldstypes— a real package on npm, so the chain would have offered a confidently wrongpnpm add -g types. Now readscap+1and rejects an overrunning file whole (thegetReadmeidiom).~/.bun, a home on a secondary volume or/homeunder autofs missed silently. Measured across all five layouts: uv and pnpm shim/store degraded honestly, but bun and legacy pnpm fell back tonpm install -g <pkg>.resolveManagerDirsnow expands symlinks in every root, andmanagerDirsabsorbedcargoBin/pipxVenvsso the fix is symmetrical — which also takeshomeDir()out ofdetectFromPath, making its "no I/O" contract literal.!= ""root guard was six hand-copied copies of one convention, one omission away from claiming any relative path (filepath.Rel("", "bin/exa")succeeds). Moved intounderDir/segmentUnder.Not done, deliberately:
$CARGO_HOME/$PIPX_HOMEare still not consulted. Honouring them changes behaviour for anyone who sets them and belongs in its own commit.Checks
go build/go vet/go test -race ./...(10 packages) /golangci-lint— greenGOOS=windows(build + vet) andGOOS=linux— greeninternal/updatercoverage 85.0% → 86.8%; every new function at 100%, the remaining gaps are pre-existing (brewPrefix,cargoCrate,runProbe)UV_TOOL_DIR=<tmp> uv tool install cowsay→Detectyieldsuv tool upgrade cowsay. Temporary test and tmp dirs removed afterwards,$HOMEuntouchedManager = "npm", want "bun"; dropping the symlink expansion, the cap guard, one field in the expansion loop, or either empty-dir guard each fails a specific testKnown limits (documented in README)
.cmdshims keepkit does not parse — pre-existing npm limitation, inheritedbun upgrade,pnpm self-update); those tools update themselves🤖 Generated with Claude Code