diff --git a/.changeset/automatically_release_dependents_in_monorepos.md b/.changeset/automatically_release_dependents_in_monorepos.md new file mode 100644 index 000000000..f475aa295 --- /dev/null +++ b/.changeset/automatically_release_dependents_in_monorepos.md @@ -0,0 +1,18 @@ +--- +knope: minor +--- + +# Automatically release dependents when internal dependencies change + +In a monorepo, setting `update_internal_dependencies = "patch"` (or `"minor"`) on a package now releases that package whenever one of its in-repo dependencies releases. The new release gets a changesets-style `Dependencies` section listing what triggered it, and propagation is transitive—releases are applied in dependency order. + +Knope finds the relationships automatically: from each opted-in package's manifest (`Cargo.toml` or `package.json` dependencies), and from `versioned_files` entries that point at another package's files. For anything neither can see (for example, versions tracked only in a workspace-root manifest), declare the relationship explicitly with `internal_dependencies`: + +```toml +[packages.consumer] +versioned_files = ["consumer/Cargo.toml"] +update_internal_dependencies = "patch" +internal_dependencies = ["my-lib"] # usually unnecessary +``` + +The default is `"none"`—nothing changes unless a package opts in. See the [`update_internal_dependencies` documentation](https://knope.tech/reference/config-file/packages#update_internal_dependencies) for details. diff --git a/.changeset/internal_dependency_api_changes.md b/.changeset/internal_dependency_api_changes.md new file mode 100644 index 000000000..de777d0f6 --- /dev/null +++ b/.changeset/internal_dependency_api_changes.md @@ -0,0 +1,11 @@ +--- +versioning: major +config: major +--- + +# New APIs for internal dependency propagation + +To support automatic monorepo dependency updates: + +- `knope-versioning`: `ChangeSource` has a new `DependencyUpdate` variant (breaking for exhaustive matches). `Package::versioned_files()`, `Config::is_lock_file()`, `VersionedFile::package_name()`, and `VersionedFile::declares_dependency()` were added. +- `knope-config`: `Package` has new `update_internal_dependencies` and `internal_dependencies` fields (breaking for struct-literal construction), and the `InternalDependencyUpdate` enum was added. diff --git a/.changeset/prerelease_changelogs_only_contain_changes_since_the_last_prerelease.md b/.changeset/prerelease_changelogs_only_contain_changes_since_the_last_prerelease.md new file mode 100644 index 000000000..5e9da6b6c --- /dev/null +++ b/.changeset/prerelease_changelogs_only_contain_changes_since_the_last_prerelease.md @@ -0,0 +1,12 @@ +--- +knope: major +versioning: major +--- + +# Prerelease changelogs only contain changes since the last prerelease + +When running `PrepareRelease` with a prerelease label, Knope now collects conventional commits since the most recent tag of _any_ kind—including prereleases—instead of the last stable release. Iterative prerelease workflows (`alpha.1` → `alpha.2`) get changelogs containing only the commits added since the previous prerelease, and packages with no new commits are no longer re-released on every run. + +If the commits since the last prerelease imply a lower version than an existing prerelease line (for example, a fix following a feature that already shipped in `1.1.0-rc.0`), the new version continues the existing line (`1.1.0-rc.1`) rather than regressing below a version that has already been pre-released. This also applies to `knope-versioning`'s version calculation: a rule-derived version with no prerelease line of its own continues the nearest existing line above it. + +Stable releases are unaffected: they still summarize everything since the last stable release. diff --git a/.changeset/route_commits_to_packages_by_paths_touched.md b/.changeset/route_commits_to_packages_by_paths_touched.md new file mode 100644 index 000000000..f64993d44 --- /dev/null +++ b/.changeset/route_commits_to_packages_by_paths_touched.md @@ -0,0 +1,16 @@ +--- +knope: minor +--- + +# Route commits to packages by the paths they touch with `track_paths` + +Setting `track_paths = true` on a package routes conventional commits to it based on the files each commit changed, instead of relying on commit scopes. By default, a package's territory is the parent directories of its own `versioned_files`; set `paths` to override it: + +```toml +[packages.my-package] +versioned_files = ["my-package/Cargo.toml"] +track_paths = true +paths = ["my-package", "docs/my-package"] # optional override +``` + +Commits that touch multiple packages' territories apply to each of them, and commits touching no tracked territory are ignored. See the [`track_paths` documentation](https://knope.tech/reference/config-file/packages#track_paths) for details. diff --git a/.changeset/track_paths_api_changes.md b/.changeset/track_paths_api_changes.md new file mode 100644 index 000000000..0386e4137 --- /dev/null +++ b/.changeset/track_paths_api_changes.md @@ -0,0 +1,11 @@ +--- +versioning: major +config: major +--- + +# New APIs for path-based commit routing + +To support `track_paths`: + +- `knope-versioning`: the conventional commit `Commit` struct has a new public `files` field listing the paths each commit changed (breaking for struct-literal construction). +- `knope-config`: `Package` has new `track_paths` and `paths` fields (breaking for struct-literal construction). diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1e7fd5134..e01f20ccb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,6 +3,7 @@ on: push: branches: - main + - feat/* pull_request: {} merge_group: diff --git a/crates/knope-config/src/lib.rs b/crates/knope-config/src/lib.rs index 913e66bbf..189020251 100644 --- a/crates/knope-config/src/lib.rs +++ b/crates/knope-config/src/lib.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub use self::{ changelog_section::ChangelogSection, - package::{Asset, AssetNameError, Assets, Package, VersionedFile}, + package::{Asset, AssetNameError, Assets, InternalDependencyUpdate, Package, VersionedFile}, template::{Template, Variable}, }; pub use crate::release_notes::ReleaseNotes; diff --git a/crates/knope-config/src/package.rs b/crates/knope-config/src/package.rs index 1f88c705c..8b8ae1cb4 100644 --- a/crates/knope-config/src/package.rs +++ b/crates/knope-config/src/package.rs @@ -24,6 +24,45 @@ pub struct Package { pub assets: Option, #[serde(default, skip_serializing_if = "<&bool>::not")] pub ignore_go_major_versioning: bool, + /// What kind of bump this package gets when one of its internal monorepo dependencies bumps. + #[serde(default, skip_serializing_if = "InternalDependencyUpdate::is_default")] + pub update_internal_dependencies: InternalDependencyUpdate, + /// Names of other packages in this config that this package depends on. Usually Knope + /// derives these relationships from `versioned_files` `dependency` entries; use this to + /// declare ones it can't see (for example, versions tracked only in a shared workspace + /// manifest). + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub internal_dependencies: Vec, + /// If `true`, conventional commits are routed to this package by the files they changed + /// rather than (or in addition to) the commit's scope. + #[serde(default, skip_serializing_if = "<&bool>::not")] + pub track_paths: bool, + /// Explicit list of directories/files that belong to this package (used when + /// `track_paths` is `true`). If empty, falls back to the parent directories of the + /// package's own (non-dependency) `versioned_files`. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub paths: Vec, +} + +/// Policy for how this package is versioned when one of its internal monorepo dependencies +/// receives a release. +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum InternalDependencyUpdate { + /// Don't bump this package when an internal dependency bumps (the default). + #[default] + None, + /// Bump this package as a patch release when an internal dependency bumps. + Patch, + /// Bump this package as a minor release when an internal dependency bumps. + Minor, +} + +impl InternalDependencyUpdate { + #[must_use] + pub fn is_default(&self) -> bool { + matches!(self, Self::None) + } } /// A type that deserializes from either a single regex string or an array of regex strings. diff --git a/crates/knope-versioning/src/changes/conventional_commit.rs b/crates/knope-versioning/src/changes/conventional_commit.rs index f4e3a1575..1d63b1964 100644 --- a/crates/knope-versioning/src/changes/conventional_commit.rs +++ b/crates/knope-versioning/src/changes/conventional_commit.rs @@ -1,4 +1,5 @@ use git_conventional::{Commit as ConventionalCommit, Footer, Type}; +use relative_path::RelativePathBuf; use tracing::debug; use super::{Change, ChangeSource, ChangeType, GitInfo}; @@ -8,6 +9,9 @@ use crate::release_notes::Sections; pub struct Commit { pub message: String, pub info: Option, + /// Files changed by this commit relative to the repo root. Empty when unknown (e.g., + /// in tests that construct commits manually). + pub files: Vec, } /// Try to parse each commit message as a [conventional commit](https://www.conventionalcommits.org/). diff --git a/crates/knope-versioning/src/changes/mod.rs b/crates/knope-versioning/src/changes/mod.rs index 0a9f175fc..5ed303c15 100644 --- a/crates/knope-versioning/src/changes/mod.rs +++ b/crates/knope-versioning/src/changes/mod.rs @@ -172,8 +172,14 @@ impl From> for ChangeType { #[derive(Clone, Debug, Eq, PartialEq)] pub enum ChangeSource { - ConventionalCommit { description: String }, - ChangeFile { id: Arc }, + ConventionalCommit { + description: String, + }, + ChangeFile { + id: Arc, + }, + /// Synthesized by Knope when propagating a release through internal monorepo dependencies. + DependencyUpdate, } impl Display for ChangeSource { @@ -184,6 +190,7 @@ impl Display for ChangeSource { .. } => write!(f, "commit {message}"), Self::ChangeFile { id, .. } => write!(f, "changeset {}", id.to_file_name()), + Self::DependencyUpdate => write!(f, "dependency update"), } } } diff --git a/crates/knope-versioning/src/package.rs b/crates/knope-versioning/src/package.rs index 863ea303e..02c76b27e 100644 --- a/crates/knope-versioning/src/package.rs +++ b/crates/knope-versioning/src/package.rs @@ -193,6 +193,12 @@ impl Package { self.versions.latest() } + /// The versioned-file configs associated with this package. + #[must_use] + pub fn versioned_files(&self) -> &[Config] { + &self.versioned_files + } + /// Apply a Rule to a [`PackageVersion`], incrementing & resetting the correct components. /// /// # Versions 0.x diff --git a/crates/knope-versioning/src/release_notes/mod.rs b/crates/knope-versioning/src/release_notes/mod.rs index 8d58ed436..2829498e3 100644 --- a/crates/knope-versioning/src/release_notes/mod.rs +++ b/crates/knope-versioning/src/release_notes/mod.rs @@ -7,7 +7,12 @@ pub use release::Release; use serde::Deserialize; use time::{OffsetDateTime, macros::format_description}; -use crate::{Action, changes::Change, package, semver::Version}; +use crate::{ + Action, + changes::{Change, ChangeSource}, + package, + semver::Version, +}; mod changelog; mod config; @@ -46,7 +51,10 @@ impl ReleaseNotes { for (section_name, sources) in self.sections.iter() { let mut changes = changes .iter() - .filter(|change| sources.contains(&change.change_type)) + .filter(|change| { + sources.contains(&change.change_type) + && change.original_source != ChangeSource::DependencyUpdate + }) .sorted() .peekable(); if changes.peek().is_some() { @@ -59,6 +67,7 @@ impl ReleaseNotes { write_body(&mut notes, changes, &self.change_templates); } } + write_dependency_updates(&mut notes, changes); let release = Release { title: release_title(&version)?, @@ -95,6 +104,28 @@ impl ReleaseNotes { )] pub struct TimeError(#[from] time::error::Format); +/// Append a "Dependencies" section listing internal monorepo dependency bumps in changesets +/// style: a single bullet "Updated dependencies" with each `name@version` nested under it. +fn write_dependency_updates(out: &mut String, changes: &[Change]) { + let dep_changes: Vec<&Change> = changes + .iter() + .filter(|c| c.original_source == ChangeSource::DependencyUpdate) + .collect(); + if dep_changes.is_empty() { + return; + } + if !out.is_empty() { + out.push_str("\n\n"); + } + out.push_str("## Dependencies\n\n- Updated dependencies"); + for change in dep_changes { + if let Some(details) = change.details.as_ref() { + out.push('\n'); + out.push_str(details); + } + } +} + fn write_body<'change>( out: &mut String, changes: Peekable>, diff --git a/crates/knope-versioning/src/semver/package_versions.rs b/crates/knope-versioning/src/semver/package_versions.rs index 7039d3906..b6b32c03c 100644 --- a/crates/knope-versioning/src/semver/package_versions.rs +++ b/crates/knope-versioning/src/semver/package_versions.rs @@ -171,6 +171,21 @@ impl PackageVersions { .map(|(stable, _)| *stable) .unwrap_or_default() }; + // An existing prerelease may already target a higher stable version (for example, a + // feature shipped in an earlier prerelease and the changes since then are only + // fixes). If the rule-derived version has no prerelease line of its own, continue + // the nearest existing line above it instead of regressing below a version that has + // already been pre-released. A rule-derived version that matches an existing line + // keeps it, so deliberately-newer lines (e.g. a hand-tagged 2.0.0-rc) don't take + // over (see `pre_after_same_pre`). + let stable_component = if self.prereleases.contains_key(&stable_component) { + stable_component + } else { + self.prereleases + .range(stable_component..) + .next() + .map_or(stable_component, |(existing, _)| *existing) + }; let pre_version = self .prereleases .get(&stable_component) diff --git a/crates/knope-versioning/src/versioned_file/mod.rs b/crates/knope-versioning/src/versioned_file/mod.rs index 4646fb18c..3a2f0072f 100644 --- a/crates/knope-versioning/src/versioned_file/mod.rs +++ b/crates/knope-versioning/src/versioned_file/mod.rs @@ -117,6 +117,30 @@ impl VersionedFile { } } + /// The package name this file declares, if it's a manifest that declares one. + /// Currently supported for `Cargo.toml` and `package.json`. + #[must_use] + pub fn package_name(&self) -> Option { + match self { + VersionedFile::Cargo(cargo) => { + cargo::name_from_document(&cargo.document).map(String::from) + } + VersionedFile::PackageJson(package_json) => package_json.package_name(), + _ => None, + } + } + + /// Whether this file is a manifest declaring a dependency on `name`. + /// Currently supported for `Cargo.toml` and `package.json`. + #[must_use] + pub fn declares_dependency(&self, name: &str) -> bool { + match self { + VersionedFile::Cargo(cargo) => cargo::contains_dependency(&cargo.document, name), + VersionedFile::PackageJson(package_json) => package_json.declares_dependency(name), + _ => false, + } + } + #[must_use] pub fn path(&self) -> &RelativePathBuf { match self { @@ -495,6 +519,16 @@ impl Config { self.path.clone() } + /// Whether this file is a lock file (recording resolved versions) rather than a manifest + /// (declaring a package or its dependencies). + #[must_use] + pub fn is_lock_file(&self) -> bool { + matches!( + self.format, + Format::CargoLock | Format::PackageLockJson | Format::DenoLock + ) + } + #[must_use] pub fn to_pathbuf(&self) -> PathBuf { self.as_path().to_path("") diff --git a/crates/knope-versioning/src/versioned_file/package_json.rs b/crates/knope-versioning/src/versioned_file/package_json.rs index da39df9f0..97638b2fe 100644 --- a/crates/knope-versioning/src/versioned_file/package_json.rs +++ b/crates/knope-versioning/src/versioned_file/package_json.rs @@ -38,6 +38,22 @@ impl PackageJson { &self.path } + /// The `name` field of the manifest, if present. + pub(crate) fn package_name(&self) -> Option { + let json = serde_json::from_str::>(&self.raw).ok()?; + json.get("name")?.as_str().map(String::from) + } + + /// Whether the manifest declares a dependency (or dev dependency) on `name`. + pub(crate) fn declares_dependency(&self, name: &str) -> bool { + let Ok(json) = serde_json::from_str::>(&self.raw) else { + return false; + }; + ["dependencies", "devDependencies"] + .iter() + .any(|key| json.get(*key).and_then(|deps| deps.get(name)).is_some()) + } + pub(crate) fn set_version( mut self, new_version: &Version, diff --git a/crates/knope/src/config/package.rs b/crates/knope/src/config/package.rs index 55209362c..138825afa 100644 --- a/crates/knope/src/config/package.rs +++ b/crates/knope/src/config/package.rs @@ -17,7 +17,7 @@ use deno_path_util::url_to_file_path; use deno_semver::package::PackageKind; use glob::glob; use itertools::Itertools; -use knope_config::{Assets, ChangelogSection}; +use knope_config::{Assets, ChangelogSection, InternalDependencyUpdate}; use knope_versioning::{ ConfigError, UnknownFile, VersionedFileConfig, package, versioned_file::cargo, }; @@ -59,6 +59,10 @@ pub struct Package { pub(crate) extra_changelog_sections: Vec, pub(crate) assets: Option, pub(crate) ignore_go_major_versioning: bool, + pub(crate) update_internal_dependencies: InternalDependencyUpdate, + pub(crate) internal_dependencies: Vec, + pub(crate) track_paths: bool, + pub(crate) paths: Vec, } impl Package { @@ -181,6 +185,10 @@ impl Package { extra_changelog_sections: vec![], assets: None, ignore_go_major_versioning: false, + update_internal_dependencies: InternalDependencyUpdate::default(), + internal_dependencies: Vec::new(), + track_paths: false, + paths: Vec::new(), } }) .collect()) @@ -485,6 +493,10 @@ impl Package { extra_changelog_sections, assets, ignore_go_major_versioning, + update_internal_dependencies, + internal_dependencies, + track_paths, + paths, } = package; let versioned_files = versioned_files .into_iter() @@ -518,6 +530,10 @@ impl Package { extra_changelog_sections, assets, ignore_go_major_versioning, + update_internal_dependencies, + internal_dependencies, + track_paths, + paths, }) } } @@ -612,6 +628,10 @@ impl From for knope_config::Package { extra_changelog_sections: package.extra_changelog_sections, assets: package.assets, ignore_go_major_versioning: package.ignore_go_major_versioning, + update_internal_dependencies: package.update_internal_dependencies, + internal_dependencies: package.internal_dependencies, + track_paths: package.track_paths, + paths: package.paths, } } } diff --git a/crates/knope/src/integrations/git.rs b/crates/knope/src/integrations/git.rs index 4bcd0edd1..aa2651d17 100644 --- a/crates/knope/src/integrations/git.rs +++ b/crates/knope/src/integrations/git.rs @@ -407,9 +407,11 @@ pub(crate) fn get_commit_messages_after_tag(tag: Option<&str>) -> Result) -> Result Vec { + let Ok(new_tree) = commit.tree() else { + return Vec::new(); + }; + let parent_tree = commit.parent(0).ok().and_then(|parent| parent.tree().ok()); + let Ok(diff) = repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&new_tree), None) else { + return Vec::new(); + }; + let mut files = Vec::new(); + diff.foreach( + &mut |delta, _| { + for path in [delta.new_file().path(), delta.old_file().path()] + .into_iter() + .flatten() + { + if let Some(s) = path.to_str() { + let rel = RelativePathBuf::from(s.replace('\\', "/")); + if !files.contains(&rel) { + files.push(rel); + } + } + } + true + }, + None, + None, + None, + ) + .ok(); + files +} + fn commit_info(commit: &git2::Commit) -> Option { Some(GitInfo { author_name: commit.author().name().map(String::from)?, diff --git a/crates/knope/src/step/releases/conventional_commits.rs b/crates/knope/src/step/releases/conventional_commits.rs index 3ac434e1a..68c8cbcd9 100644 --- a/crates/knope/src/step/releases/conventional_commits.rs +++ b/crates/knope/src/step/releases/conventional_commits.rs @@ -5,17 +5,34 @@ use tracing::debug; use crate::integrations::git::{self, get_commit_messages_after_tag}; -pub(crate) fn get_conventional_commits_after_last_stable_version( +/// Collect conventional commits from Git for a single package. +/// +/// In stable mode (`prerelease_mode = false`) we look back to the most recent **stable** +/// release tag, since prereleases are not user-facing releases. +/// +/// In prerelease mode (`prerelease_mode = true`) we look back to the most recent tag of +/// any kind — including prereleases. This lets iterative prerelease workflows (e.g., +/// alpha.1 → alpha.2) consider only the commits added since the previous prerelease, +/// rather than re-summarizing every commit since the last stable. +pub(crate) fn get_conventional_commits_after_last_release( package_name: &package::Name, all_tags: &[String], + prerelease_mode: bool, ) -> Result, git::Error> { debug!( "Getting conventional commits since last release of package {}", package_name.as_custom().unwrap_or_default() ); - let tag = PackageVersions::from_tags(package_name.as_custom(), all_tags) - .stable() - .map(|target_version| ReleaseTag::new(&target_version.into(), package_name)); + let versions = PackageVersions::from_tags(package_name.as_custom(), all_tags); + let tag = if prerelease_mode { + versions + .latest() + .map(|target_version| ReleaseTag::new(&target_version, package_name)) + } else { + versions + .stable() + .map(|target_version| ReleaseTag::new(&target_version.into(), package_name)) + }; get_commit_messages_after_tag(tag.as_ref().map(ReleaseTag::as_str)) } diff --git a/crates/knope/src/step/releases/internal_deps.rs b/crates/knope/src/step/releases/internal_deps.rs new file mode 100644 index 000000000..ef3f8ebc7 --- /dev/null +++ b/crates/knope/src/step/releases/internal_deps.rs @@ -0,0 +1,234 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use knope_config::InternalDependencyUpdate; +use knope_versioning::{ + VersionedFile, + changes::{Change, ChangeSource, ChangeType}, + package::Name, + semver::Version, +}; +use relative_path::RelativePathBuf; +use tracing::debug; + +use super::Package; + +/// A bumped dependency that should be reflected in a dependent's release notes. +#[derive(Clone, Debug)] +pub(super) struct BumpedDependency { + pub name: Name, + pub new_version: Version, +} + +/// Build dependents-of edges: `dependents[D]` = indices of packages that depend on D. +/// +/// A package P "owns" a path if it has a `versioned_files` entry for that path with no +/// `dependency` set (i.e., the file defines P's own version). A `{ path, dependency }` entry +/// in package D's `versioned_files` means D's version gets written into that file, so when +/// the file is owned by some other package O, that implies the edge "O depends on D" +/// (regardless of the name D goes by inside the file). Lock files record versions rather +/// than declare dependencies, so they neither own nor imply edges. Packages can also declare +/// dependencies explicitly via `internal_dependencies` for relationships that aren't visible +/// in `versioned_files` (e.g., versions tracked only in a shared workspace manifest). +/// +/// For packages that opted in, Knope also reads the manifests themselves: if a package's +/// manifest (`Cargo.toml`, `package.json`) declares a dependency whose name matches another +/// package's manifest name, that relationship is detected with no extra configuration. +/// +/// Edges to dependents that opted out (`update_internal_dependencies = "none"`) are skipped +/// entirely: they can't receive propagation, and including them would only change the order +/// in which packages are processed. +pub(super) fn build_dependents( + packages: &[Package], + all_versioned_files: &[VersionedFile], +) -> HashMap> { + let mut owners: HashMap = HashMap::new(); + for (idx, pkg) in packages.iter().enumerate() { + for vf in pkg.versioning.versioned_files() { + if vf.dependency.is_none() && !vf.is_lock_file() { + owners.insert(vf.as_path(), idx); + } + } + } + let name_to_idx: HashMap<&str, usize> = packages + .iter() + .enumerate() + .map(|(idx, pkg)| (pkg.name().as_ref(), idx)) + .collect(); + + let mut edges: HashMap> = HashMap::new(); + // (dependent, dependency) pairs already recorded + let mut seen: HashSet<(usize, usize)> = HashSet::new(); + let mut add_edge = |dependency_idx: usize, dependent_idx: usize, seen: &mut HashSet<_>| { + let Some(dependency) = packages.get(dependency_idx) else { + return; + }; + let Some(dependent) = packages.get(dependent_idx) else { + return; + }; + if dependent_idx == dependency_idx + || dependent.update_internal_dependencies == InternalDependencyUpdate::None + || !seen.insert((dependent_idx, dependency_idx)) + { + return; + } + edges + .entry(dependency.name().clone()) + .or_default() + .push(dependent_idx); + }; + + for (dependency_idx, pkg) in packages.iter().enumerate() { + for vf in pkg.versioning.versioned_files() { + if vf.dependency.is_none() || vf.is_lock_file() { + continue; + } + let Some(&dependent_idx) = owners.get(&vf.as_path()) else { + continue; + }; + add_edge(dependency_idx, dependent_idx, &mut seen); + } + } + + for (dependent_idx, pkg) in packages.iter().enumerate() { + for dep_name in &pkg.internal_dependencies { + let Some(&dependency_idx) = name_to_idx.get(dep_name.as_str()) else { + debug!( + "internal_dependencies of {pkg} names unknown package {dep_name}; ignoring", + pkg = pkg.name() + ); + continue; + }; + add_edge(dependency_idx, dependent_idx, &mut seen); + } + } + + // Manifest-derived edges: each package's manifests (the parsed contents of its + // non-dependency, non-lock versioned files) declare dependencies by name; match those + // names against the other packages' manifest names. + let manifests: Vec> = packages + .iter() + .map(|pkg| { + pkg.versioning + .versioned_files() + .iter() + .filter(|config| config.dependency.is_none() && !config.is_lock_file()) + .filter_map(|config| { + all_versioned_files + .iter() + .find(|vf| *vf.path() == config.as_path()) + }) + .collect() + }) + .collect(); + let manifest_names: Vec> = manifests + .iter() + .map(|files| files.iter().filter_map(|vf| vf.package_name()).collect()) + .collect(); + for (dependent_idx, (pkg, files)) in packages.iter().zip(&manifests).enumerate() { + if pkg.update_internal_dependencies == InternalDependencyUpdate::None { + continue; + } + for (dependency_idx, names) in manifest_names.iter().enumerate() { + if dependency_idx == dependent_idx { + continue; + } + if names + .iter() + .any(|name| files.iter().any(|vf| vf.declares_dependency(name))) + { + add_edge(dependency_idx, dependent_idx, &mut seen); + } + } + } + edges +} + +/// Topologically sort package indices so that a package appears after all of its internal +/// dependencies. Falls back to original order on cycles (we still return every index). +#[expect( + clippy::indexing_slicing, + reason = "all indices come from 0..packages.len() or from `dependents` values that were built from those same indices" +)] +pub(super) fn topological_order( + packages: &[Package], + dependents: &HashMap>, +) -> Vec { + let n = packages.len(); + let mut in_degree = vec![0usize; n]; + for targets in dependents.values() { + for &t in targets { + if t < n { + in_degree[t] += 1; + } + } + } + // FIFO so that packages with no dependency relationship keep their config order. + let mut queue: VecDeque = (0..n).filter(|&i| in_degree[i] == 0).collect(); + let mut visited = vec![false; n]; + let mut order = Vec::with_capacity(n); + while let Some(idx) = queue.pop_front() { + if visited[idx] { + continue; + } + visited[idx] = true; + order.push(idx); + if let Some(targets) = dependents.get(packages[idx].name()) { + for &t in targets { + if t >= n || visited[t] { + continue; + } + in_degree[t] = in_degree[t].saturating_sub(1); + if in_degree[t] == 0 { + queue.push_back(t); + } + } + } + } + // Append any remaining (would happen if there's a cycle) — better to release them than panic. + for idx in 0..n { + if !visited[idx] { + debug!( + "Internal dependency cycle detected; falling back to source order for {pkg}", + pkg = packages[idx].name() + ); + order.push(idx); + } + } + order +} + +/// Build a synthetic [`Change`] representing "this package was bumped because its internal +/// dependencies updated." The change is grouped with any others on the same package, and +/// rendered as an "Updated dependencies" section in the release notes. +pub(super) fn synthetic_change( + policy: InternalDependencyUpdate, + bumps: &[BumpedDependency], +) -> Option { + match policy { + InternalDependencyUpdate::None => None, + InternalDependencyUpdate::Patch | InternalDependencyUpdate::Minor => { + let change_type = match policy { + InternalDependencyUpdate::Minor => ChangeType::Feature, + _ => ChangeType::Fix, + }; + let details = bumps + .iter() + .map(|bump| { + format!( + " - {name}@{version}", + name = bump.name, + version = bump.new_version + ) + }) + .collect::>() + .join("\n"); + Some(Change { + change_type, + summary: "Updated dependencies".to_string(), + details: Some(details), + original_source: ChangeSource::DependencyUpdate, + git: None, + }) + } + } +} diff --git a/crates/knope/src/step/releases/mod.rs b/crates/knope/src/step/releases/mod.rs index a3c8c7c12..f3c19651a 100644 --- a/crates/knope/src/step/releases/mod.rs +++ b/crates/knope/src/step/releases/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod changelog; pub(crate) mod conventional_commits; pub(crate) mod gitea; pub(crate) mod github; +mod internal_deps; pub(crate) mod package; pub(crate) mod semver; @@ -48,7 +49,9 @@ pub(crate) async fn prepare_release( Vec::new() }; - for package in &mut state.packages { + let mut per_package_changes: Vec> = + Vec::with_capacity(state.packages.len()); + for package in &state.packages { let mut changes = package.gather_changes( prepare_release, &state.all_git_tags, @@ -81,13 +84,11 @@ pub(crate) async fn prepare_release( )); } } - - let (all_versioned_files, actions) = - package.apply_release(&changes, prepare_release, state.all_versioned_files)?; - state.all_versioned_files = all_versioned_files; - state.pending_actions.extend(actions); + per_package_changes.push(changes); } + apply_releases_with_propagation(&mut state, per_package_changes, prepare_release)?; + let actions = state .all_versioned_files .drain(..) @@ -114,6 +115,60 @@ pub(crate) fn bump_version(state: RunType, rule: &Rule) -> Result>, + prepare_release: &PrepareRelease, +) -> Result<(), Error> { + let dependents = internal_deps::build_dependents(&state.packages, &state.all_versioned_files); + let order = internal_deps::topological_order(&state.packages, &dependents); + + // Accumulates dependency bumps to be folded into a dependent's synthetic change before it + // is applied. Keyed by the dependent's index in `state.packages`. + let mut pending_dep_bumps: Vec> = + vec![Vec::new(); state.packages.len()]; + + for idx in order { + let mut changes = std::mem::take(&mut per_package_changes[idx]); + + let bumps = std::mem::take(&mut pending_dep_bumps[idx]); + if !bumps.is_empty() { + let policy = state.packages[idx].update_internal_dependencies; + if let Some(synthetic) = internal_deps::synthetic_change(policy, &bumps) { + changes.push(synthetic); + } + } + + let package = &mut state.packages[idx]; + let bumped = !changes.is_empty() || package.override_version.is_some(); + let versioned_files = std::mem::take(&mut state.all_versioned_files); + let (all_versioned_files, actions) = + package.apply_release(&changes, prepare_release, versioned_files)?; + state.all_versioned_files = all_versioned_files; + state.pending_actions.extend(actions); + + if bumped + && let Some(new_version) = package.versioning.latest_version() + && let Some(targets) = dependents.get(package.name()) + { + for &target in targets { + pending_dep_bumps[target].push(internal_deps::BumpedDependency { + name: package.name().clone(), + new_version: new_version.clone(), + }); + } + } + } + Ok(()) +} + #[derive(Debug, Diagnostic, thiserror::Error)] pub(crate) enum Error { #[error("No packages are ready to release")] diff --git a/crates/knope/src/step/releases/package.rs b/crates/knope/src/step/releases/package.rs index 08134da40..81f27fcb1 100644 --- a/crates/knope/src/step/releases/package.rs +++ b/crates/knope/src/step/releases/package.rs @@ -3,7 +3,7 @@ use std::{fmt, fmt::Display, path::PathBuf}; use changesets::PackageChange; use indexmap::IndexMap; use itertools::Itertools; -use knope_config::{Assets, changelog_section::convert_to_versioning}; +use knope_config::{Assets, InternalDependencyUpdate, changelog_section::convert_to_versioning}; use knope_versioning::{ Action, GoVersioning, PackageNewError, VersionedFile, VersionedFileError, changes::{CHANGESET_DIR, Change}, @@ -31,6 +31,17 @@ pub(crate) struct Package { pub(crate) override_version: Option, pub(crate) assets: Option, pub(crate) go_versioning: GoVersioning, + /// Bump policy when one of this package's internal monorepo dependencies releases. + pub(crate) update_internal_dependencies: InternalDependencyUpdate, + /// Explicitly-declared names of packages this package depends on, supplementing the + /// relationships derived from `versioned_files`. + pub(crate) internal_dependencies: Vec, + /// When `true`, route conventional commits to this package by the files they touched. + pub(crate) track_paths: bool, + /// Directories/files that belong to this package for path-based commit filtering. When + /// `track_paths` is true and this is empty, parents of the package's `versioned_files` + /// are used. + pub(crate) paths: Vec, } impl Package { @@ -92,6 +103,10 @@ impl Package { GoVersioning::default() }, override_version: None, + update_internal_dependencies: package.update_internal_dependencies, + internal_dependencies: package.internal_dependencies, + track_paths: package.track_paths, + paths: package.paths, }) } @@ -119,10 +134,24 @@ impl Package { let commit_messages = if should_ignore { Vec::new() } else { - conventional_commits::get_conventional_commits_after_last_stable_version( + let commits = conventional_commits::get_conventional_commits_after_last_release( &self.versioning.name, all_tags, - )? + prepare_release.prerelease_label.is_some(), + )?; + if self.track_paths { + let territory = self.territory(); + debug!( + "track_paths enabled for {pkg}; territory: {territory:?}", + pkg = self.versioning.name, + ); + commits + .into_iter() + .filter(|commit| commit_touches_any(commit, &territory)) + .collect() + } else { + commits + } }; let changeset_dir = PathBuf::from(CHANGESET_DIR); @@ -156,6 +185,30 @@ impl Package { Ok(self.versioning.get_changes(change_files, &commit_messages)) } + /// Compute the package's "territory" — the set of repo-relative directories/files used + /// for path-based commit filtering. Falls back to parent directories of non-dependency + /// `versioned_files` when no explicit `paths` were configured. + fn territory(&self) -> Vec { + if !self.paths.is_empty() { + return self.paths.clone(); + } + let mut out: Vec = Vec::new(); + for vf in self.versioning.versioned_files() { + if vf.dependency.is_some() { + continue; + } + let path = vf.as_path(); + let dir = path.parent().map_or_else( + || RelativePathBuf::from(""), + relative_path::RelativePath::to_relative_path_buf, + ); + if !out.contains(&dir) { + out.push(dir); + } + } + out + } + pub(crate) fn apply_release( &mut self, changes: &[Change], @@ -180,6 +233,35 @@ impl Package { } } +/// Returns `true` if any file changed by `commit` is inside any path in `territory`. An +/// empty territory matches no files (so the package's commits are effectively dropped — the +/// safe interpretation for "`track_paths` but nothing configured to track"). +fn commit_touches_any( + commit: &knope_versioning::changes::conventional_commit::Commit, + territory: &[RelativePathBuf], +) -> bool { + if territory.is_empty() { + return false; + } + commit + .files + .iter() + .any(|file| territory.iter().any(|root| path_is_within(file, root))) +} + +/// Whether `file` is `root` or sits inside `root` (treating `root` as a directory prefix). +fn path_is_within(file: &RelativePathBuf, root: &RelativePathBuf) -> bool { + if root.as_str().is_empty() { + return true; + } + if file == root { + return true; + } + let root_str = root.as_str().trim_end_matches('/'); + let file_str = file.as_str(); + file_str.starts_with(root_str) && file_str.as_bytes().get(root_str.len()).copied() == Some(b'/') +} + pub(crate) fn execute_prepare_actions( actions: RunType>, stage_to_git: bool, @@ -273,6 +355,10 @@ impl Package { override_version: None, assets: None, go_versioning: GoVersioning::default(), + update_internal_dependencies: InternalDependencyUpdate::default(), + internal_dependencies: Vec::new(), + track_paths: false, + paths: Vec::new(), } } } diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/dryrun_stdout.log new file mode 100644 index 000000000..951dc4d68 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/dryrun_stdout.log @@ -0,0 +1,5 @@ +Would add the following to first/Cargo.toml: second-package.version = 0.1.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add files to git: + first/Cargo.toml + second/Cargo.toml diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/first/Cargo.toml new file mode 100644 index 000000000..5f8c39eba --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/knope.toml new file mode 100644 index 000000000..247c178c4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/knope.toml @@ -0,0 +1,5 @@ +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/mod.rs new file mode 100644 index 000000000..bd8b8965c --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/mod.rs @@ -0,0 +1,18 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// By default, a dependent gets its dependency strings updated but is _not_ released itself — +/// propagation is opt-in via `update_internal_dependencies`. +#[test] +fn dependents_are_not_bumped_by_default() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("feat(second-package): A feature in the dep"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/first/Cargo.toml new file mode 100644 index 000000000..65ca6dbc7 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/dryrun_stdout.log new file mode 100644 index 000000000..5d066cfd4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/dryrun_stdout.log @@ -0,0 +1,7 @@ +Would add the following to first/Cargo.toml: version = 1.0.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add the following to Cargo.toml: second-package.version = 0.1.1 +Would add files to git: + first/Cargo.toml + second/Cargo.toml + Cargo.toml diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/Cargo.toml new file mode 100644 index 000000000..ebeebe004 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["first", "second"] + +[workspace.dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/knope.toml new file mode 100644 index 000000000..e844b2c01 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/knope.toml @@ -0,0 +1,18 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +scopes = ["first-package"] +update_internal_dependencies = "patch" +internal_dependencies = ["second-package"] + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "Cargo.toml", dependency = "second-package" }, +] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/mod.rs new file mode 100644 index 000000000..18ab465db --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/mod.rs @@ -0,0 +1,19 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// When a dependency's version is only tracked in a shared workspace manifest (owned by no +/// package), Knope can't derive the relationship — `internal_dependencies` declares it +/// explicitly so the dependent still gets released. +#[test] +fn explicit_internal_dependencies_propagate() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/Cargo.toml new file mode 100644 index 000000000..801e3d27a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["first", "second"] + +[workspace.dependencies] +second-package = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/first/Cargo.toml new file mode 100644 index 000000000..ba564fc15 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_explicit/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/dryrun_stdout.log new file mode 100644 index 000000000..aa0b1bc5a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/dryrun_stdout.log @@ -0,0 +1,5 @@ +Would add the following to first/Cargo.toml: version = 1.0.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add files to git: + first/Cargo.toml + second/Cargo.toml diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/first/Cargo.toml new file mode 100644 index 000000000..5f8c39eba --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/knope.toml new file mode 100644 index 000000000..ec7f9f48a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/knope.toml @@ -0,0 +1,14 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +scopes = ["first-package"] +update_internal_dependencies = "patch" + +[packages.second-package] +versioned_files = ["second/Cargo.toml"] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/mod.rs new file mode 100644 index 000000000..b5c5e878f --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/mod.rs @@ -0,0 +1,20 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// With no `{ path, dependency }` entries and no `internal_dependencies`, the relationship +/// is read from the manifests themselves: `first-package`'s `Cargo.toml` declares a +/// dependency on `second-package`, so opting in to `update_internal_dependencies` is all +/// the configuration needed. +#[test] +fn relationships_are_read_from_manifests() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/first/Cargo.toml new file mode 100644 index 000000000..ab302e281 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.1" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_from_manifest/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/first/Cargo.toml new file mode 100644 index 000000000..5f8c39eba --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/knope.toml new file mode 100644 index 000000000..97a88da47 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/knope.toml @@ -0,0 +1,17 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +scopes = ["first-package"] +update_internal_dependencies = "minor" + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "first/Cargo.toml", dependency = "second-package" }, +] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/mod.rs new file mode 100644 index 000000000..4f4f7b529 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/mod.rs @@ -0,0 +1,16 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +#[test] +fn dependent_with_minor_policy_gets_minor_bump() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in the dep"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/first/Cargo.toml new file mode 100644 index 000000000..0dfa47de4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.1.0" + +[dependencies] +second-package = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_minor/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/dryrun_stdout.log new file mode 100644 index 000000000..71762d196 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/dryrun_stdout.log @@ -0,0 +1,5 @@ +Would add the following to first/Cargo.toml: second-lib.version = 0.1.1, version = 1.0.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add files to git: + first/Cargo.toml + second/Cargo.toml diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/first/Cargo.toml new file mode 100644 index 000000000..a37b9e42e --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-lib = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/knope.toml new file mode 100644 index 000000000..b932cfd73 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/knope.toml @@ -0,0 +1,17 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +scopes = ["first-package"] +update_internal_dependencies = "patch" + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "first/Cargo.toml", dependency = "second-lib" }, +] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/mod.rs new file mode 100644 index 000000000..06e28e960 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/mod.rs @@ -0,0 +1,20 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// Propagation works even when the name a dependency goes by inside a file (here: the +/// published crate name `second-lib`) differs from its package key (`second-package`) — +/// the relationship comes from which package's `versioned_files` contains the entry, not +/// from matching the name. +#[test] +fn propagates_when_crate_name_differs_from_package_key() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/first/Cargo.toml new file mode 100644 index 000000000..8344b3b44 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.1" + +[dependencies] +second-lib = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_name_mismatch/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/first/Cargo.toml new file mode 100644 index 000000000..5f8c39eba --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/knope.toml new file mode 100644 index 000000000..a60cb1e28 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/knope.toml @@ -0,0 +1,17 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +scopes = ["first-package"] +update_internal_dependencies = "none" + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "first/Cargo.toml", dependency = "second-package" }, +] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/mod.rs new file mode 100644 index 000000000..96e010f58 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/mod.rs @@ -0,0 +1,16 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +#[test] +fn dependent_with_none_policy_is_not_bumped() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in the dep"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/first/Cargo.toml new file mode 100644 index 000000000..65ca6dbc7 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_none/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/dryrun_stdout.log new file mode 100644 index 000000000..023aeb2aa --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/dryrun_stdout.log @@ -0,0 +1,5 @@ +Would add the following to Cargo.lock: second-renamed = 0.1.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add files to git: + Cargo.lock + second/Cargo.toml diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.lock b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.lock new file mode 100644 index 000000000..59df21f6c --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.lock @@ -0,0 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "first-package" +version = "1.0.0" + +[[package]] +name = "second-renamed" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/knope.toml new file mode 100644 index 000000000..af5e85c30 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/knope.toml @@ -0,0 +1,17 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml", "Cargo.lock"] +scopes = ["first-package"] +update_internal_dependencies = "patch" + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "Cargo.lock", dependency = "second-renamed" }, +] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/mod.rs new file mode 100644 index 000000000..cb9a3db03 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/mod.rs @@ -0,0 +1,19 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// Sharing a lock file is not a dependency relationship: `first-package` owns no manifest +/// that `second-package` writes into, so `second-package`'s release must not propagate to +/// it even though `first-package` opted into patch bumps. +#[test] +fn shared_lock_file_does_not_propagate() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/Cargo.lock b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/Cargo.lock new file mode 100644 index 000000000..659ad4f94 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/Cargo.lock @@ -0,0 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "first-package" +version = "1.0.0" + +[[package]] +name = "second-renamed" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_shared_lock/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/Cargo.toml new file mode 100644 index 000000000..e7d118cea --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["a", "b", "c"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/a/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/a/Cargo.toml new file mode 100644 index 000000000..26ceeeacf --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/a/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "a" +version = "1.0.0" + +[dependencies] +b = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/b/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/b/Cargo.toml new file mode 100644 index 000000000..6e6b507d7 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "b" +version = "1.0.0" + +[dependencies] +c = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/c/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/c/Cargo.toml new file mode 100644 index 000000000..34b515883 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/c/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "c" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/knope.toml new file mode 100644 index 000000000..1910ec6ff --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/in/knope.toml @@ -0,0 +1,19 @@ +[packages.a] +versioned_files = ["a/Cargo.toml"] +scopes = ["a"] +update_internal_dependencies = "patch" + +[packages.b] +versioned_files = ["b/Cargo.toml", { path = "a/Cargo.toml", dependency = "b" }] +scopes = ["b"] +update_internal_dependencies = "patch" + +[packages.c] +versioned_files = ["c/Cargo.toml", { path = "b/Cargo.toml", dependency = "c" }] +scopes = ["c"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/mod.rs new file mode 100644 index 000000000..c5b3c76d1 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/mod.rs @@ -0,0 +1,19 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// Verify that bumps propagate transitively: `c` bumps, `b` (depends on c) gets a patch bump, +/// then `a` (depends on b) also gets a patch bump. +#[test] +fn propagates_transitively() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("a/v1.0.0"), + Tag("b/v1.0.0"), + Tag("c/v1.0.0"), + Commit("feat(c)!: A breaking change in c"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/a/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/a/Cargo.toml new file mode 100644 index 000000000..bfdd0d115 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/a/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "a" +version = "1.0.1" + +[dependencies] +b = "1.0.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/b/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/b/Cargo.toml new file mode 100644 index 000000000..3204104be --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "b" +version = "1.0.1" + +[dependencies] +c = "2.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/c/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/c/Cargo.toml new file mode 100644 index 000000000..525903611 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_transitive/out/c/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "c" +version = "2.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/dryrun_stdout.log b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/dryrun_stdout.log new file mode 100644 index 000000000..b7c588dbe --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/dryrun_stdout.log @@ -0,0 +1,22 @@ +Would add the following to first/Cargo.toml: second-package.version = 0.1.1, version = 1.0.1 +Would add the following to second/Cargo.toml: version = 0.1.1 +Would add the following to second/CHANGELOG.md: +## 0.1.1 ([DATE]) + +### Features + +- A feature in the dep + +Would add the following to first/CHANGELOG.md: +## 1.0.1 ([DATE]) + +### Dependencies + +- Updated dependencies + - second-package@0.1.1 + +Would add files to git: + first/Cargo.toml + second/Cargo.toml + second/CHANGELOG.md + first/CHANGELOG.md diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/CHANGELOG.md b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/CHANGELOG.md new file mode 100644 index 000000000..825c32f0d --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/Cargo.toml new file mode 100644 index 000000000..5f8c39eba --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.0" + +[dependencies] +second-package = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/knope.toml new file mode 100644 index 000000000..a677466d0 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/knope.toml @@ -0,0 +1,19 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +changelog = "first/CHANGELOG.md" +scopes = ["first-package"] +update_internal_dependencies = "patch" + +[packages.second-package] +versioned_files = [ + "second/Cargo.toml", + { path = "first/Cargo.toml", dependency = "second-package" }, +] +changelog = "second/CHANGELOG.md" +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/CHANGELOG.md b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/CHANGELOG.md new file mode 100644 index 000000000..825c32f0d --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/mod.rs new file mode 100644 index 000000000..570ef1d17 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/mod.rs @@ -0,0 +1,16 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +#[test] +fn dependent_changelog_gets_updated_dependencies_section() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("feat(second-package): A feature in the dep"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/CHANGELOG.md b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/CHANGELOG.md new file mode 100644 index 000000000..46df97215 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog +## 1.0.1 ([DATE]) + +### Dependencies + +- Updated dependencies + - second-package@0.1.1 diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/Cargo.toml new file mode 100644 index 000000000..8849f19e5 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/first/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "first-package" +version = "1.0.1" + +[dependencies] +second-package = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/CHANGELOG.md b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/CHANGELOG.md new file mode 100644 index 000000000..fe258d4cb --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog +## 0.1.1 ([DATE]) + +### Features + +- A feature in the dep diff --git a/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/internal_dependency_updates_with_changelog/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/mod.rs index d0fd21359..3e7707fe6 100644 --- a/crates/knope/tests/prepare_release/cargo/workspace/mod.rs +++ b/crates/knope/tests/prepare_release/cargo/workspace/mod.rs @@ -1,3 +1,16 @@ mod full; +mod internal_dependency_updates; +mod internal_dependency_updates_explicit; +mod internal_dependency_updates_from_manifest; +mod internal_dependency_updates_minor; +mod internal_dependency_updates_name_mismatch; +mod internal_dependency_updates_none; +mod internal_dependency_updates_shared_lock; +mod internal_dependency_updates_transitive; +mod internal_dependency_updates_with_changelog; mod no_lock; mod shared_version; +mod track_paths_basic; +mod track_paths_explicit; +mod track_paths_multi; +mod track_paths_orphan; diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/lib.rs new file mode 100644 index 000000000..4094bb1d8 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/first/lib.rs @@ -0,0 +1 @@ +// Source file for the first package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/knope.toml new file mode 100644 index 000000000..348a205d4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/knope.toml @@ -0,0 +1,13 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +track_paths = true + +[packages.second-package] +versioned_files = ["second/Cargo.toml"] +track_paths = true + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/lib.rs new file mode 100644 index 000000000..2825a4be1 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/in/second/lib.rs @@ -0,0 +1 @@ +// Source file for the second package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/mod.rs new file mode 100644 index 000000000..8c83cc443 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/mod.rs @@ -0,0 +1,21 @@ +use crate::helpers::{ + GitCommand::{Add, Commit, Tag}, + TestCase, +}; + +/// With `track_paths` and no explicit `paths`, commits are routed to packages by the +/// parent directories of their versioned files. +#[test] +fn commits_route_by_versioned_file_directories() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Add("first/lib.rs"), + Commit("feat: A feature in first"), + Add("second/lib.rs"), + Commit("fix: A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/first/Cargo.toml new file mode 100644 index 000000000..85dc7e92a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_basic/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/docs/guide.md b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/docs/guide.md new file mode 100644 index 000000000..13070a9f0 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/docs/guide.md @@ -0,0 +1,3 @@ +# Guide + +Documentation owned by the first package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/knope.toml new file mode 100644 index 000000000..fb3601a01 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/knope.toml @@ -0,0 +1,14 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +track_paths = true +paths = ["first", "docs"] + +[packages.second-package] +versioned_files = ["second/Cargo.toml"] +track_paths = true + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/lib.rs new file mode 100644 index 000000000..2825a4be1 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/in/second/lib.rs @@ -0,0 +1 @@ +// Source file for the second package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/mod.rs new file mode 100644 index 000000000..b9dc89882 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/mod.rs @@ -0,0 +1,21 @@ +use crate::helpers::{ + GitCommand::{Add, Commit, Tag}, + TestCase, +}; + +/// Explicit `paths` override the versioned-file fallback: a commit touching `docs/` routes +/// to the first package because `docs` is in its configured paths. +#[test] +fn explicit_paths_override_versioned_file_directories() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Add("docs/guide.md"), + Commit("feat: A documented feature for first"), + Add("second/lib.rs"), + Commit("fix: A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/first/Cargo.toml new file mode 100644 index 000000000..85dc7e92a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_explicit/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/lib.rs new file mode 100644 index 000000000..4094bb1d8 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/first/lib.rs @@ -0,0 +1 @@ +// Source file for the first package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/knope.toml new file mode 100644 index 000000000..348a205d4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/knope.toml @@ -0,0 +1,13 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +track_paths = true + +[packages.second-package] +versioned_files = ["second/Cargo.toml"] +track_paths = true + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/lib.rs new file mode 100644 index 000000000..2825a4be1 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/in/second/lib.rs @@ -0,0 +1 @@ +// Source file for the second package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/mod.rs new file mode 100644 index 000000000..b8a7bc255 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/mod.rs @@ -0,0 +1,19 @@ +use crate::helpers::{ + GitCommand::{Add, Commit, Tag}, + TestCase, +}; + +/// A single commit touching files in multiple packages' territories applies to all of them. +#[test] +fn commit_touching_multiple_territories_applies_to_each() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Add("first/lib.rs"), + Add("second/lib.rs"), + Commit("feat: A cross-cutting feature"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/first/Cargo.toml new file mode 100644 index 000000000..85dc7e92a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/second/Cargo.toml new file mode 100644 index 000000000..81c561431 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_multi/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.1" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/Cargo.toml new file mode 100644 index 000000000..b7939ff7b --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["first", "second"] diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/README.md b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/README.md new file mode 100644 index 000000000..392fb9448 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/README.md @@ -0,0 +1,3 @@ +# Workspace + +A file that belongs to no package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/Cargo.toml new file mode 100644 index 000000000..6efed9837 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.0.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/lib.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/lib.rs new file mode 100644 index 000000000..4094bb1d8 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/first/lib.rs @@ -0,0 +1 @@ +// Source file for the first package. diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/knope.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/knope.toml new file mode 100644 index 000000000..348a205d4 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/knope.toml @@ -0,0 +1,13 @@ +[packages.first-package] +versioned_files = ["first/Cargo.toml"] +track_paths = true + +[packages.second-package] +versioned_files = ["second/Cargo.toml"] +track_paths = true + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/in/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/mod.rs b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/mod.rs new file mode 100644 index 000000000..71ff4f2c2 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/mod.rs @@ -0,0 +1,22 @@ +use crate::helpers::{ + GitCommand::{Add, Commit, Tag}, + TestCase, +}; + +/// Commits touching only files outside every package's territory are dropped: the breaking +/// change to the README must not cause a major bump anywhere, and a package with no +/// matching commits is not released at all. +#[test] +fn commits_outside_all_territories_are_ignored() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Add("first/lib.rs"), + Commit("feat: A feature in first"), + Add("README.md"), + Commit("fix!: A breaking change to repo infrastructure"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/first/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/first/Cargo.toml new file mode 100644 index 000000000..85dc7e92a --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/first/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "first-package" +version = "1.1.0" diff --git a/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/second/Cargo.toml b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/second/Cargo.toml new file mode 100644 index 000000000..f2f748622 --- /dev/null +++ b/crates/knope/tests/prepare_release/cargo/workspace/track_paths_orphan/out/second/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "second-package" +version = "0.1.0" diff --git a/crates/knope/tests/prepare_release/go_modules/major_version_directories/dryrun_stdout.log b/crates/knope/tests/prepare_release/go_modules/major_version_directories/dryrun_stdout.log index e34dee2ed..4938782a3 100644 --- a/crates/knope/tests/prepare_release/go_modules/major_version_directories/dryrun_stdout.log +++ b/crates/knope/tests/prepare_release/go_modules/major_version_directories/dryrun_stdout.log @@ -13,14 +13,14 @@ Getting conventional commits since last release of package v1 No tags found starting with v1/v Using ALL commits Only checking commits with scopes: ["v1"] -Determining new version for v1 -commit fix(v1): A fix - implies rule PATCH -Using PATCH rule to bump from 1.0.0 to 1.0.1 Getting conventional commits since last release of package v2 No tags found starting with v2/v Using ALL commits Only checking commits with scopes: ["v2"] +Determining new version for v1 +commit fix(v1): A fix + implies rule PATCH +Using PATCH rule to bump from 1.0.0 to 1.0.1 Determining new version for v2 commit feat(v2): New feature implies rule MINOR diff --git a/crates/knope/tests/prepare_release/go_modules/major_version_directories/stdout.log b/crates/knope/tests/prepare_release/go_modules/major_version_directories/stdout.log index 36a98f417..cf5ba2524 100644 --- a/crates/knope/tests/prepare_release/go_modules/major_version_directories/stdout.log +++ b/crates/knope/tests/prepare_release/go_modules/major_version_directories/stdout.log @@ -13,14 +13,14 @@ Getting conventional commits since last release of package v1 No tags found starting with v1/v Using ALL commits Only checking commits with scopes: ["v1"] -Determining new version for v1 -commit fix(v1): A fix - implies rule PATCH -Using PATCH rule to bump from 1.0.0 to 1.0.1 Getting conventional commits since last release of package v2 No tags found starting with v2/v Using ALL commits Only checking commits with scopes: ["v2"] +Determining new version for v1 +commit fix(v1): A fix + implies rule PATCH +Using PATCH rule to bump from 1.0.0 to 1.0.1 Determining new version for v2 commit feat(v2): New feature implies rule MINOR diff --git a/crates/knope/tests/prepare_release/handle_pre_versions_that_are_too_new/dryrun_stdout.log b/crates/knope/tests/prepare_release/handle_pre_versions_that_are_too_new/dryrun_stdout.log index 84212e849..b34655234 100644 --- a/crates/knope/tests/prepare_release/handle_pre_versions_that_are_too_new/dryrun_stdout.log +++ b/crates/knope/tests/prepare_release/handle_pre_versions_that_are_too_new/dryrun_stdout.log @@ -4,7 +4,6 @@ Would add the following to CHANGELOG.md: ### Features -- A new feature - Another new feature Would add files to git: diff --git a/crates/knope/tests/prepare_release/mod.rs b/crates/knope/tests/prepare_release/mod.rs index baa370d25..d282d56f2 100644 --- a/crates/knope/tests/prepare_release/mod.rs +++ b/crates/knope/tests/prepare_release/mod.rs @@ -29,6 +29,7 @@ mod readme_version; mod release_after_prerelease; mod scopes; mod second_prerelease; +mod second_prerelease_after_distinct_stable; mod tauri_conf_json; mod unknown_versioned_file_format; mod verbose; diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/dryrun_stdout.log b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/dryrun_stdout.log new file mode 100644 index 000000000..d3acd0d26 --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/dryrun_stdout.log @@ -0,0 +1,5 @@ +Would add the following to first/package.json: version = 1.0.1 +Would add the following to second/package.json: version = 0.1.1 +Would add files to git: + first/package.json + second/package.json diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/first/package.json b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/first/package.json new file mode 100644 index 000000000..cbef58687 --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/first/package.json @@ -0,0 +1,7 @@ +{ + "name": "first-package", + "version": "1.0.0", + "dependencies": { + "second-package": "^0.1.0" + } +} diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/knope.toml b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/knope.toml new file mode 100644 index 000000000..24377b0a9 --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/knope.toml @@ -0,0 +1,14 @@ +[packages.first-package] +versioned_files = ["first/package.json"] +scopes = ["first-package"] +update_internal_dependencies = "patch" + +[packages.second-package] +versioned_files = ["second/package.json"] +scopes = ["second-package"] + +[[workflows]] +name = "release" + +[[workflows.steps]] +type = "PrepareRelease" diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/second/package.json b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/second/package.json new file mode 100644 index 000000000..83f53a9aa --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/in/second/package.json @@ -0,0 +1,4 @@ +{ + "name": "second-package", + "version": "0.1.0" +} diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/mod.rs b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/mod.rs new file mode 100644 index 000000000..360996c49 --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/mod.rs @@ -0,0 +1,18 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// Same as the Cargo workspace test, but for `package.json` manifests: the dependency on +/// `second-package` is read from `first/package.json` with no extra configuration. +#[test] +fn relationships_are_read_from_package_json() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("first-package/v1.0.0"), + Tag("second-package/v0.1.0"), + Commit("fix(second-package): A fix in second"), + ]) + .run("release"); +} diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/first/package.json b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/first/package.json new file mode 100644 index 000000000..501f947ea --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/first/package.json @@ -0,0 +1,7 @@ +{ + "name": "first-package", + "version": "1.0.1", + "dependencies": { + "second-package": "^0.1.0" + } +} \ No newline at end of file diff --git a/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/second/package.json b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/second/package.json new file mode 100644 index 000000000..744353728 --- /dev/null +++ b/crates/knope/tests/prepare_release/npm/internal_dependency_from_manifest/out/second/package.json @@ -0,0 +1,4 @@ +{ + "name": "second-package", + "version": "0.1.1" +} \ No newline at end of file diff --git a/crates/knope/tests/prepare_release/npm/mod.rs b/crates/knope/tests/prepare_release/npm/mod.rs index 3e417d914..15f4e8e25 100644 --- a/crates/knope/tests/prepare_release/npm/mod.rs +++ b/crates/knope/tests/prepare_release/npm/mod.rs @@ -1,3 +1,4 @@ mod dependencies; +mod internal_dependency_from_manifest; mod package; mod workspaces; diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/dryrun_stdout.log b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/dryrun_stdout.log new file mode 100644 index 000000000..0f5bdcdaf --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/dryrun_stdout.log @@ -0,0 +1,11 @@ +Would add the following to Cargo.toml: version = 1.1.0-rc.1 +Would add the following to CHANGELOG.md: +## 1.1.0-rc.1 ([DATE]) + +### Fixes + +- A fix after the first RC + +Would add files to git: + Cargo.toml + CHANGELOG.md diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/CHANGELOG.md b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/CHANGELOG.md new file mode 100644 index 000000000..11d97d61a --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.1.0-rc.0 + +### Features + +- New feature in first RC diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/Cargo.toml b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/Cargo.toml new file mode 100644 index 000000000..9bac9dae3 --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "default" +version = "1.1.0-rc.0" diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/knope.toml b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/knope.toml new file mode 100644 index 000000000..b06f62c8e --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/in/knope.toml @@ -0,0 +1,10 @@ +[package] +versioned_files = ["Cargo.toml"] +changelog = "CHANGELOG.md" + +[[workflows]] +name = "prerelease" + +[[workflows.steps]] +type = "PrepareRelease" +prerelease_label = "rc" diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/mod.rs b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/mod.rs new file mode 100644 index 000000000..d99daeaa4 --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/mod.rs @@ -0,0 +1,21 @@ +use crate::helpers::{ + GitCommand::{Commit, Tag}, + TestCase, +}; + +/// A second prerelease where the commits since the last prerelease imply a _lower_ stable +/// target (a fix) than the existing prerelease line (which shipped a feature). The new +/// prerelease must continue the existing line (1.1.0-rc.1), not regress to 1.0.1-rc.0, +/// and its changelog must only contain the new commits. +#[test] +fn test() { + TestCase::new(file!()) + .git(&[ + Commit("Initial commit"), + Tag("v1.0.0"), + Commit("feat: New feature in first RC"), + Tag("v1.1.0-rc.0"), + Commit("fix: A fix after the first RC"), + ]) + .run("prerelease"); +} diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/CHANGELOG.md b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/CHANGELOG.md new file mode 100644 index 000000000..a982a4384 --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/CHANGELOG.md @@ -0,0 +1,11 @@ +## 1.1.0-rc.1 ([DATE]) + +### Fixes + +- A fix after the first RC + +## 1.1.0-rc.0 + +### Features + +- New feature in first RC diff --git a/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/Cargo.toml b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/Cargo.toml new file mode 100644 index 000000000..94216a5f7 --- /dev/null +++ b/crates/knope/tests/prepare_release/second_prerelease_after_distinct_stable/out/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "default" +version = "1.1.0-rc.1" diff --git a/crates/knope/tests/prepare_release/verbose/dryrun_stdout.log b/crates/knope/tests/prepare_release/verbose/dryrun_stdout.log index 449c28c22..ae770243d 100644 --- a/crates/knope/tests/prepare_release/verbose/dryrun_stdout.log +++ b/crates/knope/tests/prepare_release/verbose/dryrun_stdout.log @@ -9,6 +9,9 @@ Running step PrepareRelease(PrepareRelease { prerelease_label: None, allow_empty Getting conventional commits since last release of package first Using commits since tag first/v1.2.3 Only checking commits with scopes: ["first"] +Getting conventional commits since last release of package second +Using commits since tag second/v0.4.6 +Only checking commits with scopes: ["second"] Determining new version for first commit feat: A feature implies rule MINOR @@ -33,9 +36,6 @@ changeset breaking_change.md changeset feature.md implies rule MINOR Using MAJOR rule to bump from 1.2.3 to 2.0.0 -Getting conventional commits since last release of package second -Using commits since tag second/v0.4.6 -Only checking commits with scopes: ["second"] Determining new version for second commit feat: A feature implies rule MINOR diff --git a/crates/knope/tests/prepare_release/verbose/stdout.log b/crates/knope/tests/prepare_release/verbose/stdout.log index 611b19462..cfd689e06 100644 --- a/crates/knope/tests/prepare_release/verbose/stdout.log +++ b/crates/knope/tests/prepare_release/verbose/stdout.log @@ -9,6 +9,9 @@ Running step PrepareRelease(PrepareRelease { prerelease_label: None, allow_empty Getting conventional commits since last release of package first Using commits since tag first/v1.2.3 Only checking commits with scopes: ["first"] +Getting conventional commits since last release of package second +Using commits since tag second/v0.4.6 +Only checking commits with scopes: ["second"] Determining new version for first commit feat: A feature implies rule MINOR @@ -33,9 +36,6 @@ changeset breaking_change.md changeset feature.md implies rule MINOR Using MAJOR rule to bump from 1.2.3 to 2.0.0 -Getting conventional commits since last release of package second -Using commits since tag second/v0.4.6 -Only checking commits with scopes: ["second"] Determining new version for second commit feat: A feature implies rule MINOR diff --git a/docs/src/content/docs/recipes/updating-dependencies.md b/docs/src/content/docs/recipes/updating-dependencies.md index 64d762bcc..1ba866286 100644 --- a/docs/src/content/docs/recipes/updating-dependencies.md +++ b/docs/src/content/docs/recipes/updating-dependencies.md @@ -65,3 +65,33 @@ versioned_files = [ You can use this same `dependency` field to override the name of a package updated in a `Cargo.lock`. ::: + +## Releasing dependents automatically + +Wiring up `versioned_files` keeps the dependency _string_ inside dependent packages in sync, +but by default each dependent must still have its own [Changeset](/reference/concepts/change) +to actually trigger a release of that dependent. + +To release dependents automatically whenever one of their internal dependencies releases, set +`update_internal_dependencies` on the dependent's package config to `"patch"` or `"minor"`. +See the +[`update_internal_dependencies` reference](/reference/config-file/packages#update_internal_dependencies) +for details. The default is `"none"`: Knope keeps dependency strings in sync but creates no +release for the dependent. + +For example, take the earlier setup where `knope` depends on `knope-versioning`. Set +`update_internal_dependencies = "patch"` on the `knope` package. Now releasing +`knope-versioning` also releases `knope` as a patch, and the new `knope` release notes +include a `Dependencies` section that lists the bumped dependencies. + +:::caution + +Knope finds the dependency relationships by reading each opted-in package's manifests +(`Cargo.toml`, `package.json`) and from `versioned_files` entries that point at another +package's files. Sometimes a relationship isn't visible to either. For example, you might +track the version only in the workspace-root `Cargo.toml` via `[workspace.dependencies]`. In +those cases, declare it explicitly with +[`internal_dependencies`](/reference/config-file/packages#internal_dependencies) on the +dependent's package config. + +::: diff --git a/docs/src/content/docs/reference/Config File/packages.mdx b/docs/src/content/docs/reference/Config File/packages.mdx index 78debbe78..433160ad1 100644 --- a/docs/src/content/docs/reference/Config File/packages.mdx +++ b/docs/src/content/docs/reference/Config File/packages.mdx @@ -486,3 +486,132 @@ ignore_go_major_versioning = true To maintain multiple major versions of a Go module, check out [this recipe](/recipes/multiple-major-go-versions) ::: + +## `update_internal_dependencies` + +When a package is part of a monorepo and depends on another package in the same repo, +Knope can automatically release the dependent whenever the dependency releases. + +Set `update_internal_dependencies` on the dependent's package config to control the bump: + +- `"none"` (default): don't release the dependent when its dependency changes. Knope still + writes the dependency's new version into the dependent's files (keeping the build + consistent), but creates no version bump or release notes for the dependent. +- `"patch"`: release the dependent as a patch bump. +- `"minor"`: release the dependent as a minor bump. + +```toml title="knope.toml" +[packages.consumer] +versioned_files = ["consumer/Cargo.toml"] +update_internal_dependencies = "patch" # or "minor"; defaults to "none" +``` + +When this propagation triggers a release, Knope adds a "Dependencies" section to the +dependent's release notes, in the style of the +[Changeset](https://github.com/changesets/changesets) tool: + +```markdown +## 1.0.1 (2026-06-04) + +### Dependencies + +- Updated dependencies + - my-dependency@1.2.3 +``` + +Propagation is transitive: if `A` depends on `B`, and `B` depends on `C`, then releasing `C` +will bump `B`, which will in turn bump `A` (subject to each package's own +`update_internal_dependencies` setting). + +### How Knope finds dependents + +For any package that opts in, Knope reads its manifests (currently `Cargo.toml` and +`package.json`). If a manifest declares a dependency whose name matches another package's +manifest name, Knope detects the relationship with no extra configuration. + +This detection alone doesn't keep the dependency's version string inside the manifest +current. To write that on every release, add a `{ path, dependency }` entry to the +dependency's `versioned_files`. + +Those `{ path, dependency }` entries also imply relationships: an entry like +`{ path = "consumer/Cargo.toml", dependency = "my-lib" }` in the `my-lib` package's config +means "when `my-lib` releases, write its new version into `consumer/Cargo.toml`". When that +file is also a versioned file of another package (`consumer` here), Knope records that +`consumer` depends on `my-lib`. The name the dependency goes by _inside_ the file doesn't +need to match its package name in `knope.toml`. + +Lock files (like `Cargo.lock` or `package-lock.json`) record versions for the whole +workspace, so sharing one does **not** create a dependency relationship. + +Sometimes a relationship isn't visible to either mechanism. Knope might not support the +manifest format yet, or you might track the version only in a shared workspace manifest via +`[workspace.dependencies]`. In those cases, declare it explicitly with +[`internal_dependencies`](#internal_dependencies). + +## `internal_dependencies` + +Explicitly declares which other packages (by their name in `knope.toml`) this package +depends on, for [`update_internal_dependencies`](#update_internal_dependencies) propagation. +This supplements the relationships Knope derives from `versioned_files`, covering cases +where the dependency's version isn't tracked in one of this package's own files: + +```toml title="knope.toml" +[packages.my-lib] +versioned_files = [ + "crates/my-lib/Cargo.toml", + # my-lib's version is tracked in the shared workspace manifest, which no package owns + { path = "Cargo.toml", dependency = "my-lib" }, +] + +[packages.consumer] +versioned_files = ["crates/consumer/Cargo.toml"] +update_internal_dependencies = "patch" +internal_dependencies = ["my-lib"] +``` + +## `track_paths` + +By default, Knope routes conventional commits to packages using the commit +[`scopes`](#scopes) field. In a monorepo, that's friction: every developer has to remember +to add the right scope, and a missing or misspelled scope drops the change. + +Set `track_paths = true` on a package to route commits to it based on the **files each +commit touches** instead. A commit counts toward this package's changes when it modifies a +file inside the package's territory. + +A package's territory defaults to the parent directories of its own (non-dependency) +`versioned_files`. For a Cargo workspace member declared via `versioned_files = +["crates/my-crate/Cargo.toml"]`, the territory is `crates/my-crate/`. + +```toml title="knope.toml" +[packages.my-crate] +versioned_files = ["crates/my-crate/Cargo.toml"] +track_paths = true +``` + +## `paths` + +Override the territory that [`track_paths`](#track_paths) derives by default. This helps when +a package's files live in several directories (for example, shared utilities) or when the +default doesn't match your layout. + +```toml title="knope.toml" +[packages.my-crate] +versioned_files = ["crates/my-crate/Cargo.toml"] +track_paths = true +paths = ["crates/my-crate", "shared/api"] +``` + +### How `track_paths` interacts with the rest + +- **Knope ignores commits in no package's territory.** A top-level `feat: README change` + doesn't bump any package, which is usually what you want. +- **A commit that touches files in several packages applies to each of them.** Every package + whose territory the commit touches gets the change. +- **The `scopes` field stays independent.** With `track_paths` on, routing no longer needs + `scopes`. If you also set `scopes`, scope filtering still applies. A commit that has a scope + must match both the package's scopes _and_ the path filter. A commit with no scope only + needs to satisfy the path filter. +- **Internal dependency propagation still applies.** A package bumped by `track_paths` will + still cascade to its dependents via + [`update_internal_dependencies`](#update_internal_dependencies).