chore(workspace): add cargo xtask crate for common ops
This commit is contained in:
@@ -11,6 +11,7 @@ members = [
|
|||||||
"crates/mcp/code-server",
|
"crates/mcp/code-server",
|
||||||
"crates/mcp/prompt-server",
|
"crates/mcp/prompt-server",
|
||||||
"crates/owlen-markdown",
|
"crates/owlen-markdown",
|
||||||
|
"xtask",
|
||||||
]
|
]
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|||||||
9
xtask/Cargo.toml
Normal file
9
xtask/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "xtask"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
clap = { workspace = true, features = ["derive"] }
|
||||||
162
xtask/src/main.rs
Normal file
162
xtask/src/main.rs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, version, about = "Owlen developer tasks", long_about = None)]
|
||||||
|
struct Xtask {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Task,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Task {
|
||||||
|
/// Format the workspace (use --check to verify without writing).
|
||||||
|
Fmt {
|
||||||
|
#[arg(long, help = "Run rustfmt in check mode")]
|
||||||
|
check: bool,
|
||||||
|
},
|
||||||
|
/// Run clippy with all warnings elevated to errors.
|
||||||
|
Lint,
|
||||||
|
/// Execute the full workspace test suite.
|
||||||
|
Test,
|
||||||
|
/// Run coverage via cargo-llvm-cov (requires the tool to be installed).
|
||||||
|
Coverage,
|
||||||
|
/// Launch the default Owlen CLI binary (owlen) with optional args.
|
||||||
|
DevRun {
|
||||||
|
#[arg(last = true, help = "Arguments forwarded to `owlen`")]
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
/// Composite release validation (fmt --check, clippy, test).
|
||||||
|
ReleaseCheck,
|
||||||
|
/// Regenerate docs/repo-map.md (accepts optional output path).
|
||||||
|
GenRepoMap {
|
||||||
|
#[arg(long, value_name = "PATH", help = "Override the repo map output path")]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let cli = Xtask::parse();
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Task::Fmt { check } => fmt(check),
|
||||||
|
Task::Lint => lint(),
|
||||||
|
Task::Test => test(),
|
||||||
|
Task::Coverage => coverage(),
|
||||||
|
Task::DevRun { args } => dev_run(args),
|
||||||
|
Task::ReleaseCheck => release_check(),
|
||||||
|
Task::GenRepoMap { output } => gen_repo_map(output),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt(check: bool) -> Result<()> {
|
||||||
|
let mut args = vec!["fmt".to_string(), "--all".to_string()];
|
||||||
|
if check {
|
||||||
|
args.push("--".to_string());
|
||||||
|
args.push("--check".to_string());
|
||||||
|
}
|
||||||
|
run_cargo(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lint() -> Result<()> {
|
||||||
|
run_cargo(vec![
|
||||||
|
"clippy".into(),
|
||||||
|
"--workspace".into(),
|
||||||
|
"--all-features".into(),
|
||||||
|
"--".into(),
|
||||||
|
"-D".into(),
|
||||||
|
"warnings".into(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test() -> Result<()> {
|
||||||
|
run_cargo(vec![
|
||||||
|
"test".into(),
|
||||||
|
"--workspace".into(),
|
||||||
|
"--all-features".into(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coverage() -> Result<()> {
|
||||||
|
run_cargo(vec![
|
||||||
|
"llvm-cov".into(),
|
||||||
|
"--workspace".into(),
|
||||||
|
"--all-features".into(),
|
||||||
|
"--summary-only".into(),
|
||||||
|
])
|
||||||
|
.with_context(|| "install `cargo llvm-cov` to use the coverage task".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dev_run(args: Vec<String>) -> Result<()> {
|
||||||
|
let mut command_args = vec![
|
||||||
|
"run".into(),
|
||||||
|
"-p".into(),
|
||||||
|
"owlen-cli".into(),
|
||||||
|
"--bin".into(),
|
||||||
|
"owlen".into(),
|
||||||
|
];
|
||||||
|
if !args.is_empty() {
|
||||||
|
command_args.push("--".into());
|
||||||
|
command_args.extend(args);
|
||||||
|
}
|
||||||
|
run_cargo(command_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_check() -> Result<()> {
|
||||||
|
fmt(true)?;
|
||||||
|
lint()?;
|
||||||
|
test()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_repo_map(output: Option<PathBuf>) -> Result<()> {
|
||||||
|
let script = workspace_root().join("scripts/gen-repo-map.sh");
|
||||||
|
if !script.exists() {
|
||||||
|
bail!("repo map script not found at {}", script.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = Command::new(&script);
|
||||||
|
cmd.current_dir(workspace_root());
|
||||||
|
if let Some(path) = output {
|
||||||
|
cmd.arg(path);
|
||||||
|
}
|
||||||
|
let status = cmd
|
||||||
|
.status()
|
||||||
|
.with_context(|| format!("failed to run {}", script.display()))?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"{} exited with status {}",
|
||||||
|
script.display(),
|
||||||
|
status.code().unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_cargo(args: Vec<String>) -> Result<()> {
|
||||||
|
let mut cmd = Command::new("cargo");
|
||||||
|
cmd.current_dir(workspace_root());
|
||||||
|
cmd.args(&args);
|
||||||
|
|
||||||
|
let status = cmd
|
||||||
|
.status()
|
||||||
|
.with_context(|| format!("failed to run cargo {}", args.join(" ")))?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"`cargo {}` exited with status {}",
|
||||||
|
args.join(" "),
|
||||||
|
status.code().unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_root() -> PathBuf {
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.expect("xtask has a parent directory")
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user