Skip to main content

greentic_bundle/cli/
init.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::Args;
5use serde::Serialize;
6
7#[derive(Debug, Args, Default)]
8pub struct InitArgs {
9    #[arg(value_name = "PATH", help = "cli.init.path.option")]
10    pub path: Option<PathBuf>,
11
12    #[arg(long, value_name = "NAME", help = "cli.init.bundle_name.option")]
13    pub bundle_name: Option<String>,
14
15    #[arg(long, value_name = "ID", help = "cli.init.bundle_id.option")]
16    pub bundle_id: Option<String>,
17
18    #[arg(
19        long,
20        value_name = "MODE",
21        default_value = "create",
22        help = "cli.init.mode.option"
23    )]
24    pub mode: String,
25
26    #[arg(
27        long,
28        value_name = "LOCALE",
29        default_value = "en",
30        help = "cli.init.locale.option"
31    )]
32    pub locale: String,
33
34    #[arg(long, default_value_t = false, help = "cli.option.execute")]
35    pub execute: bool,
36}
37
38#[derive(Debug, Serialize)]
39struct InitPreview {
40    root: String,
41    execute: bool,
42    bundle_id: String,
43    bundle_name: String,
44    mode: String,
45    locale: String,
46    expected_file_writes: Vec<String>,
47}
48
49pub fn run(args: InitArgs) -> Result<()> {
50    let root = args.path.unwrap_or_else(|| PathBuf::from("."));
51    let bundle_name = args
52        .bundle_name
53        .unwrap_or_else(|| default_bundle_name(&root));
54    let bundle_id = args
55        .bundle_id
56        .unwrap_or_else(|| normalize_bundle_id(&bundle_name));
57    let workspace = crate::project::BundleWorkspaceDefinition::new(
58        bundle_name.clone(),
59        bundle_id.clone(),
60        args.locale.clone(),
61        args.mode.clone(),
62    );
63    let preview = InitPreview {
64        root: root.display().to_string(),
65        execute: args.execute,
66        bundle_id,
67        bundle_name,
68        mode: args.mode,
69        locale: args.locale,
70        expected_file_writes: vec![
71            root.join(crate::project::WORKSPACE_ROOT_FILE)
72                .display()
73                .to_string(),
74            root.join(crate::project::LOCK_FILE).display().to_string(),
75            root.join("tenants/default/tenant.gmap")
76                .display()
77                .to_string(),
78            root.join("resolved/default.yaml").display().to_string(),
79            root.join("state/resolved/default.yaml")
80                .display()
81                .to_string(),
82        ],
83    };
84    if args.execute {
85        crate::project::init_bundle_workspace(&root, &workspace)?;
86    }
87    println!("{}", serde_json::to_string_pretty(&preview)?);
88    Ok(())
89}
90
91fn default_bundle_name(root: &std::path::Path) -> String {
92    root.file_name()
93        .and_then(|name| name.to_str())
94        .filter(|name| !name.is_empty() && *name != ".")
95        .map(|name| name.replace('-', " "))
96        .unwrap_or_else(|| "bundle".to_string())
97}
98
99fn normalize_bundle_id(raw: &str) -> String {
100    let normalized = raw
101        .trim()
102        .to_ascii_lowercase()
103        .chars()
104        .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' })
105        .collect::<String>();
106    let trimmed = normalized.trim_matches('-').to_string();
107    if trimmed.is_empty() {
108        "bundle".to_string()
109    } else {
110        trimmed
111    }
112}