wasmer-deploy-client-cli 0.1.1

CLI client for Wasmer Deploy
Documentation
use std::path::{Path, PathBuf};

use anyhow::{bail, Context};
use is_terminal::IsTerminal;
use wasmer_api::backend::gql::PublishDeployAppVariables;
use wasmer_deploy_schema::schema::DeploymentV1;

use crate::{cmd::AsyncCliCommand, ApiOpts, ItemFormatOpts};

#[derive(clap::Parser, Debug)]
pub struct CmdAppPublish {
    #[clap(flatten)]
    pub api: ApiOpts,
    #[clap(flatten)]
    pub fmt: ItemFormatOpts,

    /// Skip local schema validation.
    #[clap(long)]
    pub no_validate: bool,

    /// Automatically publish the package referenced by this app.
    ///
    /// Only works if the corresponding wasmer.toml is in the same directory.
    #[clap(long)]
    pub publish_package: bool,

    /// Do not prompt for user input.
    #[clap(long)]
    pub non_interactive: bool,

    /// The path to a YAML file with the deploy config.
    pub path: Option<PathBuf>,
}

impl CmdAppPublish {
    async fn run(self) -> Result<(), anyhow::Error> {
        let base_path = if let Some(p) = self.path {
            p
        } else {
            std::env::current_dir()?
        };
        let file_path = if base_path.is_file() {
            base_path
        } else if base_path.is_dir() {
            let full = base_path.join("deploy.yaml");
            if !full.is_file() {
                bail!("Could not find deploy.yaml at path: '{}'", full.display());
            }
            full
        } else {
            bail!("No such file or directory: '{}'", base_path.display());
        };
        let dir_path = file_path.canonicalize()?.parent().unwrap().to_owned();

        let interactive = std::io::stdin().is_terminal() && !self.non_interactive;

        let client = self.api.client()?;
        let user = wasmer_api::backend::current_user(&client).await?;

        let contents = std::fs::read_to_string(&file_path)
            .with_context(|| format!("Could not read file: '{}'", file_path.display()))?;

        let config =
            serde_yaml::from_str::<DeploymentV1>(&contents).context("Could not parse schema")?;

        let (owner, name) = if let Some((ns, name)) = config.name.split_once('/') {
            (ns.to_string(), name.to_string())
        } else {
            (user.username, config.name.clone())
        };

        // Check for a wasmer.toml
        if let Some(manifest) = Self::load_wasmer_manifest(&dir_path)? {
            if let Some(webc) = config.workload.webc() {
                let full_name_webc = format!("{}/{}", webc.namespace, webc.name);
                if full_name_webc == manifest.package.name {
                    let should_publish = if self.publish_package {
                        true
                    } else if interactive {
                        eprintln!("Found local package in wasmer.toml");
                        dialoguer::Confirm::new()
                            .with_prompt(format!("Publish package '{}'?", full_name_webc))
                            .interact_opt()?
                            .unwrap_or_default()
                    } else {
                        false
                    };

                    if should_publish {
                        eprintln!("Publishing package...");
                        let new_manifest = crate::util::republish_package_with_bumped_version(
                            &client,
                            &dir_path.join("wasmer.toml"),
                            manifest,
                        )
                        .await?;

                        eprintln!(
                            "Package '{}@{}' published successfully!",
                            new_manifest.package.name, new_manifest.package.version
                        );
                    }
                }
            }
        }

        let vars = PublishDeployAppVariables {
            config: serde_json::to_string(&config)?,
            name: name.clone().into(),
            owner: Some(owner.clone().into()),
        };

        eprintln!("Publishing app...");
        let version = wasmer_api::backend::publish_deploy_app(&client, vars).await?;
        eprintln!("App published successfully!");
        let url = format!("https://{}.{}.{}.wasmer.app", version.version, name, owner);
        eprintln!("Access your app at: {}", url);

        self.fmt.format.render(&version);

        Ok(())
    }

    fn load_wasmer_manifest(path: &Path) -> Result<Option<wasmer_toml::Manifest>, anyhow::Error> {
        let p = path.join("wasmer.toml");
        if p.is_file() {
            let contents = std::fs::read_to_string(&p)?;
            let manifest = wasmer_toml::Manifest::parse(&contents)
                .with_context(|| format!("Could not parse wasmer.toml at: '{}'", p.display()))?;
            Ok(Some(manifest))
        } else {
            Ok(None)
        }
    }
}

impl AsyncCliCommand for CmdAppPublish {
    fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> {
        Box::pin(self.run())
    }
}