Skip to content

test(install): port pnpm/test/update.ts (13/22)#438

Merged
jdx merged 5 commits intomainfrom
claude/clever-cori-73805b
May 1, 2026
Merged

test(install): port pnpm/test/update.ts (13/22)#438
jdx merged 5 commits intomainfrom
claude/clever-cori-73805b

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented Apr 30, 2026

Summary

Lands test/pnpm_update.bats — the first batch of pnpm/test/update.ts ports. 13/22 ported. Plus two synthesized fixtures and three small aube-side fixes that fell out of porting.

Ports

pnpm test (line) aube @test
update <dep> (14) aube update --latest <pkg>: bumps a single dep past its declared range
update --no-save (34) aube update --no-save: refreshes the lockfile, leaves package.json range alone
update --latest (143) aube update --latest: bumps prod deps, npm: aliases, and ranges
update --latest --save-exact (170) aube update --latest -E: rewrites manifest specs as exact pins
update --latest specific dep (197) aube update --latest <name>: bumps named deps, leaves others pinned
update --latest --prod (225) aube update --latest --prod: bumps prod deps, leaves devDeps pinned
recursive update --no-save (72) aube update -r --no-save: refreshes a workspace lockfile, leaves manifests alone
recursive update --no-shared-workspace-lockfile (118) aube update -r --no-shared-workspace-lockfile: writes a per-project lockfile
recursive update --latest specific no-shared (369) aube update -r --latest <name>: bumps named deps across workspace
recursive update --latest on shared lockfile (426) aube update -r --latest: bumps every workspace project's manifest
recursive update --latest --prod on shared lockfile (478) aube update -r --latest --prod: skips devDeps in workspace fanout
recursive update --latest specific shared (543) aube update -r --latest <name>: same shape as no-shared (per-project)
update with tag @latest will downgrade prerelease (659) aube update --latest <pkg>: downgrades prerelease to the latest dist-tag

Phase 0 fixtures mirrored

