1use anyhow::{Context, Result, bail};
48use std::process::{Command, Stdio};
49
50mod architecture;
51mod architecture_graph;
52mod cargo_generate;
53mod cargo_generate_post;
54mod ci_paths;
55mod codex_schema;
56mod doc_site;
57mod generated_surfaces;
58mod mcp_registry;
59mod patterns;
60mod provider_manifest;
61mod release_commands;
62mod release_versions;
63mod rmcp_release_monitor;
64mod scaffold;
65mod scripts;
66mod scripts_lane_a;
67mod scripts_lane_b;
68mod scripts_lane_c;
69mod scripts_lane_d;
70mod test_siblings;
71mod trace_headers_smoke;
72mod ts_client;
73mod web_source;
74mod workspace_commands;
75
76fn main() -> Result<()> {
77 let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
84 .parent()
85 .expect("xtask/Cargo.toml must have a parent directory");
86 std::env::set_current_dir(workspace_root).context("Failed to change into workspace root")?;
87
88 let args: Vec<String> = std::env::args().skip(1).collect();
89 match args.first().map(String::as_str) {
90 Some("dist") => workspace_commands::dist(),
91 Some("ci") => workspace_commands::ci(),
92 Some("symlink-docs") => workspace_commands::symlink_docs(),
93 Some("check-env") => workspace_commands::check_env(),
94 Some("patterns") => patterns_cmd(&args[1..]),
95 Some("contract-audit") => workspace_commands::contract_audit(),
96 Some("scaffold") => scaffold::run(&args[1..]),
97 Some("codex-schema") => codex_schema::run(&args[1..]),
98 Some("cargo-generate") => cargo_generate(&args[1..]),
99 Some("cargo-generate-post") => cargo_generate_post::run(&args[1..]),
100 Some("generate-docs") => workspace_commands::generate_docs(),
101 Some("doc") => workspace_commands::doc(&args[1..]),
102 Some("generate-provider-surfaces") => generated_surfaces::provider_surfaces(&args[1..]),
103 Some("check-docs") => workspace_commands::check_docs(),
104 Some("check-architecture") => architecture::check(workspace_root),
105 Some("check-mcp-registry") => mcp_registry::check_cmd(workspace_root, &args[1..]),
106 Some("check-stale-claims") => workspace_commands::check_stale_claims(),
107 Some("check-cargo-generate") => scripts_lane_d::check_cargo_generate(&args[1..]),
108 Some("sync-web-source") => web_source::sync(),
109 Some("check-web-source-sync") => web_source::check(),
110 Some("update-aurora-web") => web_source::update_aurora(),
111 Some("build-web") => scripts_lane_a::build_web(),
112 Some("web-watch") => scripts_lane_a::web_watch(),
113 Some("generate-cli") => scripts_lane_a::generate_cli(),
114 Some("repair") => scripts_lane_a::repair(),
115 Some("test-mcp-auth") => scripts_lane_a::test_mcp_auth(&args[1..]),
116 Some("test-trace-headers") => trace_headers_smoke::test_trace_headers(&args[1..]),
117 Some("block-env-commits") => scripts::block_env_commits(),
118 Some("asciicheck") => scripts_lane_d::asciicheck(&args[1..]),
119 Some("check-blob-size") => scripts_lane_c::check_blob_size(&args[1..]),
120 Some("check-coupled-files") => scripts::check_coupled_files(&args[1..]),
121 Some("check-dependency-updates") => scripts_lane_c::check_dependency_updates(&args[1..]),
122 Some("check-file-size") => scripts::check_file_size(),
123 Some("check-openapi") => scripts_lane_d::check_openapi(&args[1..]),
124 Some("check-openapi-drift") => scripts_lane_d::check_openapi(&args[1..]),
125 Some("check-ts-client") => ts_client::run(&args[1..]),
126 Some("check-palette-manifest") => generated_surfaces::check_palette_manifest(&args[1..]),
127 Some("check-provider-manifest-contract") => provider_manifest::check(),
128 Some("check-plugin-hook-contract") => {
129 scripts_lane_c::check_plugin_hook_contract(&args[1..])
130 }
131 Some("run-ascii-check") => scripts::run_ascii_check(&args[1..]),
132 Some("check-plugin-stdio-smoke") => scripts::check_plugin_stdio_smoke(),
133 Some("check-runtime-current") => scripts_lane_c::check_runtime_current(&args[1..]),
134 Some("check-schema-docs") => scripts_lane_d::check_schema_docs(&args[1..]),
135 Some("check-scaffold-intent-contract") => scripts_lane_d::check_scaffold_intent_contract(),
136 Some("sync-cargo") => scripts::sync_cargo(),
137 Some("pre-release-check") => scripts_lane_b::pre_release_check(&args[1..]),
138 Some("refresh-docs") => scripts_lane_c::refresh_docs(&args[1..]),
139 Some("test-soma-features") => scripts_lane_b::test_soma_features(workspace_root),
140 Some("validate-plugin-layout") => {
141 let plugin_root = std::env::var_os("PLUGIN_ROOT").map(std::path::PathBuf::from);
142 scripts_lane_b::validate_plugin_layout(workspace_root, plugin_root.as_deref())
143 }
144 Some("check-test-siblings") => test_siblings::check(),
145 Some("check-version-sync") => {
146 scripts_lane_b::check_version_sync(workspace_root, &args[1..])
147 }
148 Some("check-release-versions") => release_commands::check(workspace_root, &args[1..]),
149 Some("release-plan") => release_commands::plan(workspace_root, &args[1..]),
150 Some("sync-release-please-version") => {
151 release_versions::sync_release_please_version(workspace_root, "soma")
152 }
153 Some("rmcp-release-monitor") => rmcp_release_monitor::run(&args[1..]),
154 Some("bump-version") => release_commands::bump(workspace_root, &args[1..]),
155 Some("bump-soma-version") => scripts_lane_b::bump_version(workspace_root, &args[1..]),
156 Some("changed-paths") => ci_paths::run(&args[1..]),
157 Some("--help") | Some("-h") | Some("help") | None => {
158 workspace_commands::print_help();
159 Ok(())
160 }
161 Some(unknown) => {
162 bail!("Unknown xtask command: {unknown:?}\nRun `cargo xtask --help` for usage.")
163 }
164 }
165}
166
167fn cargo_generate(args: &[String]) -> Result<()> {
172 cargo_generate::run(args)
173}
174
175fn patterns_cmd(args: &[String]) -> Result<()> {
180 let mut options = patterns::PatternOptions::default();
181 for arg in args {
182 match arg.as_str() {
183 "--strict" => options.strict = true,
184 "--json" => options.json = true,
185 "--help" | "-h" => {
186 println!("Usage: cargo xtask patterns [--strict] [--json]");
187 return Ok(());
188 }
189 unknown => bail!("Unknown patterns option: {unknown}"),
190 }
191 }
192 patterns::run(options)
193}
194
195pub(crate) fn run_cargo(args: &[&str]) -> Result<()> {
201 run_cmd("cargo", args)
202}
203
204pub(crate) fn run_cmd(program: &str, args: &[&str]) -> Result<()> {
206 let status = Command::new(program)
207 .args(args)
208 .stdin(Stdio::null())
209 .status()
210 .with_context(|| format!("Failed to spawn `{program}`"))?;
211 if !status.success() {
212 bail!("`{program} {}` exited with status {status}", args.join(" "));
213 }
214 Ok(())
215}
216
217pub(crate) fn run_cmd_output(program: &str, args: &[&str]) -> Result<String> {
219 let output = Command::new(program)
220 .args(args)
221 .stdin(Stdio::null())
222 .output()
223 .with_context(|| format!("Failed to spawn `{program}`"))?;
224 if !output.status.success() {
225 bail!(
226 "`{program} {}` exited with status {}",
227 args.join(" "),
228 output.status
229 );
230 }
231 String::from_utf8(output.stdout)
232 .with_context(|| format!("`{program}` emitted non-UTF-8 stdout"))
233}
234
235pub(crate) fn command_exists(name: &str) -> bool {
239 Command::new(name)
240 .arg("--version")
241 .stdout(Stdio::null())
242 .stderr(Stdio::null())
243 .status()
244 .map(|s| s.success())
245 .unwrap_or(false)
246}