feat(cli): aube update parses <pkg>@<spec> args + accepts indirect deps#446
feat(cli): aube update parses <pkg>@<spec> args + accepts indirect deps#446
Conversation
Greptile SummaryThis PR adds two capabilities to Two aliased-indirect-dep gaps worth tracking: the parent edge rewrite loop matches Confidence Score: 4/5Safe to merge; the two flagged gaps are limited to aliased transitive deps, which are uncommon, and all primary code paths are well-tested. No P0/P1 findings. Two P2 logic gaps exist for aliased transitive deps — one causes a silent no-op for crates/aube/src/commands/update.rs — the Important Files Changed
Reviews (3): Last reviewed commit: "fix(cli): aube update flag-excluded dire..." | Re-trigger Greptile |
Benchmark changesVersions:
Public ratios: warm installs vs Bun 4x -> 4x; warm installs vs pnpm 5x -> 9x.
7f95613 vs 4326926 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 226c343. Configure here.
Two small changes in update.rs that unblock two more pnpm/test/update.ts
ports (15/22, was 13/22):
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. Now `<name>@<spec>` is split (scope-aware), the bare
`name` drives the dep lookup, and `@latest` (the only spec form
pnpm tests rely on) acts as a per-key `--latest` — manifest rewrite
triggers only for that one entry instead of every direct dep. Drops
the translation note from the misc.ts:14 port.
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
declared/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`. This part is non-obvious: the
resolver's lockfile-reuse path
(aube_resolver::resolve.rs:1164) 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 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.
- 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.
Ports landed:
- pnpm/test/update.ts:14 ('update <dep>') as `<pkg>@latest` parity
(the existing `update --latest <pkg>` translation stays as a
separate test).
- pnpm/test/update.ts:690 ('update indirect dependency should not
update package.json').
Test plan
- AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats \
test/pnpm_update.bats — 16/16 pass.
- 96/96 pass across update.bats / add.bats / install.bats — no
regression to the existing aube-native test surface.
- cargo fmt --check / cargo clippy --all-targets -- -D warnings clean.
- cargo test --workspace clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…@<spec> Addresses two P2 findings greptile flagged on the PR. 1. `aube update -r <indirect-pkg>@latest` was a silent no-op. `run_filtered`'s per-project filter only admitted args present in each project's `declared` (direct manifest deps). Indirect deps never appear there, so `per_pkg.packages` always emptied and the inner `run` was never called. Fix: also consult each project's lockfile (preferring per-project, falling back to the workspace- root shared one) so transitive deps flow through. Also added the same fallback to `run`'s `existing` lookup, since project-local lockfiles don't exist in the common `aube install` → `aube update -r` flow — without it, the inner indirect-arg validation in `run` rejects the dep that lives only in the shared lockfile. 2. `aube update foo@^2.0.0` silently swallowed the spec. `split_pkg_arg` accepts any `<name>@<spec>` form but only `@latest` was honored — anything else (`@^2.0.0`, `@1.2.3`, `@beta`) was treated as a bare name with the spec dropped. Fix: new `reject_unsupported_pkg_specs` helper called from both `run` and `run_filtered`. Errors with a message pointing at the supported alternatives (`--latest`, `<pkg>@latest`, or omit the spec for in-range refresh). Both fixes have regression tests: - `aube update <pkg>@<spec>: rejects non-latest specs with a helpful error` - `aube update -r <indirect-pkg>@latest: refreshes the indirect across all projects` Verified each test fails when its corresponding fix is reverted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nt re-resolve Addresses the cursor-bugbot finding on PR #446. A direct dep excluded by `--prod`/`--dev`/`--no-optional` (e.g. a devDep under `--prod`) fell through the new indirect-dep branch added for indirect-arg support: it missed `all_specifiers` (filtered by flags), the lockfile graph carries every package regardless of bucket so `in_graph` passed, and the dep silently re-resolved instead of erroring with the old "not a dependency" message. Fix in `run`: build `all_direct_keys` (every direct manifest key, ignoring flag filters) and short-circuit with the same error before the indirect-dep branch. The pre-indirect-support behavior is preserved verbatim — flag mismatches stay visible. Fix in `run_filtered`: build `all_declared` (same idea, per-project) and drop flag-excluded direct args from the per-project filter *before* the lockfile-name fallback rescues them. Without this guard the indirect-via-lockfile fallback would push devDeps under `--prod` into the inner `run` as if they were transitive, and `run`'s new guard would error — turning the existing "silent skip when only a devDep" recursive behavior into a hard failure. Filtering first keeps that test green. Regression test: `aube update --prod <devdep>` errors and leaves the lockfile pin at the previous version, so a future regression of either guard is caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
226c343 to
7f95613
Compare
|
Force-pushed rebased on greptile P2 — recursive indirect deps silently dropped: greptile P2 — non- cursor-bugbot — flag-excluded direct deps misclassified as indirect: a devDep named under Each fix has a regression test in Written with Claude. |

Summary
Two small changes in crates/aube/src/commands/update.rs that finish the
pnpm/test/update.tscluster 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 syntaxpnpm 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 asplit_pkg_arghelper), the barenamedrives 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>@latestsyntax verbatim instead of rewriting toupdate --latest <pkg>.2.
aube update <indirect-pkg>no longer errorsPreviously the args loop hard-failed on any name that wasn't in
package.json's deps map. Indirect deps now flow through:latestwhen the user passed<indirect>@latest.The parent-edge rewrite is the non-obvious bit. The resolver's lockfile-reuse path (aube_resolver::resolve.rs:1164) iterates each parent's locked
dependenciesmap and enqueues transitive tasks using the locked version as the range. Just dropping the indirect from thepackagesmap isn't enough — the parent's pinned edge (pkg-with-1-dep@100.0.0'sdependencies: { dep-of: 100.0.0 }) still re-resolves to the same version. Forwardinglateston the edge makes the transitive task a dist-tag spec, so the resolver fetches the packument and picks the new latest.Ports
@testupdate <dep>(14)aube update <pkg>@latest: parses arg syntax, rewrites manifest past rangeupdate indirect dependency should not update package.json(690)aube update <indirect-pkg>@latest: refreshes a transitive dep, leaves manifest aloneThe 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):
aube update(no--latest) doesn't rewrite the manifest spec when the existing range allows a newer version (pnpm does).aube update --depth Nis parsed-but-no-op.update --latestdowngrades a manifest pin past a registry's olderlatestdist-tag (pnpm preserves the pin when it's numerically newer).update -ralways writes per-project lockfiles regardless ofsharedWorkspaceLockfile.Test plan
AUBE_TEST_REGISTRY=http://localhost:4873 ./test/bats/bin/bats test/pnpm_update.bats— 16/16 pass.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).cargo fmt --checkcargo clippy --all-targets -- -D warningscargo test --workspacemise run render— no docs/usage drift (the--save-exactflag landed in #438).🤖 Generated with Claude Code
Note
Medium Risk
Changes
aube updatedependency 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 updatenow parses positional args of the form<pkg>@<spec>(scope-aware) and treats@latestas 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>@latestrewrites parent locked dependency edges tolatestso the resolver actually re-resolves the transitive version;package.jsonis left unchanged for indirect updates.Recursive
update -rwas 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--prodregression.Reviewed by Cursor Bugbot for commit 7f95613. Bugbot is set up for automated code reviews on this repo. Configure here.