1use crate::detect::detect_tool;
2use crate::error::UbtError;
3use crate::plugin::{PluginRegistry, ResolvedPlugin};
4
5pub fn cmd_init() -> Result<(), UbtError> {
6 let cwd = std::env::current_dir()?;
7 let config_path = cwd.join("ubt.toml");
8
9 if config_path.exists() {
10 println!("ubt.toml already exists at {}", config_path.display());
11 return Ok(());
12 }
13
14 let registry = PluginRegistry::new()?;
15 let (tool, example_cmd) = match detect_tool(None, None, &cwd, ®istry) {
16 Ok(detection) => {
17 let example = registry
18 .get(&detection.plugin_name)
19 .and_then(|(plugin, source)| {
20 plugin
21 .resolve_variant(&detection.variant_name, source.clone())
22 .ok()
23 })
24 .and_then(|resolved| init_example_command(&resolved))
25 .unwrap_or_else(|| r#"start = "your-command-here""#.to_string());
26 (detection.variant_name, example)
27 }
28 Err(_) => ("npm".to_string(), r#"start = "npm run dev""#.to_string()),
29 };
30
31 let content = format!(
32 r#"# ubt.toml — Universal Build Tool configuration
33
34[project]
35# Pin the tool/runtime. Remove this line to let ubt auto-detect.
36# Supported: npm, pnpm, yarn, bun, deno, cargo, go, pip, uv, poetry, bundler
37tool = "{tool}"
38
39# Override built-in commands with project-specific shell commands.
40# Available keys: build, start, test, lint, fmt, check, clean, run, exec,
41# dep.install, dep.remove, dep.update, dep.list, dep.audit, dep.outdated,
42# db.migrate, db.rollback, db.seed, db.create, db.drop, db.reset, db.status
43# Use {{{{args}}}} to forward extra CLI arguments to the underlying command.
44[commands]
45# {example_cmd}
46# ...
47
48# Add new commands not covered by built-ins.
49# Names must not conflict with built-ins (build, test, dep, db, …).
50[aliases]
51# hello = "echo hello world"
52"#,
53 tool = tool,
54 example_cmd = example_cmd
55 );
56
57 std::fs::write(&config_path, &content)?;
58 println!("Created {}", config_path.display());
59 Ok(())
60}
61
62fn init_example_command(resolved: &ResolvedPlugin) -> Option<String> {
63 let preferred = ["start", "build", "test"];
64 for key in &preferred {
65 if let Some(cmd) = resolved.commands.get(*key) {
66 let rendered = cmd.replace("{{tool}}", &resolved.binary);
67 return Some(format!(r#"{key} = "{rendered}""#));
68 }
69 }
70 let mut keys: Vec<&String> = resolved.commands.keys().collect();
72 keys.sort();
73 keys.first().map(|key| {
74 let rendered = resolved.commands[*key].replace("{{tool}}", &resolved.binary);
75 format!(r#"{key} = "{rendered}""#)
76 })
77}