Skip to main content

greentic_bundle/cli/
add.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::{Args, Subcommand};
5use serde::Serialize;
6
7#[derive(Debug, Args)]
8pub struct AddArgs {
9    #[command(subcommand)]
10    pub command: AddCommand,
11}
12
13#[derive(Debug, Subcommand)]
14pub enum AddCommand {
15    #[command(about = "cli.add.app_pack.about")]
16    AppPack(MutationArgs),
17    #[command(about = "cli.add.extension_provider.about")]
18    ExtensionProvider(MutationArgs),
19}
20
21#[derive(Debug, Args)]
22pub struct MutationArgs {
23    #[arg(value_name = "ID")]
24    pub id: String,
25
26    #[arg(
27        long,
28        value_name = "PATH",
29        default_value = ".",
30        help = "cli.add.root.option"
31    )]
32    pub root: PathBuf,
33
34    #[arg(long, default_value_t = false, help = "cli.option.dry_run")]
35    pub dry_run: bool,
36
37    #[arg(long, default_value_t = false, help = "cli.option.execute")]
38    pub execute: bool,
39}
40
41#[derive(Debug, Serialize)]
42struct MutationPreview {
43    root: String,
44    field: String,
45    action: String,
46    item: String,
47    execute: bool,
48    changed: bool,
49    current_values: Vec<String>,
50    next_values: Vec<String>,
51    expected_file_writes: Vec<String>,
52}
53
54pub fn run(args: AddArgs) -> Result<()> {
55    match args.command {
56        AddCommand::AppPack(args) => mutate(args, crate::project::ReferenceField::AppPack),
57        AddCommand::ExtensionProvider(args) => {
58            mutate(args, crate::project::ReferenceField::ExtensionProvider)
59        }
60    }
61}
62
63fn mutate(args: MutationArgs, field: crate::project::ReferenceField) -> Result<()> {
64    let mut workspace = crate::project::read_bundle_workspace(&args.root)?;
65    let current_values = workspace.references(field).to_vec();
66    if !workspace
67        .references(field)
68        .iter()
69        .any(|value| value == &args.id)
70    {
71        workspace.references_mut(field).push(args.id.clone());
72    }
73    workspace.canonicalize();
74    let next_values = workspace.references(field).to_vec();
75    let changed = current_values != next_values;
76    let execute = args.execute && !args.dry_run;
77    if execute {
78        crate::project::write_bundle_workspace(&args.root, &workspace)?;
79        crate::project::sync_lock_with_workspace(&args.root, &workspace)?;
80        crate::project::sync_project(&args.root)?;
81    }
82    let preview = MutationPreview {
83        root: args.root.display().to_string(),
84        field: field_name(field).to_string(),
85        action: "add".to_string(),
86        item: args.id,
87        execute,
88        changed,
89        current_values,
90        next_values,
91        expected_file_writes: if execute {
92            vec![
93                args.root
94                    .join(crate::project::WORKSPACE_ROOT_FILE)
95                    .display()
96                    .to_string(),
97                args.root
98                    .join(crate::project::LOCK_FILE)
99                    .display()
100                    .to_string(),
101                args.root
102                    .join("resolved/default.yaml")
103                    .display()
104                    .to_string(),
105                args.root
106                    .join("state/resolved/default.yaml")
107                    .display()
108                    .to_string(),
109            ]
110        } else {
111            vec![
112                args.root
113                    .join(crate::project::WORKSPACE_ROOT_FILE)
114                    .display()
115                    .to_string(),
116            ]
117        },
118    };
119    println!("{}", serde_json::to_string_pretty(&preview)?);
120    Ok(())
121}
122
123fn field_name(field: crate::project::ReferenceField) -> &'static str {
124    match field {
125        crate::project::ReferenceField::AppPack => "app_packs",
126        crate::project::ReferenceField::ExtensionProvider => "extension_providers",
127    }
128}