use anyhow::anyhow;
use clap::Subcommand;
use crax_monorepo::{
build::BuildOptions,
ctx::{self, write_sha},
dev::DevOptions,
monorepo,
release::ReleaseOptions,
robot::{Robot, commit, get_sha},
share::{is_clear, unwrap_ref_option},
};
use owo_colors::OwoColorize;
use which::which;
#[derive(Subcommand, Clone)]
pub enum Cmd {
#[command(about = "Update or check monorepo dependence's states")]
Audit {
#[arg(
short,
long,
default_value_t = false,
help = "Whether update pnpm-workspace.yaml or not"
)]
update: bool,
#[arg(
long,
help = "catalog remote url or local file path, eg: http://a/b/catalog.yaml or absolute path: /a/b/catalog.yaml"
)]
uri: Option<String>,
#[arg(
long,
help = "catalog remote url tag, eg: uri is http://a/b/catalog.yaml and tag is rc will get final uri is: http://a/b/catalog-rc.yaml"
)]
tag: Option<String>,
},
#[command(long_about = "See all packages information")]
Info {
#[arg(short = 'd', long, help = "See short message")]
detail: bool,
},
#[command(long_about = "See diff message for every package")]
Diff,
#[command(long_about = "Build packages")]
Build {
#[arg(short = 'S', long = "no-select", help = "Avoid select dialog")]
no_select: bool,
#[arg(short, long, value_delimiter = ',', help = "Build Packages names")]
names: Option<Vec<String>>,
#[arg(long, help = "Select by diff")]
diff: bool,
#[arg(long, help = "Do not crawl all workspace dependence")]
single: bool,
#[arg(long, help = "See builded detail")]
detail: bool,
},
#[command(long_about = "Release packages")]
Release {
#[arg(short = 'S', long = "no-select", help = "Avoid select dialog")]
no_select: bool,
#[arg(short = 'a', long, help = "Release all packages")]
all: bool,
#[arg(short, long, value_delimiter = ',', help = "Build Packages names")]
names: Option<Vec<String>>,
#[arg(short, long, value_delimiter = ',', help = "Npm registries")]
reg: Option<Vec<String>>,
#[arg(short = 'p', long = "pre", help = "Pre-patch version")]
pre: bool,
#[arg(
long = "pre-tag",
help = "Define your own pre publish tag e.g. alpha, beta, rc, default is rc"
)]
pre_tag: Option<String>,
#[arg(long, help = "Npm release tag default is latest")]
tag: Option<String>,
#[arg(
short,
long = "dry-run",
help = "Does everything a publish would do except actually publishing to the registry"
)]
dry_run: bool,
#[arg(long, help = "Release by diff")]
diff: bool,
#[arg(long, help = "Push to remote origin")]
push: bool,
#[arg(long, help = "Personal Access Token")]
pat: Option<String>,
#[arg(long, help = "Keep the current version")]
keep: bool,
#[arg(long = "no-build", help = "Skip build")]
no_build: bool,
#[arg(long, help = "See release and build detail")]
detail: bool,
#[arg(long, long = "no-git-check", help = "Ignore git status check")]
no_git_check: bool, },
#[command(long_about = "Run dev")]
Dev {
#[arg(short, long, value_delimiter = ',', help = "Npm registries")]
names: Option<Vec<String>>,
},
}
pub async fn run(ctx: &mut ctx::Ctx, command: Option<Cmd>, mut root: bool) -> anyhow::Result<()> {
let pnpm_error_msg =
"❌ Can not find pnpm, Please install it first.".bold().yellow().to_string();
if let Some(include_root) = ctx.config.include_root_project {
root = include_root
}
let mut mono = monorepo::Monorepo::new(&ctx.cwd, ctx.config.ignore.as_ref(), root);
let config = &ctx.config;
if let Some(ref meta) = config.meta {
mono.sort_and_init(meta).unwrap();
}
let pre_build_commit = unwrap_ref_option(&config.pre_build_commit, "HEAD^".into());
let diff_ignore = &config.diff_ignore;
let dts_ignore = &config.dts_ignore.clone().unwrap_or(vec![]);
match command {
Some(Cmd::Info { detail }) => Ok(mono.info(detail)),
Some(Cmd::Diff) => {
let _ = mono.diff(&pre_build_commit, diff_ignore, true).await?;
Ok(())
}
Some(Cmd::Audit { update, uri, tag }) => {
which("pnpm").expect(&pnpm_error_msg);
let remote_url = uri.or_else(|| ctx.config.catalog_remote_url.clone());
if let Some(uri) = remote_url {
Ok(mono.audit(&uri, update, tag).await?)
} else {
Err(anyhow!("Can not find any valid remote uri.".red()))
}
}
Some(Cmd::Build { no_select, names, diff, single, detail }) => {
which("pnpm").expect(&pnpm_error_msg);
let names = if diff {
Some(mono.diff(&pre_build_commit, diff_ignore, true).await?)
} else {
names.clone()
};
mono.build(BuildOptions {
names,
select: !no_select,
dts_ignore: Some(dts_ignore),
single,
avoid_run: false,
detail,
})
.await?;
Ok(())
}
Some(Cmd::Release {
no_select,
names,
pre,
reg,
all,
dry_run,
diff,
push,
pat,
keep,
no_build,
pre_tag,
tag,
detail,
no_git_check,
}) => {
let should_build_all = mono.should_build_all().await;
let mut package_names: Option<Vec<String>>;
let is_all = all || should_build_all;
if diff && !no_git_check {
is_clear(&ctx.cwd).await?;
}
package_names = if diff && !is_all {
Some(mono.diff(&pre_build_commit, diff_ignore, true).await?)
} else {
names.clone()
};
let registries = reg
.unwrap_or_else(|| ctx.config.registries.clone().expect("Please set registries!!"));
if should_build_all {
println!(
"{}\n",
"Due to the discovery of [*], all packages will be released.".yellow()
);
}
if is_all {
package_names = Some(mono.names());
}
if let Some(released_pkgs) = mono
.release(ReleaseOptions {
names: package_names,
registries: Some(®istries),
pre,
select: !no_select,
dry_run,
keep,
dts_ignore: Some(dts_ignore),
detail,
skip_build: no_build,
pre_tag: pre_tag.as_deref(),
tag,
})
.await?
{
let re_pkgs_len = released_pkgs.len();
if !keep && !released_pkgs.is_empty() && !dry_run {
let mes = released_pkgs
.iter()
.map(|s| format!("{}@{}", s, mono.get(s).current_version().unwrap()))
.collect::<Vec<String>>()
.join(" ");
let sha = get_sha(&ctx.cwd).await?;
write_sha(&sha, &ctx.cwd)?;
println!("{}", "has updated sha!".green());
let re_mes = if re_pkgs_len <= 1 { "package" } else { "packages" };
if push {
println!("{}", "start pushing...".yellow());
if let Some(v) = &config.git_path {
if let Some(p) = pat {
let robot = Robot::new(v, &p, "ci_bot", "ci_bot@runa.com");
robot.init(&ctx.cwd).await?;
commit(
&format!(
"chore(release): 🚀 {} {} [skip ci] \n\n{}",
re_pkgs_len, re_mes, &mes
),
&ctx.cwd,
)
.await?;
robot.push(&ctx.cwd).await?;
} else {
return Err(anyhow::Error::msg("Can not find git PAT!"));
}
} else {
return Err(anyhow::Error::msg("Can not find git_path"));
}
println!("{}\n", "push successful!".green());
} else {
commit(
&format!(
"chore(release): 🚀 {} {} [skip ci] \n\n{}",
re_pkgs_len, re_mes, &mes
),
&ctx.cwd,
)
.await?;
}
}
}
Ok(())
}
Some(Cmd::Dev { names }) => {
which("pnpm").expect(&pnpm_error_msg);
mono.dev(DevOptions { names }).await?;
Ok(())
}
None => Err(anyhow::Error::msg("Command not found!")),
}
}