Skip to content

feat(monorepo): rewrite dependent version constraints on release - #783

Merged
BryanFRD merged 2 commits into
mainfrom
feat/update-dependents
Aug 4, 2026
Merged

feat(monorepo): rewrite dependent version constraints on release#783
BryanFRD merged 2 commits into
mainfrom
feat/update-dependents

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Part 2 of #493. Stacked on #782 (the propagation policy) — merge that first, this PR's diff is only the manifest rewrite.

When a package is bumped, every dependent that declares it via dependsOn still pins the old range. This adds workspace.updateDependents (default false): after the cascade settles, each dependent's manifest has its constraint for the bumped package rewritten to the new version, and the file is staged in the same release commit.

{
  "workspace": { "updateDependents": true },
  "package": [
    { "name": "core", "path": "core", "versionedFiles": [{ "path": "core/package.json", "format": "json" }] },
    { "name": "cli", "path": "cli", "dependsOn": ["core"], "versionedFiles": [{ "path": "cli/package.json", "format": "json" }] }
  ]
}
● core  1.0.0 → 1.1.0  (minor)
● cli   2.3.0 → 2.4.0  (minor, dependency: core)
  ↳ core → 1.1.0 in cli/package.json

cli/package.json goes from "core": "^1.0.0" to "core": "^1.1.0" in the release commit.

What gets rewritten

json (dependencies, devDependencies, peerDependencies, optionalDependencies) and toml (dependencies, dev-dependencies, build-dependencies, including the { version = "…", path = "…" } inline form). Every other format is skipped.

Only a plain operator + version is touched, and the operator is preserved — ^1.2.3^2.0.0, not a bare pin. Anything carrying intent a pin would destroy is left for a human: workspace:*, file:/git:/npm: specs, 1.x, *, latest, >=1.0.0 <2.0.0, ^1.0.0 || ^2.0.0. A missing manifest, an unparseable one, or an absent dependency is a no-op, never a failed release.

The rewrite runs after the cascade so a constraint always lands on the version its upstream actually ended up at. It writes only when the bytes change, so a re-run adds nothing to the commit.

Notes

  • find_nested_string_value_span in formats/json.rs is new — the existing walker only found top-level keys, and dependency tables are nested.
  • Verified end-to-end on a real monorepo with a bare remote: correct output line, the rewritten manifest staged in the release commit, workspace:* untouched, and a propagate: "none" dependent neither bumped nor rewritten.

Part of #493

Copilot AI lite review requested due to automatic review settings August 4, 2026 20:57
@BryanFRD
BryanFRD enabled auto-merge (squash) August 4, 2026 20:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 4, 2026 21:01
@BryanFRD
BryanFRD force-pushed the feat/update-dependents branch from 07afcf9 to 3d8cb06 Compare August 4, 2026 21:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid feature — the constraint-rewrite logic (operator-preserving, JSON/TOML, no-op-safe) is careful and well tested. One correctness gap found in the new update_dependent_manifests path; a dry-run completeness nit besides.

Comment thread src/monorepo/run/cascade.rs
Comment thread src/monorepo/run/mod.rs Outdated
@BryanFRD
BryanFRD force-pushed the feat/update-dependents branch from 3d8cb06 to 71b7214 Compare August 4, 2026 21:14
Copilot AI review requested due to automatic review settings August 4, 2026 21:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BryanFRD

BryanFRD commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Both fixed, and you were right to push back on that manual test — the propagate: "none" package in it declared workspace:*, which rewrite_constraint refuses anyway, so the test proved nothing about the policy. With a plain ^1.0.0 it did get rewritten.

Blocking finding. update_dependent_manifests now skips any dependency whose policy is PropagatePolicy::None before looking at bumped_versions.

Dry-run nit. Taken. update_dependency is split into plan_dependency_update returning a PlannedUpdate and PlannedUpdate::apply, so the rewrite is computed and reported in both modes and only written when it is not a dry run. --dry-run output:

● core  1.0.0 → 1.1.0  (minor)
● cli   2.3.0 → 2.4.0  (minor, dependency: core)
  ↳ core → 1.1.0 in cli/package.json

with both manifests byte-identical afterwards.

Two tests in cascade.rs cover it on a three-package workspace (core bumped, cli propagating, docs on none): one asserts docs/package.json keeps ^1.0.0 while cli moves to ^2.0.0 and only cli/package.json is staged, the other asserts a dry run emits the line, stages nothing, and writes nothing. Both fail without the fixes.

Re-verified end-to-end against a bare remote: dry run previews and touches nothing, the real run rewrites cli only and stages it in the release commit, docs is untouched.

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Follow-up review. Both findings from the last pass are fixed in 71b7214: the propagate: "none" dependent is now correctly skipped before rewriting, and --dry-run now previews the rewrite via a plan/apply split instead of skipping the whole rewrite. No new issues in the diff since. Approving.

@BryanFRD
BryanFRD merged commit 281e5b3 into main Aug 4, 2026
38 checks passed
@BryanFRD
BryanFRD deleted the feat/update-dependents branch August 4, 2026 21:18
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