Skip to main content

ubt_cli/commands/
init.rs

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, &registry) {
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    // fallback: first command alphabetically
71    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}