Skip to content

Bump Cargo workspace root dependency requirements - #400

Merged
jbolda merged 11 commits into
jbolda:mainfrom
johncarmack1984:rust-workspace-root-deps
Jul 30, 2026
Merged

Bump Cargo workspace root dependency requirements#400
jbolda merged 11 commits into
jbolda:mainfrom
johncarmack1984:rust-workspace-root-deps

Conversation

@johncarmack1984

@johncarmack1984 johncarmack1984 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #397 from the Discord thread: version requirements that live outside member manifests now track member bumps, covering Cargo's root [workspace.dependencies] table (the tauri-apps/tauri#15412 shape). Default-on: it only fires where a version requirement exists, and a stale one is a silently under-constrained publish.

  • Requirements keep their form: partial pins stay partial, ^/~/= prefixes are preserved, other keys are untouched. Path-only, *, comparator-range (>=1.2, <2), and wildcard (1.*) entries stay byte-identical.
  • catalog: references in package.json are now left alone; they previously hit the partial-pin path and were rewritten to a bare major, the workspace:* corruption again. The catalog tables in pnpm-workspace.yaml stay manually managed (table bumping dropped per the review thread).
  • Runs only when covector version applies bumps; status and validation are unchanged.

From a real covector version run (pkg-b takes a minor, pkg-a cascades a patch):

[workspace.dependencies]
serde = "1.0"
rust_root_pkg_a_fixture = { version = "0.5", path = "pkg-a", default-features = false }  # patch: untouched
rust_root_pkg_b_fixture = "^0.9.0"  # was "^0.8.0"
rust_root_pkg_c_fixture = { path = "pkg-c", version = "*" }
rust_root_pkg_d_fixture = { path = "pkg-d" }

What a bump should do to a comparator range is #184's policy question, so those are recognized and skipped here. package = "..." renames are not probed, matching member-level behavior today.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Package Changes Through b59db83

There are 9 changes which include action with minor, @covector/assemble with minor, @covector/files with minor, covector with minor, @covector/apply with minor, @covector/changelog with minor, @covector/command with minor, @covector/toml with minor, @covector/types with minor

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
covector 0.12.5 0.13.0
@covector/apply 0.10.0 0.11.0
@covector/assemble 0.12.0 0.13.0
@covector/changelog 0.12.0 0.13.0
@covector/files 0.8.0 0.9.0
@covector/command 0.8.0 0.9.0
@covector/toml 0.2.0 0.3.0
@covector/types 0.0.0 0.1.0
action 0.12.5 0.13.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

@jbolda

jbolda commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Do we have a use case to support catalog:? I imagine the only use case is someone using that is for external deps which shouldn't be considered in a covector bump anyways, right?

@johncarmack1984
johncarmack1984 force-pushed the rust-workspace-root-deps branch from b4a3aa6 to 26766eb Compare July 5, 2026 01:03
@johncarmack1984 johncarmack1984 changed the title Bump workspace root dependency requirements for Cargo and pnpm catalogs Bump Cargo workspace root dependency requirements Jul 5, 2026
@johncarmack1984

Copy link
Copy Markdown
Contributor Author

Responding here so no one has to do discord forensics!:

You're right on the use case. External deps never enter this path at all (it only probes catalog entries whose name matches a covector-managed package), so bump only fires if someone catalogs an internal package with a concrete range instead of using workspace:. Technically possible, but pnpm doesn't document this pattern and I can't find a public repo doing it, so probably overkill to cover it; removed it from the YAML.

But! Left in the package.json guard, since when using it in a fixture I found a catalog: ref on a covector-managed dep was falling into the partial-pin path and getting rewritten to a bare major. Not a pattern I could find outside of the theoretical, but until a couple years ago, neither was the "workspace:" problem from my previous PR 😄. Would rather have covered the corner and not needed it than needed it and not covered it.

@jbolda jbolda left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Trying to imagine all of the edge cases we can hit here. Ideally we are just handling new situations more appropriately and not crashing or failing in an existing setup. I think addressing these two comments a bit more directly and that is everything I can think of.

