unc 0.15.0

human-friendly console utility that helps to interact with utility building Rust smart contracts from command line.
Documentation
use color_eyre::eyre::{ContextCompat, WrapErr};

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::GlobalContext)]
#[interactive_clap(output_context = NewContext)]
pub struct New {
    /// Enter a new project name (path to the project) to create a contract:
    pub project_dir: crate::types::path_buf::PathBuf,
}

#[derive(Debug, Clone)]
pub struct NewContext;

impl NewContext {
    pub fn from_previous_context(
        _previous_context: crate::GlobalContext,
        scope: &<New as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
    ) -> color_eyre::eyre::Result<Self> {
        let project_dir: &std::path::Path = scope.project_dir.as_ref();

        if project_dir.exists() {
            return Err(color_eyre::eyre::eyre!(
                "Destination `{}` already exists. Refusing to overwrite existing project.",
                project_dir.display()
            ));
        }

        let project_name = project_dir
            .file_name()
            .wrap_err("Could not extract project name from project path")?
            .to_str()
            .wrap_err("Project name has to be a valid UTF-8 string")?;

        for new_project_file in NEW_PROJECT_FILES {
            let new_file_path = project_dir.join(new_project_file.file_path);
            std::fs::create_dir_all(new_file_path.parent().wrap_err_with(|| {
                format!("Impossible to get parent for `{}`", new_file_path.display())
            })?)?;
            std::fs::write(
                &new_file_path,
                new_project_file
                    .content
                    .replace("unc-new-project-name", project_name),
            )
            .wrap_err_with(|| format!("Failed to write to file: {}", new_file_path.display()))?;
        }

        std::process::Command::new("git")
            .arg("init")
            .current_dir(project_dir)
            .output()
            .wrap_err("Failed to execute process: `git init`")?;

        println!("New project is created at '{}'.\n", project_dir.display());
        println!("Now you can build, test, and deploy your project using unc:");
        println!(" * `cargo unc build`");
        println!(" * `cargo test`");
        println!(" * `cargo unc deploy`");
        println!(
            "Your new project has preconfigured automations for CI and CD, just configure \
            `UNC_CONTRACT_STAGING_*` and `UNC_CONTRACT_PRODUCTION_*` variables and secrets \
            on GitHub to enable automatic deployment to staging and production. See more \
            details in `.github/workflow/*` files.\n"
        );

        Ok(Self)
    }
}

struct NewProjectFile {
    file_path: &'static str,
    content: &'static str,
}

const NEW_PROJECT_FILES: &[NewProjectFile] = &[
    NewProjectFile {
        file_path: ".github/workflows/deploy-production.yml",
        content: include_str!("new-project-template/.github/workflows/deploy-production.yml"),
    },
    NewProjectFile {
        file_path: ".github/workflows/deploy-dev.yml",
        content: include_str!("new-project-template/.github/workflows/deploy-dev.yml"),
    },
    NewProjectFile {
        file_path: ".github/workflows/test.yml",
        content: include_str!("new-project-template/.github/workflows/test.yml"),
    },
    NewProjectFile {
        file_path: ".github/workflows/undeploy-dev.yml",
        content: include_str!("new-project-template/.github/workflows/undeploy-dev.yml"),
    },
    NewProjectFile {
        file_path: "src/lib.rs",
        content: include_str!("new-project-template/src/lib.rs"),
    },
    NewProjectFile {
        file_path: "tests/test_basics.rs",
        content: include_str!("new-project-template/tests/test_basics.rs"),
    },
    NewProjectFile {
        file_path: ".gitignore",
        content: include_str!("new-project-template/.gitignore"),
    },
    NewProjectFile {
        file_path: "Cargo.toml",
        content: include_str!("new-project-template/Cargo.toml.template"),
    },
    NewProjectFile {
        file_path: "README.md",
        content: include_str!("new-project-template/README.md"),
    },
    NewProjectFile {
        file_path: "rust-toolchain.toml",
        content: include_str!("new-project-template/rust-toolchain.toml"),
    },
];