Skip to content

fix(release): fail when a package's versioned file does not exist - #1064

Merged
BryanFRD merged 5 commits into
mainfrom
fix/missing-versioned-files
Sep 16, 2026
Merged

BryanFRD merged 5 commits into
mainfrom
fix/missing-versioned-files

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Closes #983.

compute_plan read the package's first versioned file with read_version(...).ok(), so a path that does not exist became None and planning carried on. check then reported a clean plan, exit 0, for a configuration that cannot be released. The only signal was the missing over <file> suffix on that package's line, which is exactly what nobody notices in a list of packages.

$ ferrflow check
error[E1024]: package "api": versioned file "Cargo.toml" does not exist, so the release
would fail when it tries to write it.
  Paths in versionedFiles are relative to the repository root, not to the package's own
  path. Did you mean "packages/api/Cargo.toml"?
$ echo $?
1

Correcting the issue's stated impact

The issue says the release "runs, tags are created and the changelog is written, but the version file is never bumped". That is not what happens, at least not for a format that writes the file. Running a real release on the issue's own repro against main:

$ ferrflow release
error[E4101]: Cannot read .../vfrepo/Cargo.toml
  The system cannot find the file specified. (os error 2)
$ git tag -l
(empty)

write_version reads the file before editing it, so it fails and no tag is created. There is no silent drift and nothing to clean up afterwards.

What is real is narrower and still worth fixing: check disagrees with release. A dry run whose whole job is to say what a release will do reports success for something release refuses, and when it does refuse, the message is a bare read error with no hint about the path convention. This PR makes check answer the question it was asked, at the point where the answer is useful.

The error message and the E1024 docs are worded to that, rather than to the tag-drift claim.

Two scoping choices

Only packages this run would write. The check sits in compute_plan after the excluded and not-touched early returns, rather than running across every configured package the way validate does. A repository-wide check turns a partial or sparse checkout into a hard failure: a job that checks out only the packages it touches would stop being able to release any of them. validate and doctor still cover every configured package, which is their job.

Only formats that write the file. gomod is the case that matters: a Go module's version lives in the git tag, read_version gets it from git describe, and write_version is a no-op. A missing go.mod cannot cause the failure this check exists to catch, and the format-gomod fixture is a real repository that has no go.mod at all. Caught by CI, which is exactly what that fixture is for.

Tests

Four, on real git fixtures:

  • a_versioned_file_that_does_not_exist_fails_the_plan_rather_than_bumping_nothing reproduces the issue's config verbatim and asserts the error names api/Cargo.toml.
  • a_versioned_file_that_exists_still_plans_normally is the control, so the check cannot pass by rejecting everything.
  • an_untouched_package_is_skipped_before_its_files_are_checked pins the first scoping choice.
  • a_format_that_never_writes_the_file_does_not_need_it_to_exist pins the second, and asserts the fixture really has no go.mod first so it cannot pass vacuously.

Known red check: multi-versioned-files

Fixtures — Config & Formats is still failing on that one fixture, and the fixture is wrong rather than the change.

multi-versioned-files.json declares two toml versioned files, version.toml and version2.toml. The generator only ever writes one version.toml per package, so version2.toml has never existed. The fixture asserts that check plans this repository fine, while ferrflow release on it would fail at version2.toml exactly as above. This PR makes check agree with release, so the fixture's expectation no longer holds.

It cannot be fixed from this repo. PackageDef in the generator has no versioned_files field, so the block that format-gomod.json already carries is silently dropped by serde. Making the fixture honest means:

  1. a PR in FerrLabs/Fixtures adding versioned_files to PackageDef and materialising them,
  2. a release of it,
  3. bumping the pin in ci.yml (currently 7b04ba1 / v1.2.2) here.

Same shape as the schema-parity dance, and this PR stays red until step 3. Say the word and I will open the Fixtures side. The alternative, dropping the second file from the fixture, would delete the only coverage of multiple versioned files, which seems worse.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

SonarQube — aucune nouvelle issue

Comparaison entre le projet bac à sable de cette PR et la branche par défaut : SonarQube Community n'analyse pas les PR, ce delta est calculé côté CI. Détail

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The direction is right and the scoping argument holds, but the check does not land where the description says it does: placed above the skip returns, it also fails packages the run would never write. One blocking finding on that, plus two nits with suggestions.