Comment on lines +335 to +338
if (doc.has("workspace")) {
roots[rootManifestPath] = { file, doc };
break;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can we get into a situation where a single Cargo.toml crate (non-workspace) will loop infinitely?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not with the paths covector produces today, but nothing enforced that, so I made termination structural. Every manifest path fed to the walk is cwd-relative (loadFile normalizes them) and dirname bottoms out at . for those, so a standalone crate probes its own manifest, finds no [workspace], and stops at the top of the tree. An absolute path would have spun forever, though (dirname("/") is "/"). The loop now breaks where dirname stops changing (the fixed point at . or /), so it terminates regardless of the path's shape. Tests cover a standalone crate, a nested crate with no workspace above it, and the absolute-path worst case.

@@ -0,0 +1,11 @@
[package]
name = "rust_root_pkg_a_fixture"
version = "0.5.0"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Does this meant the situation where you set the version at the root, would this be like version.workspace = "true"? Have we tested for this versus the version specified here and just a path / features in the root? (Guess that is probably the normal case)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hadn't tested it, and writing the test caught a real bug, so good call. With version.workspace = true the covector package points at the root manifest and its version lives at [workspace.package]. The new requirement bump derived fine, but the version bump itself silently never landed: getPackageFileVersion was missing the [workspace.package] fallback that setPackageFileVersion already has, so the read came back empty and the manifest was written back with the old version while [workspace.dependencies] moved to the new requirement. Fixed the read to mirror the write, and added a fixture + apply test for the inherited arrangement: the root ends up with both the bumped [workspace.package] version and the bumped requirement, the path-and-features-only entry stays byte-identical, and the member manifests keep their checked-out bytes. (Your guess on the normal case is right: an entry with just a path and features has no version to bump and is left alone; that's pkg-d in the existing fixture and the helper entry in the new one.)

@johncarmack1984
johncarmack1984 force-pushed the rust-workspace-root-deps branch from f08c91a to 9f616e6 Compare July 14, 2026 03:09
@johncarmack1984
johncarmack1984 requested a review from jbolda July 26, 2026 21:34
An exact pin (`=0.5`) keeps its comparator through a bump, a member that no
root declares leaves every table alone, and each workspace root in a repo is
bumped independently. Note why the root walk starts at the member manifest
itself: a root holding the version its members inherit at `[workspace.package]`
is the package covector bumps.
A dependency in a `[target]` table reads its version straight off the
declaration, so a `{ workspace = true }` or path-only entry there reads back
undefined rather than empty and threw on the first requirement check.
Both paths pulled the comparator off a requirement with the same regex and
rebuilt it around the bumped version. Name that rewrite once, and name the
two conditions that make a root requirement unbumpable, so the regexes read
as what they mean.
A comparator range, a wildcard, and `*` each already cover the bumped version
and have no single pin to rewrite, but member declarations were collapsed onto
one anyway: `>=1.0 <2` became `=1.1`, `1.x` became `1.1`, `*` became `1`. The
workspace root table already refused to narrow these; apply the same guard to
member declarations, plain and behind a `workspace:` prefix alike.
@johncarmack1984

Copy link
Copy Markdown
Contributor Author

All six addressed, four commits.

Exact pin. pkg-g carries =0.5 and asserts =0.6 after a minor bump.

prevVersion undefined. Yes, and it throws. Only reachable through the [target] branch of bumpDeps, which reads target[property][depName]?.version instead of going through getPackageFileVersion. A { workspace = true } entry there reads back undefined, not "". Guarded, with a target-table fixture that reproduces the throw.

!root.doc.has(key) and multiple roots. New pkg.rust-workspace-root-deps-multi fixture, a core/ and a tools/ workspace. pkg-b bumps and appears in no root table. Both root files are asserted in full, so a spurious key fails. Plus a readCargoWorkspaceRoots unit test.

The walk checking its own manifest. Kept, and I tested your suggestion first. Starting at the parent turns the inherited-version test red: a root holding [workspace.package] version is itself the package covector bumps, so its own table is in scope. Comment at the walk, unit test for the case.

Duplication. One shared bumpRequirement. The guards are requirementFloats and requirementSpansRange, so the regexes say what they mean.

Extending that guard to member declarations turned up the same bug, wider:

">=1.0 <2"       →  "=1.1"
"*"              →  "1"
"1.x"            →  "1.1"
"workspace:1.x"  →  "workspace:1.1"

Declared ranges narrowed onto the bumped version. Same class as workspace:*"0". The suite passed before those tests existed, so nothing depended on it. I read it as a bug, not the #184 range policy, which is about widening ^1.0.0 across a major. Separate commit, easy to drop.

172 → 180 tests.

@jbolda
jbolda merged commit f22a1a9 into jbolda:main Jul 30, 2026
5 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 13, 2026
@johncarmack1984
johncarmack1984 deleted the rust-workspace-root-deps branch August 4, 2026 19:48
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.

2 participants