From b80a1007f472160f54ce090d0b06e196a30572b3 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Mon, 7 Sep 2026 14:40:20 +0200 Subject: [PATCH 1/5] fix(release): fail when a package's versioned file does not exist --- docs/site/docs-en/reference/errors.md | 20 ++++ docs/site/docs-fr/reference/errors.md | 20 ++++ src/error_code.rs | 3 + src/monorepo/run/plan.rs | 141 +++++++++++++++++++++++++- 4 files changed, 183 insertions(+), 1 deletion(-) diff --git a/docs/site/docs-en/reference/errors.md b/docs/site/docs-en/reference/errors.md index 355bfac8..82c4ed40 100644 --- a/docs/site/docs-en/reference/errors.md +++ b/docs/site/docs-en/reference/errors.md @@ -121,6 +121,26 @@ More than one config file was found in the project root (e.g. both `ferrflow.jso Running `ferrflow init` when a config file already exists. +### E1024: Versioned file does not exist + + + +A package that this run would release lists a `versionedFiles` entry whose file is not on disk. The release is stopped rather than tagging a version no manifest carries. + +The usual cause is a path written relative to the package instead of the repository root. `package.path` is not a prefix that FerrFlow adds for you: + +```toml +[[package]] +name = "api" +path = "packages/api" + +[[package.versioned_files]] +path = "Cargo.toml" # wrong, looked up at the repository root +# path = "packages/api/Cargo.toml" # right +``` + +The error names the path it probably meant. `ferrflow validate` reports the same problem for every configured package, including ones this run would not touch. + ## Validation Errors ### E1100: Invalid repo spec diff --git a/docs/site/docs-fr/reference/errors.md b/docs/site/docs-fr/reference/errors.md index 8d3a0e4d..722a059d 100644 --- a/docs/site/docs-fr/reference/errors.md +++ b/docs/site/docs-fr/reference/errors.md @@ -121,6 +121,26 @@ Plusieurs fichiers de config trouv\u00e9s dans le r\u00e9pertoire. `ferrflow init` lanc\u00e9 alors qu'un fichier de config existe d\u00e9j\u00e0. +### E1024 : Fichier versionne introuvable + + + +Un package que cette execution allait publier declare une entree `versionedFiles` dont le fichier n'est pas sur le disque. La release est interrompue au lieu de poser un tag qu'aucun manifeste ne porte. + +La cause habituelle est un chemin ecrit relativement au package plutot qu'a la racine du depot. `package.path` n'est pas un prefixe que FerrFlow ajoute pour vous : + +```toml +[[package]] +name = "api" +path = "packages/api" + +[[package.versioned_files]] +path = "Cargo.toml" # faux, cherche a la racine du depot +# path = "packages/api/Cargo.toml" # correct +``` + +L'erreur indique le chemin qu'elle suppose correct. `ferrflow validate` signale le meme probleme pour tous les packages configures, y compris ceux que cette execution n'aurait pas touches. + ## Erreurs de validation ### E1100 : Spec de repo invalide diff --git a/src/error_code.rs b/src/error_code.rs index 4c8df5be..ce6dbd64 100644 --- a/src/error_code.rs +++ b/src/error_code.rs @@ -83,6 +83,9 @@ pub const CONFIG_DUPLICATE_PACKAGE: ErrorCode = ErrorCode(1022); #[allow(dead_code)] pub const CONFIG_MISSING_PACKAGE_PATH: ErrorCode = ErrorCode(1023); +#[allow(dead_code)] +pub const CONFIG_MISSING_VERSIONED_FILE: ErrorCode = ErrorCode(1024); + #[allow(dead_code)] pub const VALIDATE_INVALID_REPO_SPEC: ErrorCode = ErrorCode(1100); #[allow(dead_code)] diff --git a/src/monorepo/run/plan.rs b/src/monorepo/run/plan.rs index e91c2587..1528b2f1 100644 --- a/src/monorepo/run/plan.rs +++ b/src/monorepo/run/plan.rs @@ -1,8 +1,9 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use crate::changelog::GitLog; use crate::config::{Config, OrphanedTagStrategy, PackageConfig, VersioningStrategy}; use crate::conventional_commits::{BumpType, determine_bump}; +use crate::error_code::{self, ErrorCodeExt}; use crate::formats::read_version; use crate::git::{ Repository, TagIndex, find_highest_semver_tag_with_cache, get_changed_files_for_commit, @@ -269,6 +270,34 @@ pub(super) fn commits_for_package( Ok(scope_commits_to_package(repo, pkg, inputs, commits)) } +fn ensure_versioned_files_exist(pkg: &PackageConfig, root: &Path) -> Result<()> { + for vf in &pkg.versioned_files { + if root.join(&vf.path).exists() { + continue; + } + return Err(anyhow!( + "package \"{name}\": versioned file \"{path}\" does not exist, so this release \ + would create a tag no manifest carries.\n \ + Paths in versionedFiles are relative to the repository root, not to the \ + package's own path. Did you mean \"{suggestion}\"?", + name = pkg.name, + path = vf.path, + suggestion = suggested_versioned_path(pkg, &vf.path), + )) + .error_code(error_code::CONFIG_MISSING_VERSIONED_FILE); + } + Ok(()) +} + +fn suggested_versioned_path(pkg: &PackageConfig, path: &str) -> String { + let prefix = pkg.path.trim_end_matches('/'); + if prefix.is_empty() || prefix == "." || path.starts_with(prefix) { + path.to_string() + } else { + format!("{prefix}/{path}") + } +} + pub(super) fn compute_plan( repo: &Repository, pkg: &PackageConfig, @@ -300,6 +329,8 @@ pub(super) fn compute_plan( tags_for_package(inputs.all_tags, &tag_search_prefix) }); + ensure_versioned_files_exist(pkg, inputs.root)?; + let file_source = pkg.versioned_files.first().and_then(|vf| { read_version(vf, inputs.root) .ok() @@ -734,6 +765,114 @@ mod tests { ); } + fn missing_file_fixture(pkg_path: &str, versioned_path: &str) -> (Fixture, Vec) { + let (dir, repo) = init_repo(); + let root = dir.path().to_path_buf(); + write_pkg(&root, "api", "2.4.0"); + write_pkg(&root, "sdk", "1.0.0"); + write_config_raw( + &root, + "", + &format!( + r#"{{"name":"api","path":"{pkg_path}","versionedFiles":[{{"path":"{versioned_path}","format":"toml"}}]}}, + {{"name":"sdk","path":"sdk","versionedFiles":[{{"path":"sdk/Cargo.toml","format":"toml"}}]}}"# + ), + ); + git(&root, &["add", "-A"]); + commit_file(&root, "seed.txt", "x", "chore: seed", 1_950_000_000); + commit_file( + &root, + "api/endpoint.rs", + "x", + "feat(api): add an endpoint", + 1_950_000_100, + ); + let fx = build_fixture(root, dir, repo); + let changed_files = get_changed_files(&fx.repo).unwrap(); + (fx, changed_files) + } + + fn plan_result(fx: &Fixture, changed_files: &[String], name: &str) -> Result { + let all_tags = collect_all_tags(&fx.repo); + let head_ancestors = build_head_ancestors(&fx.repo).ok(); + let tag_index = TagIndex::build(&fx.repo).ok(); + let prerelease_ctx = PrereleaseContext::resolve(None, "main", None).unwrap(); + let forced: Vec> = Vec::new(); + let inputs = build_inputs( + fx, + &tag_index, + &head_ancestors, + &all_tags, + &prerelease_ctx, + &forced, + changed_files, + ); + let pkg = fx + .config + .packages + .iter() + .find(|p| p.name == name) + .expect("package in fixture"); + compute_plan(&fx.repo, pkg, &inputs) + } + + #[test] + fn a_versioned_file_that_does_not_exist_fails_the_plan_rather_than_bumping_nothing() { + // versionedFiles paths are relative to the repository root. Giving one + // relative to the package used to plan a bump, tag it, and write no + // file, leaving the repo tagged at a version no manifest carries. + let (fx, changed) = missing_file_fixture("api", "Cargo.toml"); + + let err = match plan_result(&fx, &changed, "api") { + Ok(plan) => panic!( + "a missing versioned file must fail, got {:?}", + plan.summary() + ), + Err(err) => err, + }; + let msg = format!("{err:?}"); + assert!(msg.contains("does not exist"), "{msg}"); + assert!( + msg.contains("api/Cargo.toml"), + "the error should point at the repo-root path it probably meant: {msg}" + ); + } + + #[test] + fn a_versioned_file_that_exists_still_plans_normally() { + let (fx, changed) = missing_file_fixture("api", "api/Cargo.toml"); + + let plan = plan_result(&fx, &changed, "api") + .unwrap_or_else(|e| panic!("a correct config must still plan: {e:?}")); + assert!( + matches!(plan, PackagePlan::Bump(_)), + "expected a bump, got {:?}", + plan.summary() + ); + } + + #[test] + fn an_untouched_package_is_skipped_before_its_files_are_checked() { + // Scoping the check to packages this run would actually write keeps a + // partial or sparse checkout from failing a release for a package it + // was never going to touch. + let (fx, changed) = missing_file_fixture("api", "Cargo.toml"); + + let plan = plan_result(&fx, &changed, "sdk") + .unwrap_or_else(|e| panic!("an untouched package must not fail: {e:?}")); + assert!( + matches!( + plan, + PackagePlan::Skipped { + reason: SkipReason::NotTouched, + .. + } + ), + "expected sdk to be skipped, got {:?}", + plan.summary() + ); + } + fn write_config_raw(dir: &Path, workspace: &str, packages: &str) { std::fs::write( dir.join(".ferrflow"), From aab48f8648b4e0fea9d1a4b5ccdb6f4151e331d8 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Mon, 7 Sep 2026 15:12:54 +0200 Subject: [PATCH 2/5] fix(release): only require a versioned file the format actually writes --- docs/site/docs-en/reference/errors.md | 2 +- docs/site/docs-fr/reference/errors.md | 2 +- src/monorepo/run/plan.rs | 46 +++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/site/docs-en/reference/errors.md b/docs/site/docs-en/reference/errors.md index 82c4ed40..2495064e 100644 --- a/docs/site/docs-en/reference/errors.md +++ b/docs/site/docs-en/reference/errors.md @@ -125,7 +125,7 @@ Running `ferrflow init` when a config file already exists. -A package that this run would release lists a `versionedFiles` entry whose file is not on disk. The release is stopped rather than tagging a version no manifest carries. +A package that this run would release lists a `versionedFiles` entry whose file is not on disk. The run stops at plan time rather than at write time, where the same problem surfaces as a bare read error. The usual cause is a path written relative to the package instead of the repository root. `package.path` is not a prefix that FerrFlow adds for you: diff --git a/docs/site/docs-fr/reference/errors.md b/docs/site/docs-fr/reference/errors.md index 722a059d..f8310fb1 100644 --- a/docs/site/docs-fr/reference/errors.md +++ b/docs/site/docs-fr/reference/errors.md @@ -125,7 +125,7 @@ Plusieurs fichiers de config trouv\u00e9s dans le r\u00e9pertoire. -Un package que cette execution allait publier declare une entree `versionedFiles` dont le fichier n'est pas sur le disque. La release est interrompue au lieu de poser un tag qu'aucun manifeste ne porte. +Un package que cette execution allait publier declare une entree `versionedFiles` dont le fichier n'est pas sur le disque. L'execution s'arrete au moment du plan plutot qu'au moment de l'ecriture, ou le meme probleme apparait sous la forme d'une simple erreur de lecture. La cause habituelle est un chemin ecrit relativement au package plutot qu'a la racine du depot. `package.path` n'est pas un prefixe que FerrFlow ajoute pour vous : diff --git a/src/monorepo/run/plan.rs b/src/monorepo/run/plan.rs index 1528b2f1..11e0eceb 100644 --- a/src/monorepo/run/plan.rs +++ b/src/monorepo/run/plan.rs @@ -272,12 +272,19 @@ pub(super) fn commits_for_package( fn ensure_versioned_files_exist(pkg: &PackageConfig, root: &Path) -> Result<()> { for vf in &pkg.versioned_files { + // A handler that does not write the file cannot be caught out by its + // absence. gomod is the case: the version lives in the git tag, its + // write_version is a no-op, and a go.mod entry is only there to name + // the format. + if !crate::formats::get_handler(&vf.format).modifies_file() { + continue; + } if root.join(&vf.path).exists() { continue; } return Err(anyhow!( - "package \"{name}\": versioned file \"{path}\" does not exist, so this release \ - would create a tag no manifest carries.\n \ + "package \"{name}\": versioned file \"{path}\" does not exist, so the release \ + would fail when it tries to write it.\n \ Paths in versionedFiles are relative to the repository root, not to the \ package's own path. Did you mean \"{suggestion}\"?", name = pkg.name, @@ -851,6 +858,41 @@ mod tests { ); } + #[test] + fn a_format_that_never_writes_the_file_does_not_need_it_to_exist() { + // go.mod carries no version: gomod reads it from the git tag and its + // write_version is a no-op, so a missing go.mod cannot cause the drift + // this check exists to catch. + let (dir, repo) = init_repo(); + let root = dir.path().to_path_buf(); + write_pkg(&root, "mymod", "1.0.0"); + write_config_raw( + &root, + "", + r#"{"name":"mymod","path":".","versionedFiles":[{"path":"go.mod","format":"gomod"}]}"#, + ); + git(&root, &["add", "-A"]); + commit_file(&root, "seed.txt", "x", "chore: seed", 1_950_000_000); + git(&root, &["tag", "v1.0.0"]); + commit_file( + &root, + "handler.go", + "x", + "fix: handle a nil pointer", + 1_950_000_100, + ); + let fx = build_fixture(root, dir, repo); + let changed = get_changed_files(&fx.repo).unwrap(); + + assert!( + !fx.root.join("go.mod").exists(), + "the fixture must not create go.mod, or this proves nothing" + ); + plan_result(&fx, &changed, "mymod").unwrap_or_else(|e| { + panic!("a gomod package must plan without a go.mod on disk: {e:?}") + }); + } + #[test] fn an_untouched_package_is_skipped_before_its_files_are_checked() { // Scoping the check to packages this run would actually write keeps a From 5b57a28215f45e134db9ed126eda2c81e7c8e427 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Mon, 7 Sep 2026 15:23:08 +0200 Subject: [PATCH 3/5] test(release): assert the gomod plan reaches past the file check --- src/monorepo/run/plan.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/monorepo/run/plan.rs b/src/monorepo/run/plan.rs index 11e0eceb..8c840673 100644 --- a/src/monorepo/run/plan.rs +++ b/src/monorepo/run/plan.rs @@ -888,9 +888,14 @@ mod tests { !fx.root.join("go.mod").exists(), "the fixture must not create go.mod, or this proves nothing" ); - plan_result(&fx, &changed, "mymod").unwrap_or_else(|e| { + let plan = plan_result(&fx, &changed, "mymod").unwrap_or_else(|e| { panic!("a gomod package must plan without a go.mod on disk: {e:?}") }); + assert!( + matches!(plan, PackagePlan::Bump(_)), + "the plan must reach the far side of the file check, got {:?}", + plan.summary() + ); } #[test] From 9bf544e187f849035c9b423b66a139ea4918b975 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Wed, 16 Sep 2026 12:34:57 +0200 Subject: [PATCH 4/5] fix(release): check versioned files only on the path that writes them --- src/monorepo/run/plan.rs | 153 +++++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 53 deletions(-) diff --git a/src/monorepo/run/plan.rs b/src/monorepo/run/plan.rs index 8c840673..aa0cc4ce 100644 --- a/src/monorepo/run/plan.rs +++ b/src/monorepo/run/plan.rs @@ -271,38 +271,35 @@ pub(super) fn commits_for_package( } fn ensure_versioned_files_exist(pkg: &PackageConfig, root: &Path) -> Result<()> { - for vf in &pkg.versioned_files { - // A handler that does not write the file cannot be caught out by its - // absence. gomod is the case: the version lives in the git tag, its - // write_version is a no-op, and a go.mod entry is only there to name - // the format. - if !crate::formats::get_handler(&vf.format).modifies_file() { - continue; - } - if root.join(&vf.path).exists() { - continue; - } - return Err(anyhow!( - "package \"{name}\": versioned file \"{path}\" does not exist, so the release \ - would fail when it tries to write it.\n \ - Paths in versionedFiles are relative to the repository root, not to the \ - package's own path. Did you mean \"{suggestion}\"?", - name = pkg.name, - path = vf.path, - suggestion = suggested_versioned_path(pkg, &vf.path), - )) - .error_code(error_code::CONFIG_MISSING_VERSIONED_FILE); - } - Ok(()) + let missing = pkg.versioned_files.iter().find(|vf| { + crate::formats::get_handler(&vf.format).modifies_file() && !root.join(&vf.path).exists() + }); + let Some(vf) = missing else { + return Ok(()); + }; + let hint = suggested_versioned_path(pkg, &vf.path) + .map(|suggestion| { + format!( + "\n Paths in versionedFiles are relative to the repository root, not to the \ + package's own path. Did you mean \"{suggestion}\"?" + ) + }) + .unwrap_or_default(); + Err(anyhow!( + "package \"{name}\": versioned file \"{path}\" does not exist, so the release \ + would fail when it tries to write it.{hint}", + name = pkg.name, + path = vf.path, + )) + .error_code(error_code::CONFIG_MISSING_VERSIONED_FILE) } -fn suggested_versioned_path(pkg: &PackageConfig, path: &str) -> String { +fn suggested_versioned_path(pkg: &PackageConfig, path: &str) -> Option { let prefix = pkg.path.trim_end_matches('/'); - if prefix.is_empty() || prefix == "." || path.starts_with(prefix) { - path.to_string() - } else { - format!("{prefix}/{path}") + if prefix.is_empty() || prefix == "." || Path::new(path).starts_with(prefix) { + return None; } + Some(format!("{prefix}/{path}")) } pub(super) fn compute_plan( @@ -336,8 +333,6 @@ pub(super) fn compute_plan( tags_for_package(inputs.all_tags, &tag_search_prefix) }); - ensure_versioned_files_exist(pkg, inputs.root)?; - let file_source = pkg.versioned_files.first().and_then(|vf| { read_version(vf, inputs.root) .ok() @@ -438,6 +433,8 @@ pub(super) fn compute_plan( let tag = pkg.tag_for_version(&config.workspace, is_monorepo, &new_version); + ensure_versioned_files_exist(pkg, inputs.root)?; + Ok(PackagePlan::Bump(Box::new(PackageBump { recovered, current_version, @@ -772,7 +769,7 @@ mod tests { ); } - fn missing_file_fixture(pkg_path: &str, versioned_path: &str) -> (Fixture, Vec) { + fn missing_file_fixture(versioned_path: &str, api_commit: &str) -> (Fixture, Vec) { let (dir, repo) = init_repo(); let root = dir.path().to_path_buf(); write_pkg(&root, "api", "2.4.0"); @@ -781,19 +778,13 @@ mod tests { &root, "", &format!( - r#"{{"name":"api","path":"{pkg_path}","versionedFiles":[{{"path":"{versioned_path}","format":"toml"}}]}}, - {{"name":"sdk","path":"sdk","versionedFiles":[{{"path":"sdk/Cargo.toml","format":"toml"}}]}}"# + r#"{{"name":"api","path":"api","versionedFiles":[{{"path":"{versioned_path}","format":"toml"}}]}}, + {{"name":"sdk","path":"sdk","versionedFiles":[{{"path":"sdk/Missing.toml","format":"toml"}}]}}"# ), ); git(&root, &["add", "-A"]); commit_file(&root, "seed.txt", "x", "chore: seed", 1_950_000_000); - commit_file( - &root, - "api/endpoint.rs", - "x", - "feat(api): add an endpoint", - 1_950_000_100, - ); + commit_file(&root, "api/endpoint.rs", "x", api_commit, 1_950_000_100); let fx = build_fixture(root, dir, repo); let changed_files = get_changed_files(&fx.repo).unwrap(); (fx, changed_files) @@ -823,12 +814,13 @@ mod tests { compute_plan(&fx.repo, pkg, &inputs) } + fn package(name: &str, path: &str) -> PackageConfig { + serde_json::from_str(&format!(r#"{{"name":"{name}","path":"{path}"}}"#)).unwrap() + } + #[test] fn a_versioned_file_that_does_not_exist_fails_the_plan_rather_than_bumping_nothing() { - // versionedFiles paths are relative to the repository root. Giving one - // relative to the package used to plan a bump, tag it, and write no - // file, leaving the repo tagged at a version no manifest carries. - let (fx, changed) = missing_file_fixture("api", "Cargo.toml"); + let (fx, changed) = missing_file_fixture("Cargo.toml", "feat(api): add an endpoint"); let err = match plan_result(&fx, &changed, "api") { Ok(plan) => panic!( @@ -840,14 +832,14 @@ mod tests { let msg = format!("{err:?}"); assert!(msg.contains("does not exist"), "{msg}"); assert!( - msg.contains("api/Cargo.toml"), + msg.contains("Did you mean \"api/Cargo.toml\""), "the error should point at the repo-root path it probably meant: {msg}" ); } #[test] fn a_versioned_file_that_exists_still_plans_normally() { - let (fx, changed) = missing_file_fixture("api", "api/Cargo.toml"); + let (fx, changed) = missing_file_fixture("api/Cargo.toml", "feat(api): add an endpoint"); let plan = plan_result(&fx, &changed, "api") .unwrap_or_else(|e| panic!("a correct config must still plan: {e:?}")); @@ -858,11 +850,28 @@ mod tests { ); } + #[test] + fn a_touched_package_with_nothing_to_release_is_skipped_not_failed() { + let (fx, changed) = missing_file_fixture("Cargo.toml", "chore(api): bump lint config"); + + let plan = plan_result(&fx, &changed, "api").unwrap_or_else(|e| { + panic!("a package this run will not write must not fail the release: {e:?}") + }); + assert!( + matches!( + plan, + PackagePlan::Skipped { + reason: SkipReason::NoReleasableCommits, + .. + } + ), + "expected api to be skipped for having nothing to release, got {:?}", + plan.summary() + ); + } + #[test] fn a_format_that_never_writes_the_file_does_not_need_it_to_exist() { - // go.mod carries no version: gomod reads it from the git tag and its - // write_version is a no-op, so a missing go.mod cannot cause the drift - // this check exists to catch. let (dir, repo) = init_repo(); let root = dir.path().to_path_buf(); write_pkg(&root, "mymod", "1.0.0"); @@ -900,10 +909,11 @@ mod tests { #[test] fn an_untouched_package_is_skipped_before_its_files_are_checked() { - // Scoping the check to packages this run would actually write keeps a - // partial or sparse checkout from failing a release for a package it - // was never going to touch. - let (fx, changed) = missing_file_fixture("api", "Cargo.toml"); + let (fx, changed) = missing_file_fixture("api/Cargo.toml", "feat(api): add an endpoint"); + assert!( + !fx.root.join("sdk/Missing.toml").exists(), + "sdk's versioned file must be absent, or this proves nothing" + ); let plan = plan_result(&fx, &changed, "sdk") .unwrap_or_else(|e| panic!("an untouched package must not fail: {e:?}")); @@ -920,6 +930,43 @@ mod tests { ); } + #[test] + fn the_path_hint_is_only_given_when_it_names_a_different_file() { + let dir = tempfile::tempdir().unwrap(); + let mut root_pkg = package("root", "."); + root_pkg.versioned_files = + serde_json::from_str(r#"[{"path":"Cargo.toml","format":"toml"}]"#).unwrap(); + + let msg = format!( + "{:?}", + ensure_versioned_files_exist(&root_pkg, dir.path()).unwrap_err() + ); + assert!(msg.contains("does not exist"), "{msg}"); + assert!( + !msg.contains("Did you mean"), + "suggesting the path the user already wrote says nothing: {msg}" + ); + } + + #[test] + fn a_suggestion_prefixes_the_package_path_only_when_it_is_missing() { + let api = package("api", "api"); + assert_eq!( + suggested_versioned_path(&api, "Cargo.toml").as_deref(), + Some("api/Cargo.toml") + ); + assert_eq!(suggested_versioned_path(&api, "api/Cargo.toml"), None); + assert_eq!( + suggested_versioned_path(&api, "apiv2/Cargo.toml").as_deref(), + Some("api/apiv2/Cargo.toml"), + "a sibling directory sharing the prefix is not inside the package" + ); + assert_eq!( + suggested_versioned_path(&package("root", "."), "Cargo.toml"), + None + ); + } + fn write_config_raw(dir: &Path, workspace: &str, packages: &str) { std::fs::write( dir.join(".ferrflow"), From 5587f2da1f0ddda6f1d699968ad7c939b096257c Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Wed, 16 Sep 2026 17:52:34 +0200 Subject: [PATCH 5/5] test(fixtures): seed version2.toml so multi-versioned-files is releasable --- tests/fixtures/definitions/multi-versioned-files.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/fixtures/definitions/multi-versioned-files.json b/tests/fixtures/definitions/multi-versioned-files.json index 89f4965b..5704928a 100644 --- a/tests/fixtures/definitions/multi-versioned-files.json +++ b/tests/fixtures/definitions/multi-versioned-files.json @@ -14,6 +14,12 @@ "tag": "v1.0.0" } ], + "hooks": [ + { + "path": "version2.toml", + "content": "[package]\nname = \"myapp\"\nversion = \"1.0.0\"\n" + } + ], "commits": [ { "message": "feat: add multi-format support", "files": ["src/main.rs"] } ],