Comment thread src/monorepo/run/plan.rs Outdated
Comment thread src/monorepo/run/plan.rs Outdated
Comment thread src/monorepo/run/plan.rs Outdated

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up on 880f992. The modifies_file() gate is sound: modifies_file already exists on the VersionFile trait with a true default and only gomod overrides it, and formats/mod.rs has tests pinning both sides of that, so the gate cannot silently widen. One nit on the test it ships with.

Comment thread src/monorepo/run/plan.rs Outdated
@BryanFRD

BryanFRD commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Right, and it is the exact failure mode I have been trying to avoid elsewhere in this PR. Ok(Skipped) satisfies the old assertion, so if mymod ever stopped being touched the test would stay green while never executing the modifies_file() gate it exists to pin.

Applied: it now asserts PackagePlan::Bump, so the plan has to reach the far side of the check.

Both guards are needed and neither is redundant: the !go.mod.exists() assertion above stops it passing because the file happens to be there, and the Bump assertion stops it passing because the package was never planned.

@BryanFRD
BryanFRD force-pushed the fix/missing-versioned-files branch from ea886b6 to bf0ac5b Compare September 16, 2026 10:35

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bf0ac5b resolves the blocking finding: the check now runs immediately before Ok(PackagePlan::Bump(...)), so a stale versionedFiles entry only fails the packages this run would actually write, and the new chore-only test pins that position. Both nits are addressed too. One new nit on the ./ prefix in the hint, not worth holding the PR for.

The multi-versioned-files fixture is out of scope here as described, and the Fixtures pin bump is the right sequencing.

Comment thread src/monorepo/run/plan.rs
}

fn suggested_versioned_path(pkg: &PackageConfig, path: &str) -> Option<String> {
let prefix = pkg.path.trim_end_matches('/');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the prefix is trimmed at the end but not at the start, unlike every other reader of pkg.path (PackageConfig::is_touched_by, why/mod.rs, run/mod.rs all do .trim_start_matches("./").trim_end_matches('/'), and config load does not normalise it). With path = "./api", Path::new("api/Cargo.toml").starts_with("./api") is false because the leading . survives as a CurDir component, so a correct config gets Did you mean "./api/api/Cargo.toml"?. Hint text only, but it points at a path that does not exist.

Suggested change
let prefix = pkg.path.trim_end_matches('/');
let prefix = pkg.path.trim_start_matches("./").trim_end_matches('/');

@BryanFRD
BryanFRD force-pushed the fix/missing-versioned-files branch from bf0ac5b to 9bf544e Compare September 16, 2026 10:48

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5587f2d is sound, and it removes the Fixtures-side dependency the description was still blocked on. Checked against the pinned generator (FerrLabs/Fixtures@7b04ba1, v1.2.2): hooks[] is a generic file writer there, not script-only, and generate_explicit writes hook files before add_all_and_commit("chore: initial setup"), so version2.toml lands in the initial commit and under v1.0.0. Both versioned files now read 1.0.0, the feat: commit still yields the expected minor to 1.1.0, and the fixtures- cache key hashes tests/fixtures/definitions/**, so the fixture is regenerated rather than served stale.

The fixture jobs had not started on this head when I looked, so Fixtures — Config & Formats is reasoned about, not observed green.

Nit: the generator chmods every hooks[] entry 0755, so version2.toml is committed executable. Harmless here since run-tests.sh only runs check and greps stdout, and hooks[] is the only seeding mechanism v1.2.2 offers, but it is worth a word in the definition or in the PR body so the next reader does not take the mode as meaningful.

The description's "Known red check: multi-versioned-files" section is now stale and reads as if the PR is still blocked on a Fixtures release.

@BryanFRD
BryanFRD merged commit 343702a into main Sep 16, 2026
31 checks passed
@BryanFRD
BryanFRD deleted the fix/missing-versioned-files branch September 16, 2026 15:58
ferrflow Bot added a commit that referenced this pull request Sep 16, 2026
## [7.21.3] - 2026-09-16

### Bug Fixes

- fix(release): fail when a package's versioned file does not exist (#1064)
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.

check and release ignore missing versionedFiles that validate rejects

1 participant