Skip to content

feat(monorepo): propagate the upstream bump type through the dependency cascade - #782

Merged
BryanFRD merged 2 commits into
mainfrom
feat/cascade-bump-propagation
Aug 4, 2026
Merged

feat(monorepo): propagate the upstream bump type through the dependency cascade#782
BryanFRD merged 2 commits into
mainfrom
feat/cascade-bump-propagation

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Addresses points 1 and 3 of #493. Point 2 (update_dependents) is not in this PR — see the end.

The bug

The cascade hardcoded BumpType::Patch (cascade.rs:79) and wrote "patch" into the JSON outputs. So core going 1.4.0 → 2.0.0 on a feat!: handed its dependent cli a patch, and consumers upgraded cli straight into a breaking API.

Verified on a real fixture before the change: cli came out at 2.1.1. After:

core   1.4.0 -> 2.0.0  (major)
cli    2.1.0 -> 3.0.0  (major)

Propagation

bumped_names: HashSet<String> becomes bumped: HashMap<String, BumpType>, so the cascade knows what each upstream actually got. The dependent's bump is the strongest resolved bump across its moved dependencies — the same rule a package's own commits already follow.

The default is same: the dependent gets exactly what the upstream got. A breaking upstream makes the dependent breaking, which is the honest signal — it now builds against a breaking API.

Per-dependency policy

depends_on accepts the detailed form alongside the plain name, via an untagged enum, so every existing config keeps working unchanged:

depends_on = ["core"]                                        # same (default)
depends_on = [{ name = "core", propagate = "major-on-major" }]
policy major upstream minor patch
same (default) major minor patch
major-on-major major patch patch
patch patch patch patch
none

patch reproduces the pre-PR behaviour for anyone who wants it back; none opts a dependency out of the cascade entirely. An upstream that did not move never manufactures a downstream bump, under any policy.

Verified end to end

Built the binary and ran ferrflow check --json on four monorepo fixtures:

  • default + minor upstream → dependent minor
  • major-on-major + minor upstream → dependent patch
  • patch + major upstream → dependent patch (old behaviour intact)
  • none + major upstream → dependent absent from the plan entirely

Also

Schema updated for both spellings (dependsOn / depends_on) with the oneOf string-or-object shape, and the cloud copy re-synced — byte parity verified.

8 new tests on the policy table and both deserialisation forms, including that no policy can invent a bump from an unmoved upstream. Full suite green (1843), clippy --all-targets -D warnings clean.

What is not here: update_dependents

Point 2 of the issue — rewriting the dependency ranges in dependents' manifests — is deliberately left for its own PR, and I want to be explicit that I chose this rather than run out of room silently.

It is the riskiest part of the issue: it writes into user manifests, and getting it wrong corrupts a package.json or Cargo.toml. It also needs real per-format work that does not exist yet — json.rs has a hand-rolled span walker that only finds top-level string values, and there is an explicit test asserting the version bump must not touch dependencies. Doing it properly means a nested-span finder for JSON, toml_edit handling for both the dep = "1.2" and dep = { version = "1.2" } shapes, and a decision on constraint preservation (^, ~, >=, workspace:*, git/path deps) — each of which deserves its own tests and review.

The two parts are independent: propagation is useful on its own and this PR is complete without it. Happy to open the follow-up immediately.

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

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

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.

Reviewed the diff in full (config, cascade, graph, schema, fixtures).

  • BumpType already derives Ord with None < Patch < Minor < Major and a matching Display, so .max() over resolved policies and bump.to_string() in the JSON/text outputs both do the right thing — no silent behavior gap there.
  • PropagatePolicy::resolve correctly special-cases BumpType::None in every arm, so an unmoved upstream never manufactures a downstream bump under any policy (matches the dedicated test and the fixture's docs/none case).
  • The Vec<String> → Vec<Dependency> change is consistent everywhere it matters (cascade.rs, graph.rs); other call sites only ever construct depends_on: vec![], which still type-checks. bumped_names: HashSet<String>bumped: HashMap<String, BumpType> is threaded through cleanly, and HashSet stays imported in both files where it's still used elsewhere (refresh_lockfiles in mod.rs, cycle detection in graph.rs).
  • Cycle detection (graph.rs) just swaps dep.as_str() for dep.name() — behavior unchanged, tests updated accordingly.
  • Untagged Dependency enum keeps both the plain-string and detailed-object config forms working, matches the schema's oneOf, and the new policy table (same/major-on-major/patch/none) is covered by unit tests plus an end-to-end fixture exercising all four policies against one major bump.

Nit: the new monorepo-deps-propagate-policies.json fixture's check_contains entries for cli/sdk don't include the (policy, dependency: core) suffix the way the other two fixtures do — not wrong (the line is still a valid substring), just slightly less precise than it could be.

No blocking issues. Nice, honest write-up in the PR description about deliberately deferring update_dependents.

@BryanFRD
BryanFRD merged commit 8c72afe into main Aug 4, 2026
38 checks passed
@BryanFRD
BryanFRD deleted the feat/cascade-bump-propagation branch August 4, 2026 21:05
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