Both fixtures are pnpm-internal (don't exist on registry.npmjs.org), so tarballs are produced via npm pack on a minimal package.json and the packuments are hand-built.

  • @pnpm.e2e/has-prerelease at 1.0.0, 2.0.0, 3.0.0-rc.0 (latest=2.0.0, so the prerelease is "newer than latest").
  • @pnpm.e2e/qar at 100.0.0, 100.1.0 — used as the npm: alias target by the misc.ts:143/170/197/369/543 ports.

Aube-side fixes

Three small fixes landed alongside the ports (each under ~15 lines):

  1. update --latest rewrites npm: alias specifiers (update.rs). Previously the lockfile bumped but the manifest stayed at the original version. Aliased direct deps land in the lockfile graph with pkg.name == "<alias>" and pkg.alias_of == Some("<real>"); the version-lookup match accepted only real_name, so aliased entries fell through to continue and the manifest was never rewritten. Added a lookup_pkg helper that matches either name, and extended the filtered-existing snapshot to drop entries whose pkg.name matches the manifest key (alias) so the resolver doesn't keep the locked alias version under --latest.
  2. New --save-exact / -E flag on aube update mirroring pnpm update --save-exact. Pairs with --latest to drop the caret/tilde prefix on the rewritten specifier (manifest carries "1.2.3" instead of "^1.2.3").
  3. Recursive update silently skips projects that don't declare any of the named args. Without this, aube update -r --latest foo alias hard-errors on the first project that doesn't have alias in its package.json — pnpm's recursive update just filters the arg list per project. Fixes the fanout for the recursive update --latest <name> tests.

Divergences uncovered (documented in PNPM_TEST_IMPORT.md, not fixed here)

  • aube update <pkg>@<spec> not parsed. update.rs looks up the literal arg in package.json's deps map, so aube update foo@latest errors with not a dependency. Ports translate to aube update --latest <pkg>.
  • aube update --depth N is a no-op + plain aube update preserves indirect-dep versions from the lockfile. pnpm/test/update.ts:599 ('deep update') doesn't have an equivalent.
  • aube update <indirect-pkg> errors even though pnpm allows it (pnpm/test/update.ts:690).
  • aube update -r always writes per-project lockfiles, regardless of sharedWorkspaceLockfile. Per-project lockfile assertions stand in for shared-lockfile assertions.
  • Prerelease preservation across --latest. pnpm keeps a manifest pin like 3.0.0-rc.0 when the registry's latest is older (e.g. 2.0.0); aube downgrades. Affects 615, 728, 807.

Skipped

  • GitHub-shorthand support (kevva/is-negative) is its own feature (parser + git tarball fetcher + storage). The single is-negative assertion was dropped from the 143/170/197 ports — every other assertion in those tests stands. Tracked as a remaining gap in PNPM_TEST_IMPORT.md.
  • Tests 249 / 302 (recursive update --latest [--prod] on no-shared-lockfile projects) are subsumed by the shared-lockfile ports above — aube's update -r produces per-project lockfiles in both modes, so the two pnpm variants collapse.

Test plan

  • AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats test/pnpm_update.bats — 13/13 pass.
  • AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats test/update.bats test/add.bats — 35/35 still pass (no regression).
  • cargo test --workspace — all green, including cli_ordering_tests::test_cli_ordering.
  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • mise run renderaube.usage.kdl and docs/cli/update.md regenerated for the new -E / --save-exact flag.

🤖 Generated with Claude Code


Note

Medium Risk
Modifies aube update resolution and manifest-rewrite behavior (including alias handling and workspace fanout), which can affect dependency upgrades and lockfile contents across projects. Changes are well-covered by new end-to-end Bats tests but touch core package-update logic.

Overview
Ports a first batch of pnpm’s update integration tests into a new test/pnpm_update.bats, along with new local-registry fixtures for @pnpm.e2e/has-prerelease and @pnpm.e2e/qar.

Extends aube update to better match pnpm semantics: adds -E/--exact (--save-exact) to force exact pins when rewriting manifests under --latest, fixes --latest handling for npm: alias dependencies by matching/updating via the manifest key as well as the real package name, and makes recursive/workspace updates skip projects that don’t declare any of the requested package args instead of erroring. CLI usage/docs are regenerated to include the new flag.

Reviewed by Cursor Bugbot for commit 5806988. Bugbot is set up for automated code reviews on this repo. Configure here.

Lands [test/pnpm_update.bats](test/pnpm_update.bats) covering the
basic-update, --no-save, --latest --prod, and recursive update fanout
cases (single + multi-project). Updates [test/PNPM_TEST_IMPORT.md] with
the per-test status, the divergences uncovered while porting, and the
fixtures still needed (`@pnpm.e2e/qar`, `@pnpm.e2e/has-prerelease`)
for the remaining tests.

Divergences documented (not fixed in this PR):
- `aube update <pkg>@<spec>` is not parsed; ports use `--latest <pkg>`.
- `aube update --depth N` is a no-op and plain `aube update` preserves
  indirect-dep versions from the lockfile snapshot, so misc.ts:599
  ('deep update') doesn't translate.
- `aube update <indirect-pkg>` errors when the package isn't in
  package.json's dep map (update.rs:139-146), so misc.ts:690 doesn't
  translate either.
- `aube update -r` always writes per-project lockfiles, regardless of
  `sharedWorkspaceLockfile`. The shared-lockfile assertions on
  pnpm/test/update.ts:471-475 and :531-535 are dropped from the ports;
  per-project lockfile assertions stand in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e707eb6. Configure here.

Comment thread test/pnpm_update.bats Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

Ports 13 of 22 pnpm/test/update.ts tests to test/pnpm_update.bats and lands three small update.rs fixes that fell out of porting: a lookup_pkg helper for aliased direct deps, a new --save-exact/-E flag, and per-project arg filtering in run_filtered to silently skip projects that don't declare the named dep. The Rust logic is correct and the divergences from pnpm behaviour are clearly documented.

Confidence Score: 5/5

Safe to merge — only P2 test-completeness gaps, no logic errors in the production code.

All findings are P2 (missing lockfile assertions and a slightly misleading test name). The Rust changes are well-reasoned, internally consistent, and covered by the 13 passing bats tests plus the existing update.bats/add.bats regression suite.

test/pnpm_update.bats — two minor test-completeness gaps noted inline.

Important Files Changed

Filename Overview
crates/aube/src/commands/update.rs Three well-scoped fixes: lookup_pkg alias helper, manifest-key inclusion in filtered_existing, --save-exact/-E flag, and per-project arg filtering in run_filtered. Logic is correct and consistent with the surrounding code.
test/pnpm_update.bats 13 ported tests, well-structured with teardown and registry guards. Two minor gaps: the recursive --latest --prod test lacks lockfile assertions, and the --no-shared-workspace-lockfile test's .npmrc setting is a no-op in aube.
aube.usage.kdl Adds -E --exact --save-exact flag definition with correct help text; consistent with the Rust arg definition.
docs/cli/commands.json Auto-generated; correctly reflects the new --exact/--save-exact flag with short alias E.
docs/cli/update.md Auto-generated doc for the new -E --exact flag; content matches the Rust doc-comment.
test/PNPM_TEST_IMPORT.md Tracking doc updated with ported tests, divergences, and skipped cases; thorough and accurate.

Fix All in Claude Code

Reviews (4): Last reviewed commit: "fix(cli): aube update -r honors --prod/-..." | Re-trigger Greptile

Comment thread test/pnpm_update.bats
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Benchmark changes

Versions:

  • aube: 1.5.1 -> 1.5.2
  • pnpm: 11.0.2 -> 11.0.3

Public ratios: warm installs vs Bun 4x -> 4x; warm installs vs pnpm 5x -> 8x.

Benchmark aube bun pnpm
Fresh install (warm cache) 1021ms -> 306ms (-70%) 4134ms -> 1351ms (-67%) 4717ms -> 2325ms (-51%)
CI install (warm cache, GVS disabled) 2920ms -> 336ms (-88%) 3396ms -> 1387ms (-59%) 4864ms -> 2396ms (-51%)
CI install (cold cache, GVS disabled) 10801ms -> 4507ms (-58%) 10012ms -> 4684ms (-53%) 9722ms -> 4695ms (-52%)

5806988 vs 2ad5c54 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex.

Synthesizes the @pnpm.e2e/has-prerelease fixture (versions 1.0.0,
2.0.0, 3.0.0-rc.0) at test/registry/storage/@pnpm.e2e/has-prerelease/
— the package doesn't exist on registry.npmjs.org, so the tarballs
are produced via `npm pack` on a minimal manifest and the packument
is hand-built. Lands one new port that this fixture unblocks:

- pnpm/test/update.ts:659 ('update with tag @latest will downgrade
  prerelease') → `aube update --latest <pkg>` translation.

Three more has-prerelease tests (615, 728, 807) didn't translate
cleanly: aube's `update --latest` downgrades a manifest pin from a
newer prerelease (e.g. 3.0.0-rc.0) to the registry's `latest`
dist-tag (2.0.0) instead of preserving the prerelease as pnpm does.
Documented as a fourth divergence in PNPM_TEST_IMPORT.md.

8/22 ported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx changed the title test(install): port pnpm/test/update.ts (7/22) test(install): port pnpm/test/update.ts (8/22) Apr 30, 2026
jdx and others added 2 commits April 30, 2026 18:42
- Replace `refute grep` with `run grep; assert_failure` (cursor[bot]):
  `refute` accepts any non-zero exit, so `grep` failing because the
  lockfile is missing (exit 2) silently passed instead of failing the
  test. The `run; assert_failure` form keeps `$status`/`$output`
  inspectable and matches the pattern used elsewhere in the file.
- Add the missing `project/package.json` manifest assertion to the
  `--no-shared-workspace-lockfile` test (greptile[bot]): `--latest`
  is supposed to rewrite specifiers, so the test now verifies both
  the per-project lockfile location AND the manifest rewrite to
  `^100.1.0`, matching the assertion shape used in the other
  `--latest` tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors `@pnpm.e2e/qar` (100.0.0, 100.1.0) at
test/registry/storage/@pnpm.e2e/qar/ using the same hand-built packument
+ npm pack approach as @pnpm.e2e/has-prerelease, and ports the five
pnpm/test/update.ts tests this fixture unblocks. Three small aube-side
fixes landed alongside the ports to make them green:

1. update --latest now rewrites `npm:` alias specifiers in the manifest
   (previously: lockfile bumped, manifest stuck). Aliased direct deps
   land in the lockfile graph with `pkg.name == "<alias>"` and
   `pkg.alias_of == Some("<real>")`; the version-lookup match in
   update.rs accepted only `real_name`, so aliased entries fell through
   to `continue` and the manifest was never rewritten. Added a
   `lookup_pkg` helper that matches either name, and extended the
   filtered-existing snapshot to drop entries whose `pkg.name` matches
   the manifest key (alias) too — without this the resolver kept the
   locked alias version under --latest.

2. New `--save-exact` / `-E` flag on `aube update`, mirroring
   `pnpm update --save-exact`. Pairs with --latest to drop the
   caret/tilde prefix on the rewritten specifier (manifest carries
   `"1.2.3"` instead of `"^1.2.3"`).

3. Recursive update silently skips projects that don't declare any of
   the named args. Without this, `aube update -r --latest foo alias`
   hard-errors on the first project that doesn't have `alias` in its
   package.json — pnpm's recursive update just filters the arg list
   per project. Fixes the fanout for the four
   `recursive update --latest <name>` tests in update.ts.

Ports landed:
- pnpm/test/update.ts:143 ('update --latest')
- pnpm/test/update.ts:170 ('update --latest --save-exact')
- pnpm/test/update.ts:197 ('update --latest specific dependency')
- pnpm/test/update.ts:369 ('recursive update --latest specific no-shared')
- pnpm/test/update.ts:543 ('recursive update --latest specific shared',
  asserted per-project — see PNPM_TEST_IMPORT.md for the
  shared-lockfile divergence)

The `kevva/is-negative` GitHub-shorthand assertion was dropped from
143/170/197 — the `is-negative` portion is the only `is-negative`-related
expectation in those tests, and adding `user/repo` shorthand resolution
is its own feature (parser + git tarball fetcher + storage). Tracked as
a remaining gap in PNPM_TEST_IMPORT.md.

13/22 ported (was 8/22).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx changed the title test(install): port pnpm/test/update.ts (8/22) test(install): port pnpm/test/update.ts (13/22) May 1, 2026
Comment thread crates/aube/src/commands/update.rs
…ct args

run_filtered's per-project "declared" set was a union of every dep
bucket — but `run` itself filters by `--prod`/`--dev`/`--no-optional`
before looking up args. So a named arg that exists only as a devDep
in some project would survive the per-project filter (because it's in
the project's full dep map) and then hard-error inside `run` with
'package X is not a dependency' (the inner all_specifiers had already
excluded devDeps under --prod).

Mirror the include_prod / include_dev / include_optional logic from
`run` when building `declared`, so each project's declared set matches
the bucket the inner update will actually look at. Adds a regression
test exercising the failure mode greptile flagged
(`aube update -r --latest --prod foo` against a workspace where one
project has foo only as a devDep — must skip, not error).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 01798c9 into main May 1, 2026
19 checks passed
@jdx jdx deleted the claude/clever-cori-73805b branch May 1, 2026 01:22
jdx added a commit that referenced this pull request May 1, 2026
…ps (#446)

## Summary

Two small changes in
[crates/aube/src/commands/update.rs](crates/aube/src/commands/update.rs)
that finish the `pnpm/test/update.ts` cluster everywhere except the
documented prerelease-preservation and shared-lockfile divergences.
Brings the file to **15/22 ports** (was 13/22).

## Changes

### 1. `<pkg>@<spec>` arg syntax

pnpm phrases the manifest-rewrite-past-range case as `pnpm update
foo@latest`; aube was rejecting that with *package foo@latest is not a
dependency* because it looked up the literal arg in the deps map. Now
`<name>@<spec>` is split (scope-aware via a `split_pkg_arg` helper), the
bare `name` drives the dep lookup, and `@latest` (the only spec form
pnpm tests rely on) acts as a per-key `--latest` — the manifest rewrite
triggers only for that one entry instead of every direct dep.

This drops the translation note from the misc.ts:14 port — the new test
in this PR uses the original `update <pkg>@latest` syntax verbatim
instead of rewriting to `update --latest <pkg>`.

### 2. `aube update <indirect-pkg>` no longer errors

Previously the args loop hard-failed on any name that wasn't in
`package.json`'s deps map. Indirect deps now flow through:

- Validated against the lockfile graph (rejected if not in any
direct/transitive snapshot entry).
- Filtered out of the locked snapshot so the resolver re-resolves.
- Their parents' locked dep edges get rewritten to `latest` when the
user passed `<indirect>@latest`.
- Manifest is left alone (the indirect has no entry to rewrite).
- The direct dep that pulls in the indirect stays at its locked version
— only the named arg bumps.

The parent-edge rewrite is the non-obvious bit. The resolver's
lockfile-reuse path
([aube_resolver::resolve.rs:1164](crates/aube-resolver/src/resolve.rs#L1164))
iterates each parent's locked `dependencies` map and enqueues transitive
tasks using the locked **version** as the range. Just dropping the
indirect from the `packages` map isn't enough — the parent's pinned edge
(`pkg-with-1-dep@100.0.0`'s `dependencies: { dep-of: 100.0.0 }`) still
re-resolves to the same version. Forwarding `latest` on the edge makes
the transitive task a dist-tag spec, so the resolver fetches the
packument and picks the new latest.

## Ports

| pnpm test (line) | aube `@test` |
|---|---|
| `update <dep>` (14) | `aube update <pkg>@latest: parses arg syntax,
rewrites manifest past range` |
| `update indirect dependency should not update package.json` (690) |
`aube update <indirect-pkg>@latest: refreshes a transitive dep, leaves
manifest alone` |

The earlier `update --latest <pkg>` translation of misc.ts:14 stays as
its own test — keeps coverage of both invocation forms.

## Remaining gaps in update.ts

After this PR, 7 of 22 tests stay unported, all because of distinct
divergences (each tracked in
[PNPM_TEST_IMPORT.md](test/PNPM_TEST_IMPORT.md)):

- 51, 95: `aube update` (no `--latest`) doesn't rewrite the manifest
spec when the existing range allows a newer version (pnpm does).
- 599: `aube update --depth N` is parsed-but-no-op.
- 615, 728, 807: aube's `update --latest` downgrades a manifest pin past
a registry's older `latest` dist-tag (pnpm preserves the pin when it's
numerically newer).
- 249, 302: subsumed by 369/543 — aube's `update -r` always writes
per-project lockfiles regardless of `sharedWorkspaceLockfile`.

## Test plan

- [x] `AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats
test/pnpm_update.bats` — 16/16 pass.
- [x] `AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats
test/update.bats test/add.bats test/install.bats` — 96/96 pass (no
regression to the existing aube-native test surface).
- [x] `cargo fmt --check`
- [x] `cargo clippy --all-targets -- -D warnings`
- [x] `cargo test --workspace`
- [x] `mise run render` — no docs/usage drift (the `--save-exact` flag
landed in [#438](#438)).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes `aube update` dependency selection and lockfile
filtering/rewriting behavior, including transitive edge rewrites, which
could affect resolution results across workspaces. Scope is contained to
the update command and covered by new bats regression tests.
> 
> **Overview**
> `aube update` now parses positional args of the form `<pkg>@<spec>`
(scope-aware) and treats `@latest` as a per-package equivalent of
`--latest`, while hard-erroring on any other spec to avoid silently
ignoring user intent.
> 
> The update flow now accepts *indirect (transitive) deps* named on the
CLI: it validates them against the lockfile graph (with a workspace-root
lockfile fallback), drops their snapshots from the reused lockfile, and
for `<indirect>@latest` rewrites parent locked dependency edges to
`latest` so the resolver actually re-resolves the transitive version;
`package.json` is left unchanged for indirect updates.
> 
> Recursive `update -r` was adjusted to forward indirect args into
per-project updates by consulting each project’s lockfile (with
shared-lockfile fallback) and to avoid misclassifying flag-excluded
direct deps (e.g. `--prod <devDep>`) as indirect. New bats tests cover
the new arg syntax, indirect updates, spec rejection, and the `--prod`
regression.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7f95613. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant