fix(cli): aube update --latest preserves higher-than-latest prerelease pins#445
fix(cli): aube update --latest preserves higher-than-latest prerelease pins#445
Conversation
…e pins
Mirrors pnpm's #7436 regression guard: a manifest pin like
`"@pkg/has-prerelease": "3.0.0-rc.0"` shouldn't be silently
downgraded to `"2.0.0"` by `aube update --latest` just because the
registry's `latest` dist-tag is older than the user's pin.
Implementation: before building `resolver_manifest`, pre-fetch
packuments for every direct dep that's about to get its spec rewritten
to `latest`, parse the original pin and the registry's
`dist-tags.latest` as semver, and skip the rewrite for any dep where
the parsed pin is numerically newer. The resolver then sees the
original spec, resolves the original version, the lockfile keeps it,
and the manifest rewrite path leaves it alone (because resolved ==
original).
Only applied to bulk `update --latest` (no positional args). When the
user explicitly names a package — `aube update --latest <pkg>` —
they're opting in to whatever `latest` says, even when that downgrades
a prerelease (matches pnpm's `pnpm update <pkg>@latest` behavior).
Packuments come from the same cache the resolver uses moments later,
so the only cost when nothing's stale is a cheap revalidation (5-min
TTL + ETag).
Ports landed:
- pnpm/test/update.ts:615 ('update to latest without downgrading
already defined prerelease (#7436)') — bulk single-project variant.
pnpm also runs `update -r --latest` against the same single-project
fixture; aube's `-r` requires a workspace root, so the recursive
half is covered by the workspace ports below.
- pnpm/test/update.ts:728 ('update to latest recursive workspace
(outdated, updated, prerelease, outdated)') — 4-project mix.
- pnpm/test/update.ts:807 ('update to latest recursive workspace
(prerelease, outdated)') — 2-project mix.
16/22 of pnpm/test/update.ts ported (was 13/22). Drops the
prerelease-preservation divergence from PNPM_TEST_IMPORT.md.
Also notes upstream that `pnpm/test/recursive/update.ts` shrank to a
single `test.skip` upstream — pnpm itself doesn't currently pass the
only test in the file (per-project `packageConfigs.savePrefix` /
`saveExact` doesn't reach `recursive update --latest`). Nothing
portable today; re-evaluate when pnpm un-skips.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR fixes a real regression in Confidence Score: 5/5Safe to merge; the core logic is correct, both prior review concerns are resolved, and the new tests pass. No P0 or P1 findings. The single P2 (task-panic path lacks a tracing::warn) is a minor observability gap, not a correctness issue. The fix is well-scoped to bulk update --latest with no positional args, the semver comparison is correct, and the test coverage directly exercises the pnpm parity cases. No files require special attention beyond the minor P2 in update.rs. Important Files Changed
Reviews (2): Last reviewed commit: "fix(cli): warn instead of silently faili..." | Re-trigger Greptile |
…n guard Two greptile P2 callouts on update.rs: 1. The pre-fetch loop was silently swallowing packument fetch + parse errors (`.ok()?`), so a transient registry blip during the prerelease-preservation guard would silently fall through to the rewrite path and downgrade the user's prerelease pin — exactly the bug this guard exists to prevent. Replace the swallow with a `tracing::warn!` for both the fetch failure and a non-semver `latest` dist-tag, then continue with the resolver path (which has its own retry/cache semantics). 2. Document `exact_pin_version`'s slice contract: the returned `&str` is the bare version (already stripped of `npm:`, the `<name>@` prefix, the optional `=`, and surrounding whitespace), suitable for `Version::parse` but NOT a round-trip back to the original specifier. Today's only caller hands it straight to `parse`, but future callers shouldn't be surprised by the shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Benchmark changesVersions:
Public ratios: warm installs vs Bun 4x -> 4x; warm installs vs pnpm 5x -> 9x.
2c90361 vs 60ff453 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex. |
Summary
Fixes a real bug uncovered while porting pnpm/test/update.ts:
aube update --latestwas silently downgrading user-pinned prereleases when the registry'slatestdist-tag was older than the pin. Pnpm guards against this via #7436; aube didn't, so a manifest like"@pkg/has-prerelease": "3.0.0-rc.0"got rewritten to"2.0.0"if the registry's latest was 2.0.0.This PR fixes the regression and ports the three pnpm tests it was blocking. Bumps pnpm/test/update.ts coverage from 13/22 → 16/22.
The fix
In
crates/aube/src/commands/update.rs, before buildingresolver_manifest, pre-fetch packuments for every direct dep that's about to get its spec rewritten tolatest. Parse the original pin and the registry'sdist-tags.latestas semver. Skip the rewrite for any dep where the parsed pin is numerically newer.The resolver then sees the original spec, resolves the original version, the lockfile keeps it, and the manifest rewrite path leaves it alone (because resolved == original).
Only applied to bulk
update --latest(no positional args). When the user explicitly names a package —aube update --latest <pkg>— they're opting in to whateverlatestsays, even when that downgrades a prerelease (matches pnpm'spnpm update <pkg>@latestbehavior, which is also tested at update.ts:659 and already ported).Packuments come from the same cache the resolver uses moments later, so the only cost when nothing's stale is a cheap revalidation (5-min TTL + ETag).
Ports
@testupdate to latest without downgrading already defined prerelease (#7436)(615)aube update --latest: keeps a higher-than-latest prerelease pinupdate to latest recursive workspace (outdated, updated, prerelease, outdated)(728)aube update -r --latest: prerelease workspace mix preserves higher pinsupdate to latest recursive workspace (prerelease, outdated)(807)aube update -r --latest: prerelease + outdated workspace mixpnpm test 615 also runs
update -r --latestagainst the same single-project fixture; aube's-rrequires a workspace root, so the recursive half is covered by the workspace ports above (728 and 807) rather than re-running it.Doc cleanup
While at it,
pnpm/test/recursive/update.ts— listed in PNPM_TEST_IMPORT.md as Phase 2 work with "5 tests, 2 dist-tag uses" — turns out to be 1test.skipupstream now (pnpm itself doesn't currently pass it: per-projectpackageConfigs.savePrefix/saveExactdoesn't reachrecursive update --latest). Updated the doc to reflect this — nothing portable today, re-evaluate when pnpm un-skips.Test plan
AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats test/pnpm_update.bats— 17/17 pass.AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats test/update.bats— 10/10 still pass (no regression).cargo test --workspace— all green.cargo fmt --checkcargo clippy --all-targets -- -D warnings🤖 Generated with Claude Code
Note
Medium Risk
Updates
update --latestbehavior to conditionally skip rewriting certain manifest pins based on live registrydist-tags.latest, adding concurrent packument fetches; this could impact update correctness/performance and introduces new network-failure edge cases.Overview
Fixes a regression where bulk
aube update --latestcould downgrade user-pinned prereleases when the registry’sdist-tags.latestis numerically lower.Before rewriting specifiers to
latest, the command now pre-fetches packuments for targeted direct deps, detects exact-version pins (includingnpm:alias forms), and skips thelatestrewrite when the pinned version parses higher thandist-tags.latest(bulk updates only; explicitupdate --latest <pkg>keeps the downgrade behavior).Adds three new
pnpm_update.batstests covering prerelease preservation in single-project and recursive workspace updates, and updatesPNPM_TEST_IMPORT.mdto reflect the increased ported coverage and the correctedpnpm/test/recursive/update.tsstatus.Reviewed by Cursor Bugbot for commit 2c90361. Bugbot is set up for automated code reviews on this repo. Configure here.