pub mod changeset;
pub mod git_lifecycle;
pub mod github;
pub mod linked_versions;
pub mod propagation;
pub mod release_files;
pub mod version;
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Args;
use log::info;
use semver::Version;
use crate::model::changeset::{ChangeType, Changeset};
use crate::model::config::Config;
use crate::package_manager::Project;
use changeset::{aggregate_changesets, resolve_commit_references};
use git_lifecycle::{GitContext, finalize_git_lifecycle, preflight_checks, setup_git_context};
use linked_versions::{
reconcile_linked_versions, resolve_linked_groups, sync_linked_groups_after_propagation,
};
use propagation::apply_dependency_propagation;
use release_files::prepare_release_files;
#[derive(Args, Default)]
pub struct PrepareArgs {
#[arg(short = 'p', long = "package")]
pub packages: Vec<String>,
#[arg(long)]
pub no_git: bool,
#[arg(long)]
pub branch: Option<String>,
}
#[derive(Debug)]
pub(crate) struct ReleaseInfo {
pub(crate) package_name: String,
pub(crate) new_version: Version,
pub(crate) changelog_entry: String,
}
pub(crate) type PackageChanges = Vec<(
ChangeType,
Option<String>,
Option<crate::model::changelog::CommitReference>,
)>;
pub(super) type PropagationResult = (BTreeMap<String, Vec<String>>, Vec<PathBuf>);
pub(crate) type PropagationMap = BTreeMap<String, (ChangeType, BTreeSet<String>)>;
#[derive(Debug)]
pub(crate) struct PrepareOutput {
pub(crate) release_infos: Vec<ReleaseInfo>,
pub(crate) modified_files: Vec<PathBuf>,
}
pub(super) struct VersionPlan {
pub(super) aggregated: BTreeMap<String, ChangeType>,
pub(super) changes_per_package: BTreeMap<String, PackageChanges>,
pub(super) version_overrides: BTreeMap<String, Version>,
pub(super) dep_entries: BTreeMap<String, Vec<String>>,
pub(super) propagation_changeset_paths: Vec<PathBuf>,
}
async fn compute_version_plan(
changesets: &[(crate::path::AbsolutePath, Changeset)],
args: &PrepareArgs,
env: &crate::Env,
config: &Config,
projects: &[Project],
git_ctx: &GitContext,
dry_run: bool,
) -> anyhow::Result<VersionPlan> {
let git = env.git();
let commit_refs = resolve_commit_references(changesets, git, git_ctx.enabled).await;
let (mut aggregated, mut changes_per_package) =
aggregate_changesets(changesets, &args.packages, projects, &commit_refs)?;
let linked_groups = resolve_linked_groups(config, args, projects)?;
let mut version_overrides = reconcile_linked_versions(
&mut aggregated,
&mut changes_per_package,
&linked_groups,
projects,
);
let (dep_entries, propagation_changeset_paths) = apply_dependency_propagation(
projects,
&mut aggregated,
&version_overrides,
&args.packages,
config.prepare.dependency_bump,
env,
dry_run,
)
.await?;
sync_linked_groups_after_propagation(
&mut aggregated,
&mut changes_per_package,
&mut version_overrides,
&linked_groups,
projects,
);
Ok(VersionPlan {
aggregated,
changes_per_package,
version_overrides,
dep_entries,
propagation_changeset_paths,
})
}
pub(crate) async fn cmd_prepare(
args: &PrepareArgs,
dry_run: bool,
env: &crate::Env,
config: Config,
) -> anyhow::Result<ExitCode> {
let git = env.git();
let adapters = config.create_adapters(env)?;
let projects = config.load_projects_for_adapters(&adapters).await?;
let changesets = Changeset::read_all(env).await?;
if changesets.is_empty() {
info!("No pending changesets found. Nothing to prepare.");
return Ok(ExitCode::SUCCESS);
}
let git_ctx = setup_git_context(&config, args);
let plan = compute_version_plan(
&changesets,
args,
env,
&config,
&projects,
&git_ctx,
dry_run,
)
.await?;
let branches = preflight_checks(git, &config, env, args, &git_ctx, dry_run).await?;
let output =
prepare_release_files(&adapters, &projects, &changesets, plan, dry_run, env.fs()).await?;
finalize_git_lifecycle(git, &config, env, &output, &branches, &git_ctx, dry_run).await?;
Ok(ExitCode::SUCCESS)
}
#[cfg(test)]
mod tests;