mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 21:58:20 +00:00
deploy flow dx
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -1413,10 +1413,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "edge-schema"
|
name = "edge-schema"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "183ddfb52c2441be9d8c3c870632135980ba98e0c4f688da11bcbebb4e26f128"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
"hex",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
@@ -1436,11 +1437,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "edge-schema"
|
name = "edge-schema"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0966f1fd49610cc67a835124e6fb4d00a36104e1aa34383c5ef5a265ca00ea2a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
|
"hex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
@@ -2228,7 +2228,7 @@ dependencies = [
|
|||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.5.6",
|
"socket2 0.4.10",
|
||||||
"tokio 1.37.0",
|
"tokio 1.37.0",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -5587,7 +5587,7 @@ version = "1.6.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 0.1.10",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6253,6 +6253,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"wasmer-config 0.1.0",
|
||||||
"webc",
|
"webc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6412,7 +6413,7 @@ dependencies = [
|
|||||||
"semver 1.0.22",
|
"semver 1.0.22",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml 0.8.26",
|
"serde_yaml 0.9.34+deprecated",
|
||||||
"sha2",
|
"sha2",
|
||||||
"shared-buffer",
|
"shared-buffer",
|
||||||
"tar",
|
"tar",
|
||||||
@@ -6783,6 +6784,7 @@ dependencies = [
|
|||||||
"wasmer-config 0.1.0",
|
"wasmer-config 0.1.0",
|
||||||
"wasmer-wasm-interface",
|
"wasmer-wasm-interface",
|
||||||
"wasmparser 0.121.2",
|
"wasmparser 0.121.2",
|
||||||
|
"webc",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ rkyv = { version = "0.7.40", features = ["indexmap", "validation", "strict"] }
|
|||||||
memmap2 = { version = "0.6.2" }
|
memmap2 = { version = "0.6.2" }
|
||||||
edge-schema = { version = "=0.1.0" }
|
edge-schema = { version = "=0.1.0" }
|
||||||
indexmap = "1"
|
indexmap = "1"
|
||||||
|
serde_yaml = "0.9.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
test-generator = { path = "tests/lib/test-generator" }
|
test-generator = { path = "tests/lib/test-generator" }
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ rust-version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Wasmer dependencies.
|
# Wasmer dependencies.
|
||||||
edge-schema.workspace = true
|
edge-schema.workspace = true
|
||||||
|
wasmer-config.workspace = true
|
||||||
webc = "5"
|
webc = "5"
|
||||||
|
|
||||||
# crates.io dependencies.
|
# crates.io dependencies.
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ use std::{collections::HashSet, pin::Pin, time::Duration};
|
|||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use cynic::{MutationBuilder, QueryBuilder};
|
use cynic::{MutationBuilder, QueryBuilder};
|
||||||
use edge_schema::schema::{NetworkTokenV1, PackageIdentifier};
|
use edge_schema::schema::NetworkTokenV1;
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use wasmer_config::package::PackageIdent;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
@@ -24,10 +25,21 @@ use crate::{
|
|||||||
/// the API, and should not be used where possible.
|
/// the API, and should not be used where possible.
|
||||||
pub async fn fetch_webc_package(
|
pub async fn fetch_webc_package(
|
||||||
client: &WasmerClient,
|
client: &WasmerClient,
|
||||||
ident: &PackageIdentifier,
|
ident: &PackageIdent,
|
||||||
default_registry: &Url,
|
default_registry: &Url,
|
||||||
) -> Result<webc::compat::Container, anyhow::Error> {
|
) -> Result<webc::compat::Container, anyhow::Error> {
|
||||||
let url = ident.build_download_url_with_default_registry(default_registry);
|
let url = match ident {
|
||||||
|
PackageIdent::Named(n) => Url::parse(&format!(
|
||||||
|
"{default_registry}/{}:{}",
|
||||||
|
n.full_name(),
|
||||||
|
n.version_or_default()
|
||||||
|
))?,
|
||||||
|
PackageIdent::Hash(h) => match get_package_release(client, &h.to_string()).await? {
|
||||||
|
Some(webc) => Url::parse(&webc.webc_url)?,
|
||||||
|
None => anyhow::bail!("Could not find package with hash '{}'", h),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let data = client
|
let data = client
|
||||||
.client
|
.client
|
||||||
.get(url)
|
.get(url)
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ interfaces = { version = "0.0.9", optional = true }
|
|||||||
|
|
||||||
uuid = { version = "1.3.0", features = ["v4"] }
|
uuid = { version = "1.3.0", features = ["v4"] }
|
||||||
time = { version = "0.3.17", features = ["macros"] }
|
time = { version = "0.3.17", features = ["macros"] }
|
||||||
serde_yaml = "0.8.26"
|
serde_yaml = {workspace = true}
|
||||||
comfy-table = "7.0.1"
|
comfy-table = "7.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,46 @@
|
|||||||
//! Create a new Edge app.
|
//! Create a new Edge app.
|
||||||
|
|
||||||
use std::{path::PathBuf, str::FromStr};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
|
use clap::Parser;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dialoguer::Confirm;
|
use dialoguer::Confirm;
|
||||||
use edge_schema::schema::PackageIdentifier;
|
use indicatif::ProgressBar;
|
||||||
use is_terminal::IsTerminal;
|
use is_terminal::IsTerminal;
|
||||||
|
use std::{path::PathBuf, str::FromStr, time::Duration};
|
||||||
use wasmer_api::{
|
use wasmer_api::{
|
||||||
|
query::current_user_with_namespaces,
|
||||||
types::{DeployAppVersion, Package, UserWithNamespaces},
|
types::{DeployAppVersion, Package, UserWithNamespaces},
|
||||||
WasmerClient,
|
WasmerClient,
|
||||||
};
|
};
|
||||||
|
use wasmer_config::{
|
||||||
|
app::AppConfigV1,
|
||||||
|
package::{Manifest, NamedPackageIdent, PackageIdent, PackageSource, MANIFEST_FILE_NAME},
|
||||||
|
};
|
||||||
|
use wasmer_registry::wasmer_env::WasmerEnv;
|
||||||
|
|
||||||
|
const TICK: Duration = Duration::from_millis(250);
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{
|
||||||
app::{deploy_app_verbose, AppConfigV1, DeployAppOpts, WaitMode},
|
app::deploy::{deploy_app_verbose, DeployAppOpts, WaitMode},
|
||||||
AsyncCliCommand,
|
AsyncCliCommand, Login,
|
||||||
},
|
},
|
||||||
opts::{ApiOpts, ItemFormatOpts},
|
opts::{ApiOpts, ItemFormatOpts},
|
||||||
utils::package_wizard::{CreateMode, PackageType, PackageWizard},
|
utils::{
|
||||||
|
package_wizard::{CreateMode, PackageType, PackageWizard},
|
||||||
|
prompts::prompt_for_namespace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a new Edge app.
|
/// Create a new Edge app.
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
pub struct CmdAppCreate {
|
pub struct CmdAppCreate {
|
||||||
#[clap(name = "type", short = 't', long)]
|
#[clap(name = "type", short = 't', long)]
|
||||||
template: Option<AppType>,
|
pub template: Option<AppType>,
|
||||||
|
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
publish_package: bool,
|
pub publish_package: bool,
|
||||||
|
|
||||||
/// Skip local schema validation.
|
/// Skip local schema validation.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
@@ -46,10 +58,6 @@ pub struct CmdAppCreate {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub owner: Option<String>,
|
pub owner: Option<String>,
|
||||||
|
|
||||||
/// Name to use when creating a new package.
|
|
||||||
#[clap(long)]
|
|
||||||
pub new_package_name: Option<String>,
|
|
||||||
|
|
||||||
/// The name of the app (can be changed later)
|
/// The name of the app (can be changed later)
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
@@ -117,252 +125,256 @@ struct AppCreatorOutput {
|
|||||||
|
|
||||||
impl AppCreator {
|
impl AppCreator {
|
||||||
async fn build_browser_shell_app(self) -> Result<AppCreatorOutput, anyhow::Error> {
|
async fn build_browser_shell_app(self) -> Result<AppCreatorOutput, anyhow::Error> {
|
||||||
const WASM_BROWSER_CONTAINER_PACKAGE: &str = "wasmer/wasmer-sh";
|
todo!()
|
||||||
const WASM_BROWSER_CONTAINER_VERSION: &str = "0.2";
|
// const WASM_BROWSER_CONTAINER_PACKAGE: &str = "wasmer/wasmer-sh";
|
||||||
|
// const WASM_BROWSER_CONTAINER_VERSION: &str = "0.2";
|
||||||
|
|
||||||
eprintln!("A browser web shell wraps another package and runs it in the browser");
|
// eprintln!("A browser web shell wraps another package and runs it in the browser");
|
||||||
eprintln!("Select the package to wrap.");
|
// eprintln!("Select the package to wrap.");
|
||||||
|
|
||||||
let (inner_pkg, _inner_pkg_api) = crate::utils::prompt_for_package(
|
// let (inner_pkg, _inner_pkg_api) = crate::utils::prompt_for_package(
|
||||||
"Package",
|
// "Package",
|
||||||
None,
|
// None,
|
||||||
Some(crate::utils::PackageCheckMode::MustExist),
|
// Some(crate::utils::PackageCheckMode::MustExist),
|
||||||
self.api.as_ref(),
|
// self.api.as_ref(),
|
||||||
)
|
// )
|
||||||
.await?;
|
// .await?;
|
||||||
|
|
||||||
eprintln!("What should be the name of the wrapper package?");
|
// eprintln!("What should be the name of the wrapper package?");
|
||||||
|
|
||||||
let default_name = format!("{}-webshell", inner_pkg.name);
|
// let default_name = format!("{}-webshell", inner_pkg.name);
|
||||||
let outer_pkg_name =
|
// let outer_pkg_name =
|
||||||
crate::utils::prompts::prompt_for_ident("Package name", Some(&default_name))?;
|
// crate::utils::prompts::prompt_for_ident("Package name", Some(&default_name))?;
|
||||||
let outer_pkg_full_name = format!("{}/{}", self.owner, outer_pkg_name);
|
// let outer_pkg_full_name = format!("{}/{}", self.owner, outer_pkg_name);
|
||||||
|
|
||||||
eprintln!("What should be the name of the app?");
|
// eprintln!("What should be the name of the app?");
|
||||||
|
|
||||||
let default_name = if outer_pkg_name.ends_with("webshell") {
|
// let default_name = if outer_pkg_name.ends_with("webshell") {
|
||||||
format!("{}-{}", self.owner, outer_pkg_name)
|
// format!("{}-{}", self.owner, outer_pkg_name)
|
||||||
} else {
|
// } else {
|
||||||
format!("{}-{}-webshell", self.owner, outer_pkg_name)
|
// format!("{}-{}-webshell", self.owner, outer_pkg_name)
|
||||||
};
|
// };
|
||||||
let app_name = crate::utils::prompts::prompt_for_ident("App name", Some(&default_name))?;
|
// let app_name = crate::utils::prompts::prompt_for_ident("App name", Some(&default_name))?;
|
||||||
|
|
||||||
// Build the package.
|
// // Build the package.
|
||||||
|
|
||||||
let public_dir = self.dir.join("public");
|
// let public_dir = self.dir.join("public");
|
||||||
if !public_dir.exists() {
|
// if !public_dir.exists() {
|
||||||
std::fs::create_dir_all(&public_dir)?;
|
// std::fs::create_dir_all(&public_dir)?;
|
||||||
}
|
// }
|
||||||
|
|
||||||
let init = serde_json::json!({
|
// let init = serde_json::json!({
|
||||||
"init": format!("{}/{}", inner_pkg.namespace, inner_pkg.name),
|
// "init": format!("{}/{}", inner_pkg.namespace.unwrap(), inner_pkg.name),
|
||||||
"prompt": inner_pkg.name,
|
// "prompt": inner_pkg.name,
|
||||||
"no_welcome": true,
|
// "no_welcome": true,
|
||||||
"connect": format!("wss://{app_name}.wasmer.app/.well-known/edge-vpn"),
|
// "connect": format!("wss://{app_name}.wasmer.app/.well-known/edge-vpn"),
|
||||||
});
|
// });
|
||||||
let init_path = public_dir.join("init.json");
|
// let init_path = public_dir.join("init.json");
|
||||||
std::fs::write(&init_path, init.to_string())
|
// std::fs::write(&init_path, init.to_string())
|
||||||
.with_context(|| format!("Failed to write to '{}'", init_path.display()))?;
|
// .with_context(|| format!("Failed to write to '{}'", init_path.display()))?;
|
||||||
|
|
||||||
let package = wasmer_config::package::PackageBuilder::new(
|
// let package = wasmer_config::package::PackageBuilder::new(
|
||||||
outer_pkg_full_name,
|
// outer_pkg_full_name,
|
||||||
"0.1.0".parse().unwrap(),
|
// "0.1.0".parse().unwrap(),
|
||||||
format!("{} web shell", inner_pkg.name),
|
// format!("{} web shell", inner_pkg.name),
|
||||||
)
|
// )
|
||||||
.rename_commands_to_raw_command_name(false)
|
// .rename_commands_to_raw_command_name(false)
|
||||||
.build()?;
|
// .build()?;
|
||||||
|
|
||||||
let manifest = wasmer_config::package::ManifestBuilder::new(package)
|
// let manifest = wasmer_config::package::ManifestBuilder::new(package)
|
||||||
.with_dependency(
|
// .with_dependency(
|
||||||
WASM_BROWSER_CONTAINER_PACKAGE,
|
// WASM_BROWSER_CONTAINER_PACKAGE,
|
||||||
WASM_BROWSER_CONTAINER_VERSION.to_string().parse().unwrap(),
|
// WASM_BROWSER_CONTAINER_VERSION.to_string().parse().unwrap(),
|
||||||
)
|
// )
|
||||||
.map_fs("public", PathBuf::from("public"))
|
// .map_fs("public", PathBuf::from("public"))
|
||||||
.build()?;
|
// .build()?;
|
||||||
|
|
||||||
let manifest_path = self.dir.join("wasmer.toml");
|
// let manifest_path = self.dir.join("wasmer.toml");
|
||||||
|
|
||||||
let raw = manifest.to_string()?;
|
// let raw = manifest.to_string()?;
|
||||||
eprintln!(
|
// eprintln!(
|
||||||
"Writing wasmer.toml package to '{}'",
|
// "Writing wasmer.toml package to '{}'",
|
||||||
manifest_path.display()
|
// manifest_path.display()
|
||||||
);
|
// );
|
||||||
std::fs::write(&manifest_path, raw)?;
|
// std::fs::write(&manifest_path, raw)?;
|
||||||
|
|
||||||
let app_cfg = AppConfigV1 {
|
// let app_cfg = AppConfigV1 {
|
||||||
app_id: None,
|
// app_id: None,
|
||||||
name: app_name,
|
// name: app_name,
|
||||||
owner: Some(self.owner.clone()),
|
// owner: Some(self.owner.clone()),
|
||||||
cli_args: None,
|
// cli_args: None,
|
||||||
env: Default::default(),
|
// env: Default::default(),
|
||||||
volumes: None,
|
// volumes: None,
|
||||||
domains: None,
|
// domains: None,
|
||||||
scaling: None,
|
// scaling: None,
|
||||||
package: edge_schema::schema::PackageIdentifier {
|
// package: NamedPackageIdent {
|
||||||
repository: None,
|
// registry: None,
|
||||||
namespace: self.owner,
|
// namespace: Some(self.owner),
|
||||||
name: outer_pkg_name,
|
// name: outer_pkg_name,
|
||||||
tag: None,
|
// tag: None,
|
||||||
}
|
// }
|
||||||
.into(),
|
// .into(),
|
||||||
capabilities: None,
|
// capabilities: None,
|
||||||
scheduled_tasks: None,
|
// scheduled_tasks: None,
|
||||||
debug: Some(false),
|
// debug: Some(false),
|
||||||
extra: Default::default(),
|
// extra: Default::default(),
|
||||||
health_checks: None,
|
// health_checks: None,
|
||||||
};
|
// };
|
||||||
|
|
||||||
Ok(AppCreatorOutput {
|
// Ok(AppCreatorOutput {
|
||||||
app: app_cfg,
|
// app: app_cfg,
|
||||||
api_pkg: None,
|
// api_pkg: None,
|
||||||
local_package: Some((self.dir, manifest)),
|
// local_package: Some((self.dir, manifest)),
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn build_app(self) -> Result<AppCreatorOutput, anyhow::Error> {
|
async fn build_app(self) -> Result<AppCreatorOutput, anyhow::Error> {
|
||||||
let package_opt: Option<PackageIdentifier> = if let Some(package) = self.package {
|
todo!()
|
||||||
Some(package.parse()?)
|
// let package_opt: Option<PackageIdent> = if let Some(package) = self.package {
|
||||||
} else if let Some((_, local)) = self.local_package.as_ref() {
|
// Some(package.parse()?)
|
||||||
let full = format!(
|
// } else if let Some((_, local)) = self.local_package.as_ref() {
|
||||||
"{}@{}",
|
// let full = format!(
|
||||||
local.package.clone().unwrap().name,
|
// "{}@{}",
|
||||||
local.package.clone().unwrap().version
|
// local.package.clone().unwrap().name,
|
||||||
);
|
// local.package.clone().unwrap().version
|
||||||
let mut pkg_ident = PackageIdentifier::from_str(&local.package.clone().unwrap().name)
|
// );
|
||||||
.with_context(|| {
|
// let mut pkg_ident = NamedPackageIdent::from_str(&local.package.clone().unwrap().name)
|
||||||
format!("local package manifest has invalid name: '{full}'")
|
// .with_context(|| {
|
||||||
})?;
|
// format!("local package manifest has invalid name: '{full}'")
|
||||||
|
// })?;
|
||||||
|
// // pkg
|
||||||
|
// // Pin the version.
|
||||||
|
// pkg_ident.tag = Some(wasmer_config::package::Tag::VersionReq(
|
||||||
|
// local.package.clone().unwrap().version.,
|
||||||
|
// ));
|
||||||
|
|
||||||
// Pin the version.
|
// if self.interactive {
|
||||||
pkg_ident.tag = Some(local.package.clone().unwrap().version.to_string());
|
// eprintln!("Found local package: '{}'", full.green());
|
||||||
|
|
||||||
if self.interactive {
|
// let msg = format!("Use package '{pkg_ident}'");
|
||||||
eprintln!("Found local package: '{}'", full.green());
|
|
||||||
|
|
||||||
let msg = format!("Use package '{pkg_ident}'");
|
// let should_use = Confirm::new()
|
||||||
|
// .with_prompt(&msg)
|
||||||
|
// .interact_opt()?
|
||||||
|
// .unwrap_or_default();
|
||||||
|
|
||||||
let should_use = Confirm::new()
|
// if should_use {
|
||||||
.with_prompt(&msg)
|
// Some(pkg_ident)
|
||||||
.interact_opt()?
|
// } else {
|
||||||
.unwrap_or_default();
|
// None
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Some(pkg_ident)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// None
|
||||||
|
// };
|
||||||
|
|
||||||
if should_use {
|
// let (pkg, api_pkg, local_package) = if let Some(pkg) = package_opt {
|
||||||
Some(pkg_ident)
|
// if let Some(api) = &self.api {
|
||||||
} else {
|
// let p2 =
|
||||||
None
|
// wasmer_api::query::get_package(api, format!("{}/{}", pkg.namespace, pkg.name))
|
||||||
}
|
// .await?;
|
||||||
} else {
|
|
||||||
Some(pkg_ident)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let (pkg, api_pkg, local_package) = if let Some(pkg) = package_opt {
|
// (pkg.into(), p2, self.local_package)
|
||||||
if let Some(api) = &self.api {
|
// } else {
|
||||||
let p2 =
|
// (pkg.into(), None, self.local_package)
|
||||||
wasmer_api::query::get_package(api, format!("{}/{}", pkg.namespace, pkg.name))
|
// }
|
||||||
.await?;
|
// } else {
|
||||||
|
// eprintln!("No package found or specified.");
|
||||||
|
|
||||||
(pkg.into(), p2, self.local_package)
|
// let ty = match self.type_ {
|
||||||
} else {
|
// AppType::HttpServer => None,
|
||||||
(pkg.into(), None, self.local_package)
|
// AppType::StaticWebsite => Some(PackageType::StaticWebsite),
|
||||||
}
|
// AppType::BrowserShell => None,
|
||||||
} else {
|
// AppType::JsWorker => Some(PackageType::JsWorker),
|
||||||
eprintln!("No package found or specified.");
|
// AppType::PyApplication => Some(PackageType::PyApplication),
|
||||||
|
// };
|
||||||
|
|
||||||
let ty = match self.type_ {
|
// let create_mode = match ty {
|
||||||
AppType::HttpServer => None,
|
// Some(PackageType::StaticWebsite)
|
||||||
AppType::StaticWebsite => Some(PackageType::StaticWebsite),
|
// | Some(PackageType::JsWorker)
|
||||||
AppType::BrowserShell => None,
|
// | Some(PackageType::PyApplication) => CreateMode::Create,
|
||||||
AppType::JsWorker => Some(PackageType::JsWorker),
|
// // Only static website creation is currently supported.
|
||||||
AppType::PyApplication => Some(PackageType::PyApplication),
|
// _ => CreateMode::SelectExisting,
|
||||||
};
|
// };
|
||||||
|
|
||||||
let create_mode = match ty {
|
// let w = PackageWizard {
|
||||||
Some(PackageType::StaticWebsite)
|
// path: self.dir.clone(),
|
||||||
| Some(PackageType::JsWorker)
|
// name: self.new_package_name.clone(),
|
||||||
| Some(PackageType::PyApplication) => CreateMode::Create,
|
// type_: ty,
|
||||||
// Only static website creation is currently supported.
|
// create_mode,
|
||||||
_ => CreateMode::SelectExisting,
|
// namespace: Some(self.owner.clone()),
|
||||||
};
|
// namespace_default: self.user.as_ref().map(|u| u.username.clone()),
|
||||||
|
// user: self.user.clone(),
|
||||||
|
// };
|
||||||
|
|
||||||
let w = PackageWizard {
|
// let output = w.run(self.api.as_ref()).await?;
|
||||||
path: self.dir.clone(),
|
// (
|
||||||
name: self.new_package_name.clone(),
|
// output.ident,
|
||||||
type_: ty,
|
// output.api,
|
||||||
create_mode,
|
// output
|
||||||
namespace: Some(self.owner.clone()),
|
// .local_path
|
||||||
namespace_default: self.user.as_ref().map(|u| u.username.clone()),
|
// .and_then(move |x| Some((x, output.local_manifest?))),
|
||||||
user: self.user.clone(),
|
// )
|
||||||
};
|
// };
|
||||||
|
|
||||||
let output = w.run(self.api.as_ref()).await?;
|
// let ident = pkg.as_ident().context("unnamed packages not supported")?;
|
||||||
(
|
|
||||||
output.ident,
|
|
||||||
output.api,
|
|
||||||
output
|
|
||||||
.local_path
|
|
||||||
.and_then(move |x| Some((x, output.local_manifest?))),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let ident = pkg.as_ident().context("unnamed packages not supported")?;
|
// let name = if let Some(name) = self.app_name {
|
||||||
|
// name
|
||||||
|
// } else {
|
||||||
|
// let default = match self.type_ {
|
||||||
|
// AppType::HttpServer | AppType::StaticWebsite => {
|
||||||
|
// format!("{}-{}", ident.namespace, ident.name)
|
||||||
|
// }
|
||||||
|
// AppType::JsWorker | AppType::PyApplication => {
|
||||||
|
// format!("{}-{}-worker", ident.namespace, ident.name)
|
||||||
|
// }
|
||||||
|
// AppType::BrowserShell => {
|
||||||
|
// format!("{}-{}-webshell", ident.namespace, ident.name)
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
let name = if let Some(name) = self.app_name {
|
// dialoguer::Input::new()
|
||||||
name
|
// .with_prompt("What should be the name of the app? <NAME>.wasmer.app")
|
||||||
} else {
|
// .with_initial_text(default)
|
||||||
let default = match self.type_ {
|
// .interact_text()
|
||||||
AppType::HttpServer | AppType::StaticWebsite => {
|
// .unwrap()
|
||||||
format!("{}-{}", ident.namespace, ident.name)
|
// };
|
||||||
}
|
|
||||||
AppType::JsWorker | AppType::PyApplication => {
|
|
||||||
format!("{}-{}-worker", ident.namespace, ident.name)
|
|
||||||
}
|
|
||||||
AppType::BrowserShell => {
|
|
||||||
format!("{}-{}-webshell", ident.namespace, ident.name)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dialoguer::Input::new()
|
// let cli_args = match self.type_ {
|
||||||
.with_prompt("What should be the name of the app? <NAME>.wasmer.app")
|
// AppType::PyApplication => Some(vec!["/src/main.py".to_string()]),
|
||||||
.with_initial_text(default)
|
// AppType::JsWorker => Some(vec!["/src/index.js".to_string()]),
|
||||||
.interact_text()
|
// _ => None,
|
||||||
.unwrap()
|
// };
|
||||||
};
|
|
||||||
|
|
||||||
let cli_args = match self.type_ {
|
// // TODO: check if name already exists.
|
||||||
AppType::PyApplication => Some(vec!["/src/main.py".to_string()]),
|
// let cfg = AppConfigV1 {
|
||||||
AppType::JsWorker => Some(vec!["/src/index.js".to_string()]),
|
// app_id: None,
|
||||||
_ => None,
|
// owner: Some(self.owner.clone()),
|
||||||
};
|
// volumes: None,
|
||||||
|
// name,
|
||||||
|
// env: Default::default(),
|
||||||
|
// scaling: None,
|
||||||
|
// // CLI args are only set for JS and Py workers for now.
|
||||||
|
// cli_args,
|
||||||
|
// // TODO: allow setting the description.
|
||||||
|
// // description: Some("".to_string()),
|
||||||
|
// package: pkg.clone(),
|
||||||
|
// capabilities: None,
|
||||||
|
// scheduled_tasks: None,
|
||||||
|
// debug: Some(false),
|
||||||
|
// domains: None,
|
||||||
|
// extra: Default::default(),
|
||||||
|
// health_checks: None,
|
||||||
|
// };
|
||||||
|
|
||||||
// TODO: check if name already exists.
|
// Ok(AppCreatorOutput {
|
||||||
let cfg = AppConfigV1 {
|
// app: cfg,
|
||||||
app_id: None,
|
// api_pkg,
|
||||||
owner: Some(self.owner.clone()),
|
// local_package,
|
||||||
volumes: None,
|
// })
|
||||||
name,
|
|
||||||
env: Default::default(),
|
|
||||||
scaling: None,
|
|
||||||
// CLI args are only set for JS and Py workers for now.
|
|
||||||
cli_args,
|
|
||||||
// TODO: allow setting the description.
|
|
||||||
// description: Some("".to_string()),
|
|
||||||
package: pkg.clone(),
|
|
||||||
capabilities: None,
|
|
||||||
scheduled_tasks: None,
|
|
||||||
debug: Some(false),
|
|
||||||
domains: None,
|
|
||||||
extra: Default::default(),
|
|
||||||
health_checks: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(AppCreatorOutput {
|
|
||||||
app: cfg,
|
|
||||||
api_pkg,
|
|
||||||
local_package,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,212 +383,7 @@ impl AsyncCliCommand for CmdAppCreate {
|
|||||||
type Output = (AppConfigV1, Option<DeployAppVersion>);
|
type Output = (AppConfigV1, Option<DeployAppVersion>);
|
||||||
|
|
||||||
async fn run_async(self) -> Result<(AppConfigV1, Option<DeployAppVersion>), anyhow::Error> {
|
async fn run_async(self) -> Result<(AppConfigV1, Option<DeployAppVersion>), anyhow::Error> {
|
||||||
let interactive = self.non_interactive == false && std::io::stdin().is_terminal();
|
todo!()
|
||||||
|
|
||||||
let base_path = if let Some(p) = self.path {
|
|
||||||
p
|
|
||||||
} else {
|
|
||||||
std::env::current_dir()?
|
|
||||||
};
|
|
||||||
|
|
||||||
let (base_dir, appcfg_path) = if base_path.is_file() {
|
|
||||||
let dir = base_path
|
|
||||||
.canonicalize()?
|
|
||||||
.parent()
|
|
||||||
.context("could not determine parent directory")?
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
(dir, base_path)
|
|
||||||
} else if base_path.is_dir() {
|
|
||||||
let full = base_path.join(AppConfigV1::CANONICAL_FILE_NAME);
|
|
||||||
(base_path, full)
|
|
||||||
} else {
|
|
||||||
bail!("No such file or directory: '{}'", base_path.display());
|
|
||||||
};
|
|
||||||
|
|
||||||
if appcfg_path.is_file() {
|
|
||||||
bail!(
|
|
||||||
"App configuration file already exists at '{}'",
|
|
||||||
appcfg_path.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let api = if self.offline {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.api.client()?)
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = if let Some(api) = &api {
|
|
||||||
let u = wasmer_api::query::current_user_with_namespaces(
|
|
||||||
api,
|
|
||||||
Some(wasmer_api::types::GrapheneRole::Admin),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Some(u)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let type_ = match self.template {
|
|
||||||
Some(t) => t,
|
|
||||||
None => {
|
|
||||||
if interactive {
|
|
||||||
let index = dialoguer::Select::new()
|
|
||||||
.with_prompt("App type")
|
|
||||||
.default(0)
|
|
||||||
.items(&[
|
|
||||||
"Static website",
|
|
||||||
"HTTP server",
|
|
||||||
"Browser shell",
|
|
||||||
"JS Worker (WinterJS)",
|
|
||||||
"Python Application",
|
|
||||||
])
|
|
||||||
.interact()?;
|
|
||||||
match index {
|
|
||||||
0 => AppType::StaticWebsite,
|
|
||||||
1 => AppType::HttpServer,
|
|
||||||
2 => AppType::BrowserShell,
|
|
||||||
3 => AppType::JsWorker,
|
|
||||||
4 => AppType::PyApplication,
|
|
||||||
x => panic!("unhandled app type index '{x}'"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("No app type specified: use --type XXX");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let owner = if let Some(owner) = self.owner {
|
|
||||||
owner
|
|
||||||
} else if interactive {
|
|
||||||
crate::utils::prompts::prompt_for_namespace(
|
|
||||||
"Who should own this package?",
|
|
||||||
None,
|
|
||||||
user.as_ref(),
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
bail!("No owner specified: use --owner XXX");
|
|
||||||
};
|
|
||||||
|
|
||||||
let allow_local_package = match type_ {
|
|
||||||
AppType::HttpServer => true,
|
|
||||||
AppType::StaticWebsite => true,
|
|
||||||
AppType::BrowserShell => false,
|
|
||||||
AppType::JsWorker => true,
|
|
||||||
AppType::PyApplication => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let local_package = if allow_local_package {
|
|
||||||
match crate::utils::load_package_manifest(&base_dir) {
|
|
||||||
Ok(Some(p)) => Some(p),
|
|
||||||
Ok(None) => None,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"{warning}: could not load package manifest: {err}",
|
|
||||||
warning = "Warning".yellow(),
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let creator = AppCreator {
|
|
||||||
app_name: self.name,
|
|
||||||
new_package_name: self.new_package_name,
|
|
||||||
package: self.package,
|
|
||||||
type_,
|
|
||||||
interactive,
|
|
||||||
dir: base_dir,
|
|
||||||
owner: owner.clone(),
|
|
||||||
api,
|
|
||||||
user,
|
|
||||||
local_package,
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = match type_ {
|
|
||||||
AppType::HttpServer
|
|
||||||
| AppType::StaticWebsite
|
|
||||||
| AppType::JsWorker
|
|
||||||
| AppType::PyApplication => creator.build_app().await?,
|
|
||||||
AppType::BrowserShell => creator.build_browser_shell_app().await?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let AppCreatorOutput {
|
|
||||||
app: cfg,
|
|
||||||
api_pkg,
|
|
||||||
local_package,
|
|
||||||
..
|
|
||||||
} = output;
|
|
||||||
|
|
||||||
let deploy_now = if self.offline {
|
|
||||||
false
|
|
||||||
} else if self.non_interactive {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
Confirm::new()
|
|
||||||
.with_prompt("Would you like to publish the app now?".to_string())
|
|
||||||
.interact()?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make sure to write out the app.yaml to avoid not creating it when the
|
|
||||||
// publish or deploy step fails.
|
|
||||||
// (the later flow only writes a new app.yaml after a success)
|
|
||||||
let raw_app_config = cfg.clone().to_yaml()?;
|
|
||||||
std::fs::write(&appcfg_path, raw_app_config).with_context(|| {
|
|
||||||
format!("could not write app config to '{}'", appcfg_path.display())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (final_config, app_version) = if deploy_now {
|
|
||||||
eprintln!("Creating the app...");
|
|
||||||
|
|
||||||
let api = self.api.client()?;
|
|
||||||
|
|
||||||
if api_pkg.is_none() {
|
|
||||||
if let Some((path, manifest)) = &local_package {
|
|
||||||
eprintln!("Publishing package...");
|
|
||||||
let manifest = manifest.clone();
|
|
||||||
crate::utils::republish_package(&api, path, manifest, None).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw_config = cfg.clone().to_yaml()?;
|
|
||||||
std::fs::write(&appcfg_path, raw_config).with_context(|| {
|
|
||||||
format!("could not write config to '{}'", appcfg_path.display())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let wait_mode = if self.no_wait {
|
|
||||||
WaitMode::Deployed
|
|
||||||
} else {
|
|
||||||
WaitMode::Reachable
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts = DeployAppOpts {
|
|
||||||
app: &cfg,
|
|
||||||
original_config: None,
|
|
||||||
allow_create: true,
|
|
||||||
make_default: true,
|
|
||||||
owner: Some(owner.clone()),
|
|
||||||
wait: wait_mode,
|
|
||||||
};
|
|
||||||
let (_app, app_version) = deploy_app_verbose(&api, opts).await?;
|
|
||||||
|
|
||||||
let new_cfg = super::app_config_from_api(&app_version)?;
|
|
||||||
(new_cfg, Some(app_version))
|
|
||||||
} else {
|
|
||||||
(cfg, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!("Writing app config to '{}'", appcfg_path.display());
|
|
||||||
let raw_final_config = final_config.clone().to_yaml()?;
|
|
||||||
std::fs::write(&appcfg_path, raw_final_config)
|
|
||||||
.with_context(|| format!("could not write config to '{}'", appcfg_path.display()))?;
|
|
||||||
|
|
||||||
eprintln!("To (re)deploy your app, run 'wasmer deploy'");
|
|
||||||
|
|
||||||
Ok((final_config, app_version))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,13 +402,12 @@ mod tests {
|
|||||||
non_interactive: true,
|
non_interactive: true,
|
||||||
offline: true,
|
offline: true,
|
||||||
owner: Some("testuser".to_string()),
|
owner: Some("testuser".to_string()),
|
||||||
new_package_name: Some("static-site-1".to_string()),
|
|
||||||
name: Some("static-site-1".to_string()),
|
name: Some("static-site-1".to_string()),
|
||||||
path: Some(dir.path().to_owned()),
|
path: Some(dir.path().to_owned()),
|
||||||
no_wait: true,
|
no_wait: true,
|
||||||
api: ApiOpts::default(),
|
api: ApiOpts::default(),
|
||||||
fmt: ItemFormatOpts::default(),
|
fmt: ItemFormatOpts::default(),
|
||||||
package: None,
|
package: Some("testuser/static-site1@0.1.0".to_string()),
|
||||||
};
|
};
|
||||||
cmd.run_async().await.unwrap();
|
cmd.run_async().await.unwrap();
|
||||||
|
|
||||||
@@ -629,7 +435,6 @@ debug: false
|
|||||||
non_interactive: true,
|
non_interactive: true,
|
||||||
offline: true,
|
offline: true,
|
||||||
owner: Some("wasmer".to_string()),
|
owner: Some("wasmer".to_string()),
|
||||||
new_package_name: None,
|
|
||||||
name: Some("testapp".to_string()),
|
name: Some("testapp".to_string()),
|
||||||
path: Some(dir.path().to_owned()),
|
path: Some(dir.path().to_owned()),
|
||||||
no_wait: true,
|
no_wait: true,
|
||||||
@@ -662,7 +467,6 @@ debug: false
|
|||||||
non_interactive: true,
|
non_interactive: true,
|
||||||
offline: true,
|
offline: true,
|
||||||
owner: Some("wasmer".to_string()),
|
owner: Some("wasmer".to_string()),
|
||||||
new_package_name: None,
|
|
||||||
name: Some("test-js-worker".to_string()),
|
name: Some("test-js-worker".to_string()),
|
||||||
path: Some(dir.path().to_owned()),
|
path: Some(dir.path().to_owned()),
|
||||||
no_wait: true,
|
no_wait: true,
|
||||||
@@ -698,7 +502,6 @@ debug: false
|
|||||||
non_interactive: true,
|
non_interactive: true,
|
||||||
offline: true,
|
offline: true,
|
||||||
owner: Some("wasmer".to_string()),
|
owner: Some("wasmer".to_string()),
|
||||||
new_package_name: None,
|
|
||||||
name: Some("test-py-worker".to_string()),
|
name: Some("test-py-worker".to_string()),
|
||||||
path: Some(dir.path().to_owned()),
|
path: Some(dir.path().to_owned()),
|
||||||
no_wait: true,
|
no_wait: true,
|
||||||
|
|||||||
448
lib/cli/src/commands/app/deploy.rs
Normal file
448
lib/cli/src/commands/app/deploy.rs
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
use super::AsyncCliCommand;
|
||||||
|
use crate::{
|
||||||
|
commands::{app::create::CmdAppCreate, package, Publish},
|
||||||
|
opts::{ApiOpts, ItemFormatOpts},
|
||||||
|
utils::load_package_manifest,
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::{path::PathBuf, str::FromStr, time::Duration};
|
||||||
|
use wasmer_api::{
|
||||||
|
types::{DeployApp, DeployAppVersion},
|
||||||
|
WasmerClient,
|
||||||
|
};
|
||||||
|
use wasmer_config::{
|
||||||
|
app::AppConfigV1,
|
||||||
|
package::{PackageIdent, PackageSource},
|
||||||
|
};
|
||||||
|
use wasmer_registry::wasmer_env::WasmerEnv;
|
||||||
|
|
||||||
|
/// Deploy an app to Wasmer Edge.
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
pub struct CmdAppDeploy {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub api: ApiOpts,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub fmt: ItemFormatOpts,
|
||||||
|
|
||||||
|
/// Skip local schema validation.
|
||||||
|
#[clap(long)]
|
||||||
|
pub no_validate: bool,
|
||||||
|
|
||||||
|
/// Do not prompt for user input.
|
||||||
|
#[clap(long)]
|
||||||
|
pub non_interactive: 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,
|
||||||
|
|
||||||
|
/// The path to the app.yaml file.
|
||||||
|
#[clap(long)]
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Do not wait for the app to become reachable.
|
||||||
|
#[clap(long)]
|
||||||
|
pub no_wait: bool,
|
||||||
|
|
||||||
|
/// Do not make the new app version the default (active) version.
|
||||||
|
/// This is useful for testing a deployment first, before moving it to "production".
|
||||||
|
#[clap(long)]
|
||||||
|
pub no_default: bool,
|
||||||
|
|
||||||
|
/// Do not persist the app version ID in the app.yaml.
|
||||||
|
#[clap(long)]
|
||||||
|
pub no_persist_id: bool,
|
||||||
|
|
||||||
|
/// Specify the owner (user or namespace) of the app.
|
||||||
|
/// Will default to the currently logged in user, or the existing one
|
||||||
|
/// if the app can be found.
|
||||||
|
#[clap(long)]
|
||||||
|
pub owner: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdAppDeploy {
|
||||||
|
async fn publish(
|
||||||
|
&self,
|
||||||
|
owner: String,
|
||||||
|
manifest_dir_path: PathBuf,
|
||||||
|
) -> anyhow::Result<PackageIdent> {
|
||||||
|
let (_, manifest) = match load_package_manifest(&manifest_dir_path)? {
|
||||||
|
Some(r) => r,
|
||||||
|
None => anyhow::bail!(
|
||||||
|
"Could not read or find manifest in path '{}'!",
|
||||||
|
manifest_dir_path.display()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let publish_cmd = Publish {
|
||||||
|
env: WasmerEnv::default(),
|
||||||
|
dry_run: false,
|
||||||
|
quiet: false,
|
||||||
|
package_name: None,
|
||||||
|
version: None,
|
||||||
|
no_validate: false,
|
||||||
|
package_path: Some(manifest_dir_path.to_str().unwrap().to_string()),
|
||||||
|
wait: !self.no_wait,
|
||||||
|
wait_all: false,
|
||||||
|
timeout: humantime::Duration::from_str("2m").unwrap(),
|
||||||
|
package_namespace: match manifest.package {
|
||||||
|
Some(_) => None,
|
||||||
|
None => Some(owner),
|
||||||
|
},
|
||||||
|
non_interactive: self.non_interactive,
|
||||||
|
};
|
||||||
|
|
||||||
|
match publish_cmd.run_async().await? {
|
||||||
|
Some(id) => Ok(id),
|
||||||
|
None => anyhow::bail!("Error while publishing package. Stopping."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_owner(&self, app: &AppConfigV1) -> anyhow::Result<String> {
|
||||||
|
if let Some(owner) = &app.owner {
|
||||||
|
return Ok(owner.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(std::io::stdin().is_terminal() && !self.non_interactive) {
|
||||||
|
// if not interactive we can't prompt the user to choose the owner of the app.
|
||||||
|
anyhow::bail!("No owner specified: use --owner XXX");
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.api.client() {
|
||||||
|
Ok(client) => {
|
||||||
|
let user = wasmer_api::query::current_user_with_namespaces(&client, None).await?;
|
||||||
|
crate::utils::prompts::prompt_for_namespace(
|
||||||
|
"Who should own this package?",
|
||||||
|
None,
|
||||||
|
Some(&user),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(e) => anyhow::bail!(
|
||||||
|
"Can't determine user info: {e}. Please, user `wasmer login` before deploying an app or use the --owner <owner> flag to signal the owner of the app to deploy."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AsyncCliCommand for CmdAppDeploy {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
|
||||||
|
let interactive = std::io::stdin().is_terminal() && !self.non_interactive;
|
||||||
|
let client = self
|
||||||
|
.api
|
||||||
|
.client()
|
||||||
|
.with_context(|| "Can't begin deploy flow")?;
|
||||||
|
|
||||||
|
let app_config_path = {
|
||||||
|
let base_path = self.path.clone().unwrap_or(std::env::current_dir()?);
|
||||||
|
if base_path.is_file() {
|
||||||
|
base_path
|
||||||
|
} else if base_path.is_dir() {
|
||||||
|
let f = base_path.join(AppConfigV1::CANONICAL_FILE_NAME);
|
||||||
|
if !f.is_file() {
|
||||||
|
anyhow::bail!("Could not find app.yaml at path '{}'", f.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
f
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No such file or directory '{}'", base_path.display());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !app_config_path.is_file() {
|
||||||
|
if interactive {
|
||||||
|
eprintln!("It seems you are trying to create a new app!");
|
||||||
|
|
||||||
|
let create_cmd = CmdAppCreate {
|
||||||
|
template: None,
|
||||||
|
publish_package: false,
|
||||||
|
no_validate: false,
|
||||||
|
non_interactive: false,
|
||||||
|
offline: false,
|
||||||
|
owner: None,
|
||||||
|
name: None,
|
||||||
|
path: None,
|
||||||
|
no_wait: false,
|
||||||
|
api: self.api.clone(),
|
||||||
|
fmt: ItemFormatOpts {
|
||||||
|
format: self.fmt.format.clone(),
|
||||||
|
},
|
||||||
|
package: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
create_cmd.run_async().await?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Cannot deploy app as no app.yaml was found in path '{}'",
|
||||||
|
app_config_path.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(app_config_path.is_file());
|
||||||
|
|
||||||
|
let config_str = std::fs::read_to_string(&app_config_path)
|
||||||
|
.with_context(|| format!("Could not read file '{}'", app_config_path.display()))?;
|
||||||
|
|
||||||
|
let mut app_config: AppConfigV1 = AppConfigV1::parse_yaml(&config_str)?;
|
||||||
|
eprintln!("Loaded app from: '{}'", app_config_path.display());
|
||||||
|
|
||||||
|
let owner = self.get_owner(&app_config).await?;
|
||||||
|
|
||||||
|
let wait = if self.no_wait {
|
||||||
|
WaitMode::Deployed
|
||||||
|
} else {
|
||||||
|
WaitMode::Reachable
|
||||||
|
};
|
||||||
|
|
||||||
|
let opts = match app_config.package {
|
||||||
|
PackageSource::Path(ref path) => {
|
||||||
|
eprintln!("Checking local package at path '{}'...", path);
|
||||||
|
let package =
|
||||||
|
PackageSource::from(self.publish(owner.clone(), PathBuf::from(path)).await?);
|
||||||
|
|
||||||
|
// We should now assume that the package pointed to by the path is now published,
|
||||||
|
// and `package_spec` is either a hash or an identifier.
|
||||||
|
|
||||||
|
app_config.package = package;
|
||||||
|
|
||||||
|
DeployAppOpts {
|
||||||
|
app: &app_config,
|
||||||
|
original_config: Some(app_config.clone().to_yaml_value().unwrap()),
|
||||||
|
allow_create: true,
|
||||||
|
make_default: !self.no_default,
|
||||||
|
owner: Some(owner),
|
||||||
|
wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
eprintln!("Using package {}", app_config.package.to_string());
|
||||||
|
DeployAppOpts {
|
||||||
|
app: &app_config,
|
||||||
|
original_config: Some(app_config.clone().to_yaml_value().unwrap()),
|
||||||
|
allow_create: true,
|
||||||
|
make_default: !self.no_default,
|
||||||
|
owner: Some(owner),
|
||||||
|
wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_app, app_version) = deploy_app_verbose(&client, opts).await?;
|
||||||
|
|
||||||
|
let mut new_app_config = app_config_from_api(&app_version)?;
|
||||||
|
if self.no_persist_id {
|
||||||
|
new_app_config.app_id = None;
|
||||||
|
}
|
||||||
|
// If the config changed, write it back.
|
||||||
|
if new_app_config != app_config {
|
||||||
|
// We want to preserve unknown fields to allow for newer app.yaml
|
||||||
|
// settings without requring new CLI versions, so instead of just
|
||||||
|
// serializing the new config, we merge it with the old one.
|
||||||
|
let new_merged = crate::utils::merge_yaml_values(
|
||||||
|
&app_config.to_yaml_value()?,
|
||||||
|
&new_app_config.to_yaml_value()?,
|
||||||
|
);
|
||||||
|
let new_config_raw = serde_yaml::to_string(&new_merged)?;
|
||||||
|
std::fs::write(&app_config_path, new_config_raw).with_context(|| {
|
||||||
|
format!("Could not write file: '{}'", app_config_path.display())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.fmt.format == crate::utils::render::ItemFormat::Json {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&app_version)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeployAppOpts<'a> {
|
||||||
|
pub app: &'a AppConfigV1,
|
||||||
|
// Original raw yaml config.
|
||||||
|
// Present here to enable forwarding unknown fields to the backend, which
|
||||||
|
// preserves forwards-compatibility for schema changes.
|
||||||
|
pub original_config: Option<serde_yaml::value::Value>,
|
||||||
|
pub allow_create: bool,
|
||||||
|
pub make_default: bool,
|
||||||
|
pub owner: Option<String>,
|
||||||
|
pub wait: WaitMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn deploy_app(
|
||||||
|
client: &WasmerClient,
|
||||||
|
opts: DeployAppOpts<'_>,
|
||||||
|
) -> Result<DeployAppVersion, anyhow::Error> {
|
||||||
|
let app = opts.app;
|
||||||
|
|
||||||
|
let config_value = app.clone().to_yaml_value()?;
|
||||||
|
let final_config = if let Some(old) = &opts.original_config {
|
||||||
|
crate::utils::merge_yaml_values(old, &config_value)
|
||||||
|
} else {
|
||||||
|
config_value
|
||||||
|
};
|
||||||
|
let mut raw_config = serde_yaml::to_string(&final_config)?.trim().to_string();
|
||||||
|
raw_config.push('\n');
|
||||||
|
|
||||||
|
// TODO: respect allow_create flag
|
||||||
|
|
||||||
|
let version = wasmer_api::query::publish_deploy_app(
|
||||||
|
client,
|
||||||
|
dbg!(wasmer_api::types::PublishDeployAppVars {
|
||||||
|
config: raw_config,
|
||||||
|
name: app.name.clone().into(),
|
||||||
|
owner: opts.owner.map(|o| o.into()),
|
||||||
|
make_default: Some(opts.make_default),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("could not create app in the backend")?;
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum WaitMode {
|
||||||
|
/// Wait for the app to be deployed.
|
||||||
|
Deployed,
|
||||||
|
/// Wait for the app to be deployed and ready.
|
||||||
|
Reachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [Self::deploy], but also prints verbose information.
|
||||||
|
pub async fn deploy_app_verbose(
|
||||||
|
client: &WasmerClient,
|
||||||
|
opts: DeployAppOpts<'_>,
|
||||||
|
) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
|
||||||
|
let owner = &opts.owner.clone().or_else(|| opts.app.owner.clone());
|
||||||
|
let app = &opts.app;
|
||||||
|
|
||||||
|
let pretty_name = if let Some(owner) = &owner {
|
||||||
|
format!("{}/{}", owner, app.name)
|
||||||
|
} else {
|
||||||
|
app.name.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let make_default = opts.make_default;
|
||||||
|
|
||||||
|
eprintln!("Deploying app {pretty_name}...\n");
|
||||||
|
|
||||||
|
let wait = opts.wait;
|
||||||
|
let version = deploy_app(client, opts).await?;
|
||||||
|
|
||||||
|
let app_id = version
|
||||||
|
.app
|
||||||
|
.as_ref()
|
||||||
|
.context("app field on app version is empty")?
|
||||||
|
.id
|
||||||
|
.inner()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let app = wasmer_api::query::get_app_by_id(client, app_id.clone())
|
||||||
|
.await
|
||||||
|
.context("could not fetch app from backend")?;
|
||||||
|
|
||||||
|
let full_name = format!("{}/{}", app.owner.global_name, app.name);
|
||||||
|
|
||||||
|
eprintln!(" ✅ App {full_name} was successfully deployed!");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("> App URL: {}", app.url);
|
||||||
|
eprintln!("> Versioned URL: {}", version.url);
|
||||||
|
eprintln!("> Admin dashboard: {}", app.admin_url);
|
||||||
|
|
||||||
|
match wait {
|
||||||
|
WaitMode::Deployed => {}
|
||||||
|
WaitMode::Reachable => {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Waiting for new deployment to become available...");
|
||||||
|
eprintln!("(You can safely stop waiting now with CTRL-C)");
|
||||||
|
|
||||||
|
let stderr = std::io::stderr();
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
let start = tokio::time::Instant::now();
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let check_url = if make_default { &app.url } else { &version.url };
|
||||||
|
|
||||||
|
let mut sleep_millis: u64 = 1_000;
|
||||||
|
loop {
|
||||||
|
let total_elapsed = start.elapsed();
|
||||||
|
if total_elapsed > Duration::from_secs(60 * 5) {
|
||||||
|
eprintln!();
|
||||||
|
anyhow::bail!("\nApp still not reachable after 5 minutes...");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut lock = stderr.lock();
|
||||||
|
write!(&mut lock, ".").unwrap();
|
||||||
|
lock.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_start = tokio::time::Instant::now();
|
||||||
|
|
||||||
|
match client.get(check_url).send().await {
|
||||||
|
Ok(res) => {
|
||||||
|
let header = res
|
||||||
|
.headers()
|
||||||
|
.get(edge_util::headers::HEADER_APP_VERSION_ID)
|
||||||
|
.and_then(|x| x.to_str().ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if header == version.id.inner() {
|
||||||
|
eprintln!("\nNew version is now reachable at {check_url}");
|
||||||
|
eprintln!("Deployment complete");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
current=%header,
|
||||||
|
expected=%app.active_version.id.inner(),
|
||||||
|
"app is not at the right version yet",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!(?err, "health check request failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let elapsed: u64 = request_start
|
||||||
|
.elapsed()
|
||||||
|
.as_millis()
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let to_sleep = Duration::from_millis(sleep_millis.saturating_sub(elapsed));
|
||||||
|
tokio::time::sleep(to_sleep).await;
|
||||||
|
sleep_millis = (sleep_millis * 2).max(10_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((app, version))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app_config_from_api(version: &DeployAppVersion) -> Result<AppConfigV1, anyhow::Error> {
|
||||||
|
let app_id = version
|
||||||
|
.app
|
||||||
|
.as_ref()
|
||||||
|
.context("app field on app version is empty")?
|
||||||
|
.id
|
||||||
|
.inner()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let cfg = &version.user_yaml_config;
|
||||||
|
let mut cfg = AppConfigV1::parse_yaml(cfg)
|
||||||
|
.context("could not parse app config from backend app version")?;
|
||||||
|
|
||||||
|
cfg.app_id = Some(app_id);
|
||||||
|
Ok(cfg)
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
//! Edge app commands.
|
//! Edge app commands.
|
||||||
|
|
||||||
|
#![allow(unused, dead_code)]
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
pub mod deploy;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
@@ -10,16 +12,8 @@ pub mod version;
|
|||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use std::{io::Write, time::Duration};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
|
||||||
use edge_schema::schema::AppConfigV1;
|
|
||||||
use wasmer_api::{
|
|
||||||
types::{DeployApp, DeployAppVersion},
|
|
||||||
WasmerClient,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::commands::AsyncCliCommand;
|
use crate::commands::AsyncCliCommand;
|
||||||
|
use edge_schema::schema::AppConfigV1;
|
||||||
|
|
||||||
/// Manage Wasmer Deploy apps.
|
/// Manage Wasmer Deploy apps.
|
||||||
#[derive(clap::Subcommand, Debug)]
|
#[derive(clap::Subcommand, Debug)]
|
||||||
@@ -32,13 +26,14 @@ pub enum CmdApp {
|
|||||||
Delete(delete::CmdAppDelete),
|
Delete(delete::CmdAppDelete),
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
Version(version::CmdAppVersion),
|
Version(version::CmdAppVersion),
|
||||||
|
Deploy(deploy::CmdAppDeploy),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AsyncCliCommand for CmdApp {
|
impl AsyncCliCommand for CmdApp {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
async fn run_async(self) -> Result<(), anyhow::Error> {
|
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::Get(cmd) => {
|
Self::Get(cmd) => {
|
||||||
cmd.run_async().await?;
|
cmd.run_async().await?;
|
||||||
@@ -56,188 +51,7 @@ impl AsyncCliCommand for CmdApp {
|
|||||||
Self::Logs(cmd) => cmd.run_async().await,
|
Self::Logs(cmd) => cmd.run_async().await,
|
||||||
Self::Delete(cmd) => cmd.run_async().await,
|
Self::Delete(cmd) => cmd.run_async().await,
|
||||||
Self::Version(cmd) => cmd.run_async().await,
|
Self::Version(cmd) => cmd.run_async().await,
|
||||||
|
Self::Deploy(cmd) => cmd.run_async().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DeployAppOpts<'a> {
|
|
||||||
pub app: &'a AppConfigV1,
|
|
||||||
// Original raw yaml config.
|
|
||||||
// Present here to enable forwarding unknown fields to the backend, which
|
|
||||||
// preserves forwards-compatibility for schema changes.
|
|
||||||
pub original_config: Option<serde_yaml::Value>,
|
|
||||||
pub allow_create: bool,
|
|
||||||
pub make_default: bool,
|
|
||||||
pub owner: Option<String>,
|
|
||||||
pub wait: WaitMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn deploy_app(
|
|
||||||
client: &WasmerClient,
|
|
||||||
opts: DeployAppOpts<'_>,
|
|
||||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
let app = opts.app;
|
|
||||||
|
|
||||||
let config_value = app.clone().to_yaml_value()?;
|
|
||||||
let final_config = if let Some(old) = &opts.original_config {
|
|
||||||
crate::utils::merge_yaml_values(old, &config_value)
|
|
||||||
} else {
|
|
||||||
config_value
|
|
||||||
};
|
|
||||||
let mut raw_config = serde_yaml::to_string(&final_config)?.trim().to_string();
|
|
||||||
raw_config.push('\n');
|
|
||||||
|
|
||||||
// TODO: respect allow_create flag
|
|
||||||
|
|
||||||
let version = wasmer_api::query::publish_deploy_app(
|
|
||||||
client,
|
|
||||||
wasmer_api::types::PublishDeployAppVars {
|
|
||||||
config: raw_config,
|
|
||||||
name: app.name.clone().into(),
|
|
||||||
owner: opts.owner.map(|o| o.into()),
|
|
||||||
make_default: Some(opts.make_default),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("could not create app in the backend")?;
|
|
||||||
|
|
||||||
Ok(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub enum WaitMode {
|
|
||||||
/// Wait for the app to be deployed.
|
|
||||||
Deployed,
|
|
||||||
/// Wait for the app to be deployed and ready.
|
|
||||||
Reachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as [Self::deploy], but also prints verbose information.
|
|
||||||
pub async fn deploy_app_verbose(
|
|
||||||
client: &WasmerClient,
|
|
||||||
opts: DeployAppOpts<'_>,
|
|
||||||
) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
|
|
||||||
let owner = &opts.owner.clone().or_else(|| opts.app.owner.clone());
|
|
||||||
let app = &opts.app;
|
|
||||||
|
|
||||||
let pretty_name = if let Some(owner) = &owner {
|
|
||||||
format!("{}/{}", owner, app.name)
|
|
||||||
} else {
|
|
||||||
app.name.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let make_default = opts.make_default;
|
|
||||||
|
|
||||||
eprintln!("Deploying app {pretty_name}...\n");
|
|
||||||
|
|
||||||
let wait = opts.wait;
|
|
||||||
let version = deploy_app(client, opts).await?;
|
|
||||||
|
|
||||||
let app_id = version
|
|
||||||
.app
|
|
||||||
.as_ref()
|
|
||||||
.context("app field on app version is empty")?
|
|
||||||
.id
|
|
||||||
.inner()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let app = wasmer_api::query::get_app_by_id(client, app_id.clone())
|
|
||||||
.await
|
|
||||||
.context("could not fetch app from backend")?;
|
|
||||||
|
|
||||||
let full_name = format!("{}/{}", app.owner.global_name, app.name);
|
|
||||||
|
|
||||||
eprintln!(" ✅ App {full_name} was successfully deployed!");
|
|
||||||
eprintln!();
|
|
||||||
eprintln!("> App URL: {}", app.url);
|
|
||||||
eprintln!("> Versioned URL: {}", version.url);
|
|
||||||
eprintln!("> Admin dashboard: {}", app.admin_url);
|
|
||||||
|
|
||||||
match wait {
|
|
||||||
WaitMode::Deployed => {}
|
|
||||||
WaitMode::Reachable => {
|
|
||||||
eprintln!();
|
|
||||||
eprintln!("Waiting for new deployment to become available...");
|
|
||||||
eprintln!("(You can safely stop waiting now with CTRL-C)");
|
|
||||||
|
|
||||||
let stderr = std::io::stderr();
|
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
let start = tokio::time::Instant::now();
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
|
|
||||||
let check_url = if make_default { &app.url } else { &version.url };
|
|
||||||
|
|
||||||
let mut sleep_millis: u64 = 1_000;
|
|
||||||
loop {
|
|
||||||
let total_elapsed = start.elapsed();
|
|
||||||
if total_elapsed > Duration::from_secs(60 * 5) {
|
|
||||||
eprintln!();
|
|
||||||
bail!("\nApp still not reachable after 5 minutes...");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut lock = stderr.lock();
|
|
||||||
write!(&mut lock, ".").unwrap();
|
|
||||||
lock.flush().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let request_start = tokio::time::Instant::now();
|
|
||||||
|
|
||||||
match client.get(check_url).send().await {
|
|
||||||
Ok(res) => {
|
|
||||||
let header = res
|
|
||||||
.headers()
|
|
||||||
.get(edge_util::headers::HEADER_APP_VERSION_ID)
|
|
||||||
.and_then(|x| x.to_str().ok())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if header == version.id.inner() {
|
|
||||||
eprintln!("\nNew version is now reachable at {check_url}");
|
|
||||||
eprintln!("Deployment complete");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
current=%header,
|
|
||||||
expected=%app.active_version.id.inner(),
|
|
||||||
"app is not at the right version yet",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::debug!(?err, "health check request failed");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let elapsed: u64 = request_start
|
|
||||||
.elapsed()
|
|
||||||
.as_millis()
|
|
||||||
.try_into()
|
|
||||||
.unwrap_or_default();
|
|
||||||
let to_sleep = Duration::from_millis(sleep_millis.saturating_sub(elapsed));
|
|
||||||
tokio::time::sleep(to_sleep).await;
|
|
||||||
sleep_millis = (sleep_millis * 2).max(10_000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((app, version))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn app_config_from_api(version: &DeployAppVersion) -> Result<AppConfigV1, anyhow::Error> {
|
|
||||||
let app_id = version
|
|
||||||
.app
|
|
||||||
.as_ref()
|
|
||||||
.context("app field on app version is empty")?
|
|
||||||
.id
|
|
||||||
.inner()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let cfg = &version.user_yaml_config;
|
|
||||||
let mut cfg = AppConfigV1::parse_yaml(cfg)
|
|
||||||
.context("could not parse app config from backend app version")?;
|
|
||||||
|
|
||||||
cfg.app_id = Some(app_id);
|
|
||||||
Ok(cfg)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
use crate::commands::{
|
|
||||||
app::{DeployAppOpts, WaitMode},
|
|
||||||
deploy::{CmdDeploy, DeployAppVersion},
|
|
||||||
};
|
|
||||||
use edge_schema::schema::{AppConfigV1, PackageSpecifier};
|
|
||||||
use std::{path::PathBuf, str::FromStr};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// Deploy an unnamed package from its manifest's path.
|
|
||||||
pub struct DeployFromPackageManifestPath {
|
|
||||||
pub pkg_manifest_path: PathBuf,
|
|
||||||
pub config: AppConfigV1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeployFromPackageManifestPath {
|
|
||||||
pub async fn deploy(&self, cmd: &CmdDeploy) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
let client = cmd.api.client()?;
|
|
||||||
let owner = match &self.config.owner {
|
|
||||||
Some(owner) => Some(owner.clone()),
|
|
||||||
None => cmd.owner.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let manifest =
|
|
||||||
match crate::utils::load_package_manifest(&self.pkg_manifest_path)?.map(|x| x.1) {
|
|
||||||
Some(manifest) => manifest,
|
|
||||||
None => anyhow::bail!(
|
|
||||||
"The path '{}' doesn't point to a (valid) manifest",
|
|
||||||
self.pkg_manifest_path.display()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if manifest.package.is_some() {
|
|
||||||
anyhow::bail!("Cannot publish package as unnamed, as the manifest pointed to by '{}' contains a package field", self.pkg_manifest_path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("Publishing package...");
|
|
||||||
let (_, maybe_hash) = crate::utils::republish_package(
|
|
||||||
&client,
|
|
||||||
&self.pkg_manifest_path,
|
|
||||||
manifest.clone(),
|
|
||||||
owner.clone(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"Unnamed package from manifest '{}' published successfully!",
|
|
||||||
self.pkg_manifest_path.display()
|
|
||||||
);
|
|
||||||
eprintln!();
|
|
||||||
|
|
||||||
let wait_mode = if cmd.no_wait {
|
|
||||||
WaitMode::Deployed
|
|
||||||
} else {
|
|
||||||
WaitMode::Reachable
|
|
||||||
};
|
|
||||||
|
|
||||||
match maybe_hash {
|
|
||||||
Some(hash) => {
|
|
||||||
let package_spec = PackageSpecifier::from_str(&format!("sha256:{}", hash))?;
|
|
||||||
let new_config = AppConfigV1 {
|
|
||||||
package: package_spec,
|
|
||||||
..self.config.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts = DeployAppOpts {
|
|
||||||
app: &new_config,
|
|
||||||
original_config: Some(self.config.clone().to_yaml_value().unwrap()),
|
|
||||||
allow_create: true,
|
|
||||||
make_default: !cmd.no_default,
|
|
||||||
owner,
|
|
||||||
wait: wait_mode,
|
|
||||||
};
|
|
||||||
let (_app, app_version) =
|
|
||||||
crate::commands::app::deploy_app_verbose(&client, opts).await?;
|
|
||||||
|
|
||||||
if cmd.fmt.format == crate::utils::render::ItemFormat::Json {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&app_version)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(app_version)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
anyhow::bail!("Backend did not return a hash for the published unnamed package")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
use self::{
|
|
||||||
manifest_path::DeployFromPackageManifestPath, sha256::DeployFromSha256Hash,
|
|
||||||
webc::DeployFromWebc,
|
|
||||||
};
|
|
||||||
use super::CmdDeploy;
|
|
||||||
use edge_schema::schema::{AppConfigV1, PackageHash, PackageSpecifier};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use wasmer_api::types::DeployAppVersion;
|
|
||||||
|
|
||||||
pub(super) mod manifest_path;
|
|
||||||
pub(super) mod sha256;
|
|
||||||
pub(super) mod webc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DeployApp {
|
|
||||||
Path(DeployFromPackageManifestPath),
|
|
||||||
Ident(DeployFromWebc),
|
|
||||||
Sha256Hash(DeployFromSha256Hash),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AppConfigV1> for DeployApp {
|
|
||||||
fn from(config: AppConfigV1) -> Self {
|
|
||||||
match &config.package {
|
|
||||||
PackageSpecifier::Ident(webc_id) => DeployApp::Ident(DeployFromWebc {
|
|
||||||
webc_id: webc_id.clone(),
|
|
||||||
config,
|
|
||||||
}),
|
|
||||||
PackageSpecifier::Path(pkg_manifest_path) => {
|
|
||||||
DeployApp::Path(DeployFromPackageManifestPath {
|
|
||||||
pkg_manifest_path: PathBuf::from(pkg_manifest_path),
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
PackageSpecifier::Hash(PackageHash(hash)) => {
|
|
||||||
DeployApp::Sha256Hash(DeployFromSha256Hash {
|
|
||||||
hash: hash.clone(),
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeployApp {
|
|
||||||
pub(super) async fn deploy(
|
|
||||||
self,
|
|
||||||
app_config_path: PathBuf,
|
|
||||||
cmd: &CmdDeploy,
|
|
||||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
match self {
|
|
||||||
DeployApp::Path(p) => p.deploy(cmd).await,
|
|
||||||
DeployApp::Ident(i) => i.deploy(app_config_path, cmd).await,
|
|
||||||
DeployApp::Sha256Hash(s) => s.deploy(app_config_path, cmd).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
use crate::commands::deploy::CmdDeploy;
|
|
||||||
use edge_schema::schema::{AppConfigV1, Sha256Hash};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use wasmer_api::types::DeployAppVersion;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeployFromSha256Hash {
|
|
||||||
pub hash: Sha256Hash,
|
|
||||||
pub config: AppConfigV1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeployFromSha256Hash {
|
|
||||||
pub async fn deploy(
|
|
||||||
&self,
|
|
||||||
_app_config_path: PathBuf,
|
|
||||||
_cmd: &CmdDeploy,
|
|
||||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
use crate::commands::{
|
|
||||||
app::{DeployAppOpts, WaitMode},
|
|
||||||
deploy::CmdDeploy,
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
|
||||||
use edge_schema::schema::{AppConfigV1, PackageIdentifier, PackageSpecifier};
|
|
||||||
use is_terminal::IsTerminal;
|
|
||||||
use std::{io::Write, path::PathBuf};
|
|
||||||
use url::Url;
|
|
||||||
use wasmer_api::types::DeployAppVersion;
|
|
||||||
|
|
||||||
/// Deploy a named package from its Webc identifier.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeployFromWebc {
|
|
||||||
pub webc_id: PackageIdentifier,
|
|
||||||
pub config: AppConfigV1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeployFromWebc {
|
|
||||||
pub async fn deploy(
|
|
||||||
&self,
|
|
||||||
app_config_path: PathBuf,
|
|
||||||
cmd: &CmdDeploy,
|
|
||||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
let webc_id = &self.webc_id;
|
|
||||||
let client = cmd.api.client()?;
|
|
||||||
let pkg_name = webc_id.to_string();
|
|
||||||
let interactive = std::io::stdin().is_terminal() && !cmd.non_interactive;
|
|
||||||
let dir_path = app_config_path.canonicalize()?.parent().unwrap().to_owned();
|
|
||||||
|
|
||||||
// Find and load the mandatory `wasmer.toml` file.
|
|
||||||
let local_manifest_path = dir_path.join(crate::utils::DEFAULT_PACKAGE_MANIFEST_FILE);
|
|
||||||
let local_manifest = crate::utils::load_package_manifest(&local_manifest_path)?
|
|
||||||
.map(|x| x.1)
|
|
||||||
// Ignore local package if it is not referenced by the app.
|
|
||||||
.filter(|m| {
|
|
||||||
if let Some(pkg) = &m.package {
|
|
||||||
pkg.name == format!("{}/{}", webc_id.namespace, webc_id.name)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let new_package_manifest = if let Some(manifest) = local_manifest {
|
|
||||||
let should_publish = if cmd.publish_package {
|
|
||||||
true
|
|
||||||
} else if interactive {
|
|
||||||
eprintln!();
|
|
||||||
dialoguer::Confirm::new()
|
|
||||||
.with_prompt(format!("Publish new version of package '{}'?", pkg_name))
|
|
||||||
.interact_opt()?
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_publish {
|
|
||||||
eprintln!("Publishing package...");
|
|
||||||
let (new_manifest, _maybe_hash) =
|
|
||||||
crate::utils::republish_package(&client, &local_manifest_path, manifest, None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
eprint!("Waiting for package to become available...");
|
|
||||||
std::io::stderr().flush().unwrap();
|
|
||||||
|
|
||||||
let start_wait = std::time::Instant::now();
|
|
||||||
loop {
|
|
||||||
if start_wait.elapsed().as_secs() > 300 {
|
|
||||||
anyhow::bail!("Timed out waiting for package to become available");
|
|
||||||
}
|
|
||||||
|
|
||||||
eprint!(".");
|
|
||||||
std::io::stderr().flush().unwrap();
|
|
||||||
|
|
||||||
let new_version_opt = wasmer_api::query::get_package_version(
|
|
||||||
&client,
|
|
||||||
new_manifest.package.as_ref().unwrap().name.clone(),
|
|
||||||
new_manifest.package.as_ref().unwrap().version.to_string(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match new_version_opt {
|
|
||||||
Ok(Some(new_version)) => {
|
|
||||||
if new_version.distribution.pirita_sha256_hash.is_some() {
|
|
||||||
eprintln!();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Error - could not query package info: package not found"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
anyhow::bail!("Error - could not query package info: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("Package '{pkg_name}' published successfully!",);
|
|
||||||
eprintln!();
|
|
||||||
Some(new_manifest)
|
|
||||||
} else {
|
|
||||||
if interactive {
|
|
||||||
eprintln!();
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = if let Some(manifest) = new_package_manifest {
|
|
||||||
let package = manifest.package.unwrap();
|
|
||||||
let mut package_splits = package.name.split("/");
|
|
||||||
let package_namespace = package_splits.next().unwrap();
|
|
||||||
let package_name = package_splits.next().unwrap();
|
|
||||||
let package_spec = PackageSpecifier::Ident(PackageIdentifier {
|
|
||||||
repository: package.repository.map(|s| Url::parse(&s).unwrap()),
|
|
||||||
namespace: package_namespace.to_string(),
|
|
||||||
name: package_name.to_string(),
|
|
||||||
tag: Some(package.version.to_string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
AppConfigV1 {
|
|
||||||
package: package_spec,
|
|
||||||
..self.config.clone()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.config.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let wait_mode = if cmd.no_wait {
|
|
||||||
WaitMode::Deployed
|
|
||||||
} else {
|
|
||||||
WaitMode::Reachable
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts = DeployAppOpts {
|
|
||||||
app: &config,
|
|
||||||
original_config: Some(config.clone().to_yaml_value().unwrap()),
|
|
||||||
allow_create: true,
|
|
||||||
make_default: !cmd.no_default,
|
|
||||||
owner: match &config.owner {
|
|
||||||
Some(owner) => Some(owner.clone()),
|
|
||||||
None => cmd.owner.clone(),
|
|
||||||
},
|
|
||||||
wait: wait_mode,
|
|
||||||
};
|
|
||||||
let (_app, app_version) = crate::commands::app::deploy_app_verbose(&client, opts).await?;
|
|
||||||
|
|
||||||
let mut new_config = crate::commands::app::app_config_from_api(&app_version)?;
|
|
||||||
if cmd.no_persist_id {
|
|
||||||
new_config.app_id = None;
|
|
||||||
}
|
|
||||||
// If the config changed, write it back.
|
|
||||||
if new_config != config {
|
|
||||||
// We want to preserve unknown fields to allow for newer app.yaml
|
|
||||||
// settings without requring new CLI versions, so instead of just
|
|
||||||
// serializing the new config, we merge it with the old one.
|
|
||||||
let new_merged = crate::utils::merge_yaml_values(
|
|
||||||
&config.to_yaml_value()?,
|
|
||||||
&new_config.to_yaml_value()?,
|
|
||||||
);
|
|
||||||
let new_config_raw = serde_yaml::to_string(&new_merged)?;
|
|
||||||
std::fs::write(&app_config_path, new_config_raw).with_context(|| {
|
|
||||||
format!("Could not write file: '{}'", app_config_path.display())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.fmt.format == crate::utils::render::ItemFormat::Json {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&app_version)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(app_version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
use super::AsyncCliCommand;
|
|
||||||
use crate::{
|
|
||||||
commands::deploy::deploy::DeployApp,
|
|
||||||
opts::{ApiOpts, ItemFormatOpts},
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
|
||||||
use edge_schema::schema::AppConfigV1;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use wasmer_api::types::DeployAppVersion;
|
|
||||||
|
|
||||||
// [todo]: deploy inside deploy? Let's think of a better name.
|
|
||||||
mod deploy;
|
|
||||||
|
|
||||||
/// Deploy an app to Wasmer Edge.
|
|
||||||
#[derive(clap::Parser, Debug)]
|
|
||||||
pub struct CmdDeploy {
|
|
||||||
#[clap(flatten)]
|
|
||||||
pub api: ApiOpts,
|
|
||||||
|
|
||||||
#[clap(flatten)]
|
|
||||||
pub fmt: ItemFormatOpts,
|
|
||||||
|
|
||||||
/// Skip local schema validation.
|
|
||||||
#[clap(long)]
|
|
||||||
pub no_validate: bool,
|
|
||||||
|
|
||||||
/// Do not prompt for user input.
|
|
||||||
#[clap(long)]
|
|
||||||
pub non_interactive: 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,
|
|
||||||
|
|
||||||
/// The path to the app.yaml file.
|
|
||||||
#[clap(long)]
|
|
||||||
pub path: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Do not wait for the app to become reachable.
|
|
||||||
#[clap(long)]
|
|
||||||
pub no_wait: bool,
|
|
||||||
|
|
||||||
/// Do not make the new app version the default (active) version.
|
|
||||||
/// This is useful for testing a deployment first, before moving it to "production".
|
|
||||||
#[clap(long)]
|
|
||||||
pub no_default: bool,
|
|
||||||
|
|
||||||
/// Do not persist the app version ID in the app.yaml.
|
|
||||||
#[clap(long)]
|
|
||||||
pub no_persist_id: bool,
|
|
||||||
|
|
||||||
/// Specify the owner (user or namespace) of the app.
|
|
||||||
/// Will default to the currently logged in user, or the existing one
|
|
||||||
/// if the app can be found.
|
|
||||||
#[clap(long)]
|
|
||||||
pub owner: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl AsyncCliCommand for CmdDeploy {
|
|
||||||
type Output = DeployAppVersion;
|
|
||||||
|
|
||||||
async fn run_async(self) -> Result<DeployAppVersion, anyhow::Error> {
|
|
||||||
let app_path = {
|
|
||||||
let base_path = self.path.clone().unwrap_or(std::env::current_dir()?);
|
|
||||||
if base_path.is_file() {
|
|
||||||
base_path
|
|
||||||
} else if base_path.is_dir() {
|
|
||||||
let f = base_path.join(AppConfigV1::CANONICAL_FILE_NAME);
|
|
||||||
if !f.is_file() {
|
|
||||||
anyhow::bail!("Could not find app.yaml at path '{}'", f.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
f
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("No such file or directory '{}'", base_path.display());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_str = std::fs::read_to_string(&app_path)
|
|
||||||
.with_context(|| format!("Could not read file '{}'", app_path.display()))?;
|
|
||||||
|
|
||||||
let config: AppConfigV1 = AppConfigV1::parse_yaml(&config_str)?;
|
|
||||||
eprintln!("Loaded app from: '{}'", app_path.display());
|
|
||||||
|
|
||||||
Into::<DeployApp>::into(config)
|
|
||||||
.deploy(app_path, &self)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ mod container;
|
|||||||
mod create_exe;
|
mod create_exe;
|
||||||
#[cfg(feature = "static-artifact-create")]
|
#[cfg(feature = "static-artifact-create")]
|
||||||
mod create_obj;
|
mod create_obj;
|
||||||
pub(crate) mod deploy;
|
|
||||||
pub(crate) mod domain;
|
pub(crate) mod domain;
|
||||||
#[cfg(feature = "static-artifact-create")]
|
#[cfg(feature = "static-artifact-create")]
|
||||||
mod gen_c_header;
|
mod gen_c_header;
|
||||||
@@ -131,10 +130,10 @@ impl WasmerCmd {
|
|||||||
Some(Cmd::Inspect(inspect)) => inspect.execute(),
|
Some(Cmd::Inspect(inspect)) => inspect.execute(),
|
||||||
Some(Cmd::Init(init)) => init.execute(),
|
Some(Cmd::Init(init)) => init.execute(),
|
||||||
Some(Cmd::Login(login)) => login.execute(),
|
Some(Cmd::Login(login)) => login.execute(),
|
||||||
Some(Cmd::Publish(publish)) => publish.execute(),
|
Some(Cmd::Publish(publish)) => publish.run().map(|_| ()),
|
||||||
Some(Cmd::Package(cmd)) => match cmd {
|
Some(Cmd::Package(cmd)) => match cmd {
|
||||||
Package::Download(cmd) => cmd.execute(),
|
Package::Download(cmd) => cmd.execute(),
|
||||||
Package::Build(cmd) => cmd.execute(),
|
Package::Build(cmd) => cmd.execute().map(|_| ()),
|
||||||
},
|
},
|
||||||
Some(Cmd::Container(cmd)) => match cmd {
|
Some(Cmd::Container(cmd)) => match cmd {
|
||||||
crate::commands::Container::Unpack(cmd) => cmd.execute(),
|
crate::commands::Container::Unpack(cmd) => cmd.execute(),
|
||||||
@@ -345,8 +344,8 @@ enum Cmd {
|
|||||||
Container(crate::commands::Container),
|
Container(crate::commands::Container),
|
||||||
|
|
||||||
// Edge commands
|
// Edge commands
|
||||||
/// Deploy apps to Wasmer Edge.
|
/// Deploy apps to Wasmer Edge. [alias: app deploy]
|
||||||
Deploy(crate::commands::deploy::CmdDeploy),
|
Deploy(crate::commands::app::deploy::CmdAppDeploy),
|
||||||
|
|
||||||
/// Manage deployed Edge apps.
|
/// Manage deployed Edge apps.
|
||||||
#[clap(subcommand, alias = "apps")]
|
#[clap(subcommand, alias = "apps")]
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ use std::path::PathBuf;
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dialoguer::console::{style, Emoji};
|
use dialoguer::console::{style, Emoji};
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
|
use wasmer_config::package::PackageHash;
|
||||||
|
|
||||||
|
use crate::utils::load_package_manifest;
|
||||||
|
|
||||||
/// Build a container from a package manifest.
|
/// Build a container from a package manifest.
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
@@ -41,9 +44,21 @@ impl PackageBuild {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn execute(&self) -> Result<(), anyhow::Error> {
|
pub(crate) fn execute(&self) -> Result<PackageHash, anyhow::Error> {
|
||||||
let manifest_path = self.manifest_path()?;
|
let manifest_path = self.manifest_path()?;
|
||||||
|
let Some((_, manifest)) = load_package_manifest(&manifest_path)? else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Could not locate manifest in path '{}'",
|
||||||
|
manifest_path.display()
|
||||||
|
)
|
||||||
|
};
|
||||||
let pkg = webc::wasmer_package::Package::from_manifest(manifest_path)?;
|
let pkg = webc::wasmer_package::Package::from_manifest(manifest_path)?;
|
||||||
|
let pkg_hash = PackageHash::from_sha256_bytes(pkg.webc_hash());
|
||||||
|
let name = if let Some(manifest_pkg) = manifest.package {
|
||||||
|
format!("{}-{}.webc", manifest_pkg.name, manifest_pkg.version)
|
||||||
|
} else {
|
||||||
|
format!("{}.webc", pkg_hash)
|
||||||
|
};
|
||||||
|
|
||||||
// Setup the progress bar
|
// Setup the progress bar
|
||||||
let pb = if self.quiet {
|
let pb = if self.quiet {
|
||||||
@@ -58,20 +73,13 @@ impl PackageBuild {
|
|||||||
READING_MANIFEST_EMOJI
|
READING_MANIFEST_EMOJI
|
||||||
));
|
));
|
||||||
|
|
||||||
let manifest = pkg
|
|
||||||
.manifest()
|
|
||||||
.wapm()
|
|
||||||
.context("could not load package manifest")?
|
|
||||||
.context("package does not contain a Wasmer manifest")?;
|
|
||||||
|
|
||||||
// rest of the code writes the package to disk and is irrelevant
|
// rest of the code writes the package to disk and is irrelevant
|
||||||
// to checking.
|
// to checking.
|
||||||
if self.check {
|
if self.check {
|
||||||
return Ok(());
|
return Ok(pkg_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pkgname = manifest.name.unwrap().replace('/', "-");
|
let manifest = pkg.manifest();
|
||||||
let name = format!("{}-{}.webc", pkgname, manifest.version.unwrap(),);
|
|
||||||
|
|
||||||
pb.println(format!(
|
pb.println(format!(
|
||||||
"{} {}Creating output directory...",
|
"{} {}Creating output directory...",
|
||||||
@@ -119,7 +127,7 @@ impl PackageBuild {
|
|||||||
out_path.display()
|
out_path.display()
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(pkg_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manifest_path(&self) -> Result<PathBuf, anyhow::Error> {
|
fn manifest_path(&self) -> Result<PathBuf, anyhow::Error> {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use anyhow::{bail, Context};
|
|||||||
use dialoguer::console::{style, Emoji};
|
use dialoguer::console::{style, Emoji};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
use wasmer_config::package::{PackageIdent, PackageSource};
|
||||||
use wasmer_registry::wasmer_env::WasmerEnv;
|
use wasmer_registry::wasmer_env::WasmerEnv;
|
||||||
use wasmer_wasix::runtime::resolver::PackageSpecifier;
|
|
||||||
|
|
||||||
/// Download a package from the registry.
|
/// Download a package from the registry.
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
@@ -27,10 +27,7 @@ pub struct PackageDownload {
|
|||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
|
|
||||||
/// The package to download.
|
/// The package to download.
|
||||||
/// Can be:
|
package: PackageSource,
|
||||||
/// * a pakage specifier: `namespace/package[@vesion]`
|
|
||||||
/// * a URL
|
|
||||||
package: PackageSpecifier,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static CREATING_OUTPUT_DIRECTORY_EMOJI: Emoji<'_, '_> = Emoji("📁 ", "");
|
static CREATING_OUTPUT_DIRECTORY_EMOJI: Emoji<'_, '_> = Emoji("📁 ", "");
|
||||||
@@ -94,10 +91,11 @@ impl PackageDownload {
|
|||||||
step_num += 1;
|
step_num += 1;
|
||||||
|
|
||||||
let (download_url, token) = match &self.package {
|
let (download_url, token) = match &self.package {
|
||||||
PackageSpecifier::Registry { full_name, version } => {
|
PackageSource::Ident(PackageIdent::Named(id)) => {
|
||||||
let endpoint = self.env.registry_endpoint()?;
|
let endpoint = self.env.registry_endpoint()?;
|
||||||
let version = version.to_string();
|
let version = id.version_or_default().to_string();
|
||||||
let version = if version == "*" { None } else { Some(version) };
|
let version = if version == "*" { None } else { Some(version) };
|
||||||
|
let full_name = id.full_name();
|
||||||
let token = self.env.get_token_opt().map(|x| x.to_string());
|
let token = self.env.get_token_opt().map(|x| x.to_string());
|
||||||
|
|
||||||
let package = wasmer_registry::query_package_from_registry(
|
let package = wasmer_registry::query_package_from_registry(
|
||||||
@@ -119,7 +117,7 @@ impl PackageDownload {
|
|||||||
|
|
||||||
(download_url, token)
|
(download_url, token)
|
||||||
}
|
}
|
||||||
PackageSpecifier::HashSha256(hash) => {
|
PackageSource::Ident(PackageIdent::Hash(hash)) => {
|
||||||
let endpoint = self.env.registry_endpoint()?;
|
let endpoint = self.env.registry_endpoint()?;
|
||||||
let token = self.env.get_token_opt().map(|x| x.to_string());
|
let token = self.env.get_token_opt().map(|x| x.to_string());
|
||||||
|
|
||||||
@@ -131,17 +129,13 @@ impl PackageDownload {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let pkg = rt.block_on(wasmer_api::query::get_package_release(&client, &hash))?
|
let pkg = rt.block_on(wasmer_api::query::get_package_release(&client, &hash.to_string()))?
|
||||||
.with_context(|| format!("Package with sha256:{hash} does not exist in the registry, or is not accessible"))?;
|
.with_context(|| format!("Package with {hash} does not exist in the registry, or is not accessible"))?;
|
||||||
|
|
||||||
(pkg.webc_url, token)
|
(pkg.webc_url, token)
|
||||||
}
|
}
|
||||||
PackageSpecifier::Url(url) => {
|
PackageSource::Path(p) => bail!("cannot download a package from a local path: '{p}'"),
|
||||||
bail!("cannot download a package from a URL: '{}'", url);
|
PackageSource::Url(url) => bail!("cannot download a package from a URL: '{}'", url),
|
||||||
}
|
|
||||||
PackageSpecifier::Path(_) => {
|
|
||||||
bail!("cannot download a package from a local path");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use dialoguer::Confirm;
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
|
use wasmer_config::package::PackageIdent;
|
||||||
use wasmer_registry::{publish::PublishWait, wasmer_env::WasmerEnv};
|
use wasmer_registry::{publish::PublishWait, wasmer_env::WasmerEnv};
|
||||||
|
|
||||||
use super::PackageBuild;
|
use crate::{opts::ApiOpts, utils::load_package_manifest};
|
||||||
|
|
||||||
|
use super::{AsyncCliCommand, PackageBuild};
|
||||||
|
|
||||||
/// Publish a package to the package registry.
|
/// Publish a package to the package registry.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct Publish {
|
pub struct Publish {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
env: WasmerEnv,
|
pub env: WasmerEnv,
|
||||||
/// Run the publish logic without sending anything to the registry server
|
/// Run the publish logic without sending anything to the registry server
|
||||||
#[clap(long, name = "dry-run")]
|
#[clap(long, name = "dry-run")]
|
||||||
pub dry_run: bool,
|
pub dry_run: bool,
|
||||||
/// Run the publish command without any output
|
/// Run the publish command without any output
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
/// Override the package of the uploaded package in the wasmer.toml
|
/// Override the namespace of the package to upload
|
||||||
|
#[clap(long)]
|
||||||
|
pub package_namespace: Option<String>,
|
||||||
|
/// Override the name of the package to upload
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub package_name: Option<String>,
|
pub package_name: Option<String>,
|
||||||
/// Override the package version of the uploaded package in the wasmer.toml
|
/// Override the package version of the uploaded package in the wasmer.toml
|
||||||
@@ -44,17 +52,67 @@ pub struct Publish {
|
|||||||
/// for each individual query to the registry during the publish flow.
|
/// for each individual query to the registry during the publish flow.
|
||||||
#[clap(long, default_value = "2m")]
|
#[clap(long, default_value = "2m")]
|
||||||
pub timeout: humantime::Duration,
|
pub timeout: humantime::Duration,
|
||||||
|
|
||||||
|
/// Do not prompt for user input.
|
||||||
|
#[clap(long)]
|
||||||
|
pub non_interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Publish {
|
#[async_trait::async_trait]
|
||||||
/// Executes `wasmer publish`
|
impl AsyncCliCommand for Publish {
|
||||||
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
type Output = Option<PackageIdent>;
|
||||||
// first check if the package could be built successfuly
|
|
||||||
let package_path = match self.package_path.as_ref() {
|
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
|
||||||
|
let manifest_dir_path = match self.package_path.as_ref() {
|
||||||
Some(s) => std::env::current_dir()?.join(s),
|
Some(s) => std::env::current_dir()?.join(s),
|
||||||
None => std::env::current_dir()?,
|
None => std::env::current_dir()?,
|
||||||
};
|
};
|
||||||
PackageBuild::check(package_path).execute()?;
|
|
||||||
|
let (_, manifest) = match load_package_manifest(&manifest_dir_path)? {
|
||||||
|
Some(r) => r,
|
||||||
|
None => anyhow::bail!(
|
||||||
|
"Path '{}' does not contain a valid `wasmer.toml` manifest.",
|
||||||
|
manifest_dir_path.display()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let hash = PackageBuild::check(manifest_dir_path).execute()?;
|
||||||
|
|
||||||
|
let api = ApiOpts {
|
||||||
|
token: self.env.token().clone(),
|
||||||
|
registry: Some(self.env.registry_endpoint()?),
|
||||||
|
};
|
||||||
|
let client = api.client()?;
|
||||||
|
|
||||||
|
let maybe_already_published =
|
||||||
|
wasmer_api::query::get_package_release(&client, &hash.to_string())
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if maybe_already_published.is_some() {
|
||||||
|
eprintln!("Package with hash {hash} already present on registry");
|
||||||
|
return Ok(Some(PackageIdent::Hash(hash)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut version = self.version.clone();
|
||||||
|
|
||||||
|
if let Some(pkg) = manifest.package {
|
||||||
|
if std::io::stdin().is_terminal() && !self.non_interactive {
|
||||||
|
eprintln!("Current package version is {}.", pkg.version);
|
||||||
|
let mut next_version = pkg.version.clone();
|
||||||
|
next_version.patch += 1;
|
||||||
|
if Confirm::new()
|
||||||
|
.with_prompt(format!(
|
||||||
|
"Do you want to bump it to a new version ({} -> {})?",
|
||||||
|
pkg.version, next_version
|
||||||
|
))
|
||||||
|
.interact()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
version = Some(next_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let token = self
|
let token = self
|
||||||
.env
|
.env
|
||||||
@@ -80,9 +138,9 @@ impl Publish {
|
|||||||
package_path: self.package_path.clone(),
|
package_path: self.package_path.clone(),
|
||||||
wait,
|
wait,
|
||||||
timeout: self.timeout.into(),
|
timeout: self.timeout.into(),
|
||||||
package_namespace: None,
|
package_namespace: self.package_namespace,
|
||||||
};
|
};
|
||||||
publish.execute().map_err(on_error)?;
|
let res = publish.execute().map_err(on_error)?;
|
||||||
|
|
||||||
if let Err(e) = invalidate_graphql_query_cache(&self.env) {
|
if let Err(e) = invalidate_graphql_query_cache(&self.env) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -91,7 +149,7 @@ impl Publish {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use wasmer::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "compiler")]
|
#[cfg(feature = "compiler")]
|
||||||
use wasmer_compiler::ArtifactBuild;
|
use wasmer_compiler::ArtifactBuild;
|
||||||
|
use wasmer_config::package::PackageSource as PackageSpecifier;
|
||||||
use wasmer_registry::{wasmer_env::WasmerEnv, Package};
|
use wasmer_registry::{wasmer_env::WasmerEnv, Package};
|
||||||
#[cfg(feature = "journal")]
|
#[cfg(feature = "journal")]
|
||||||
use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
|
use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
|
||||||
@@ -44,7 +45,7 @@ use wasmer_wasix::{
|
|||||||
runtime::{
|
runtime::{
|
||||||
module_cache::{CacheError, ModuleHash},
|
module_cache::{CacheError, ModuleHash},
|
||||||
package_loader::PackageLoader,
|
package_loader::PackageLoader,
|
||||||
resolver::{PackageSpecifier, QueryError},
|
resolver::QueryError,
|
||||||
task_manager::VirtualTaskManagerExt,
|
task_manager::VirtualTaskManagerExt,
|
||||||
},
|
},
|
||||||
Runtime, WasiError,
|
Runtime, WasiError,
|
||||||
@@ -210,7 +211,7 @@ impl Run {
|
|||||||
let mut dependencies = Vec::new();
|
let mut dependencies = Vec::new();
|
||||||
|
|
||||||
for name in &self.wasi.uses {
|
for name in &self.wasi.uses {
|
||||||
let specifier = PackageSpecifier::parse(name)
|
let specifier = PackageSpecifier::from_str(name)
|
||||||
.with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
|
.with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
|
||||||
let pkg = {
|
let pkg = {
|
||||||
let specifier = specifier.clone();
|
let specifier = specifier.clone();
|
||||||
@@ -560,7 +561,7 @@ impl PackageSource {
|
|||||||
return Ok(PackageSource::Dir(path.to_path_buf()));
|
return Ok(PackageSource::Dir(path.to_path_buf()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(pkg) = PackageSpecifier::parse(s) {
|
if let Ok(pkg) = PackageSpecifier::from_str(s) {
|
||||||
return Ok(PackageSource::Package(pkg));
|
return Ok(PackageSource::Package(pkg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use tokio::runtime::Handle;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use virtual_fs::{DeviceFile, FileSystem, PassthruFileSystem, RootFileSystemBuilder};
|
use virtual_fs::{DeviceFile, FileSystem, PassthruFileSystem, RootFileSystemBuilder};
|
||||||
use wasmer::{Engine, Function, Instance, Memory32, Memory64, Module, RuntimeError, Store, Value};
|
use wasmer::{Engine, Function, Instance, Memory32, Memory64, Module, RuntimeError, Store, Value};
|
||||||
|
use wasmer_config::package::PackageSource as PackageSpecifier;
|
||||||
use wasmer_registry::wasmer_env::WasmerEnv;
|
use wasmer_registry::wasmer_env::WasmerEnv;
|
||||||
#[cfg(feature = "journal")]
|
#[cfg(feature = "journal")]
|
||||||
use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
|
use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
|
||||||
@@ -28,10 +29,7 @@ use wasmer_wasix::{
|
|||||||
runtime::{
|
runtime::{
|
||||||
module_cache::{FileSystemCache, ModuleCache, ModuleHash},
|
module_cache::{FileSystemCache, ModuleCache, ModuleHash},
|
||||||
package_loader::{BuiltinPackageLoader, PackageLoader},
|
package_loader::{BuiltinPackageLoader, PackageLoader},
|
||||||
resolver::{
|
resolver::{FileSystemSource, InMemorySource, MultiSource, Source, WapmSource, WebSource},
|
||||||
FileSystemSource, InMemorySource, MultiSource, PackageSpecifier, Source, WapmSource,
|
|
||||||
WebSource,
|
|
||||||
},
|
|
||||||
task_manager::{
|
task_manager::{
|
||||||
tokio::{RuntimeOrHandle, TokioTaskManager},
|
tokio::{RuntimeOrHandle, TokioTaskManager},
|
||||||
VirtualTaskManagerExt,
|
VirtualTaskManagerExt,
|
||||||
@@ -228,7 +226,7 @@ impl Wasi {
|
|||||||
|
|
||||||
let mut uses = Vec::new();
|
let mut uses = Vec::new();
|
||||||
for name in &self.uses {
|
for name in &self.uses {
|
||||||
let specifier = PackageSpecifier::parse(name)
|
let specifier = PackageSpecifier::from_str(name)
|
||||||
.with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
|
.with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
|
||||||
let pkg = {
|
let pkg = {
|
||||||
let inner_rt = rt.clone();
|
let inner_rt = rt.clone();
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
#![doc(html_logo_url = "https://github.com/wasmerio.png?size=200")]
|
#![doc(html_logo_url = "https://github.com/wasmerio.png?size=200")]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_docs,
|
missing_docs,
|
||||||
dead_code,
|
// dead_code,
|
||||||
nonstandard_style,
|
nonstandard_style,
|
||||||
unused_mut,
|
unused_mut,
|
||||||
unused_variables,
|
// unused_variables,
|
||||||
unused_unsafe,
|
unused_unsafe,
|
||||||
unreachable_patterns
|
unreachable_patterns
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use edge_schema::schema::PackageIdentifier;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use wasmer_api::WasmerClient;
|
use wasmer_api::WasmerClient;
|
||||||
use wasmer_config::package::Manifest;
|
use wasmer_config::package::NamedPackageIdent;
|
||||||
use wasmer_wasix::runners::MappedDirectory;
|
use wasmer_wasix::runners::MappedDirectory;
|
||||||
|
|
||||||
fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<MappedDirectory> {
|
fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<MappedDirectory> {
|
||||||
@@ -114,7 +113,7 @@ pub fn load_package_manifest(
|
|||||||
pub fn prompt_for_package_name(
|
pub fn prompt_for_package_name(
|
||||||
message: &str,
|
message: &str,
|
||||||
default: Option<&str>,
|
default: Option<&str>,
|
||||||
) -> Result<PackageIdentifier, anyhow::Error> {
|
) -> Result<NamedPackageIdent, anyhow::Error> {
|
||||||
loop {
|
loop {
|
||||||
let raw: String = dialoguer::Input::new()
|
let raw: String = dialoguer::Input::new()
|
||||||
.with_prompt(message)
|
.with_prompt(message)
|
||||||
@@ -122,7 +121,7 @@ pub fn prompt_for_package_name(
|
|||||||
.interact_text()
|
.interact_text()
|
||||||
.context("could not read user input")?;
|
.context("could not read user input")?;
|
||||||
|
|
||||||
match raw.parse::<PackageIdentifier>() {
|
match raw.parse::<NamedPackageIdent>() {
|
||||||
Ok(p) => break Ok(p),
|
Ok(p) => break Ok(p),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("invalid package name: {err}");
|
eprintln!("invalid package name: {err}");
|
||||||
@@ -150,7 +149,7 @@ pub async fn prompt_for_package(
|
|||||||
default: Option<&str>,
|
default: Option<&str>,
|
||||||
check: Option<PackageCheckMode>,
|
check: Option<PackageCheckMode>,
|
||||||
client: Option<&WasmerClient>,
|
client: Option<&WasmerClient>,
|
||||||
) -> Result<(PackageIdentifier, Option<wasmer_api::types::Package>), anyhow::Error> {
|
) -> Result<(NamedPackageIdent, Option<wasmer_api::types::Package>), anyhow::Error> {
|
||||||
loop {
|
loop {
|
||||||
let name = prompt_for_package_name(message, default)?;
|
let name = prompt_for_package_name(message, default)?;
|
||||||
|
|
||||||
@@ -181,122 +180,122 @@ pub async fn prompt_for_package(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Republish the package described by the [`wasmer_config::package::Manifest`] given as argument and return a
|
// /// Republish the package described by the [`wasmer_config::package::Manifest`] given as argument and return a
|
||||||
/// [`Result<wasmer_config::package::Manifest>`].
|
// /// [`Result<wasmer_config::package::Manifest>`].
|
||||||
///
|
// ///
|
||||||
/// If the package described is named (i.e. has name, namespace and version), the returned manifest
|
// /// If the package described is named (i.e. has name, namespace and version), the returned manifest
|
||||||
/// will have its minor version bumped. If the package is unnamed, the returned manifest will be
|
// /// will have its minor version bumped. If the package is unnamed, the returned manifest will be
|
||||||
/// equal to the one given as input.
|
// /// equal to the one given as input.
|
||||||
pub async fn republish_package(
|
// pub async fn republish_package(
|
||||||
client: &WasmerClient,
|
// client: &WasmerClient,
|
||||||
manifest_path: &Path,
|
// manifest_path: &Path,
|
||||||
manifest: wasmer_config::package::Manifest,
|
// manifest: wasmer_config::package::Manifest,
|
||||||
patch_owner: Option<String>,
|
// patch_owner: Option<String>,
|
||||||
) -> Result<(wasmer_config::package::Manifest, Option<String>), anyhow::Error> {
|
// ) -> Result<(wasmer_config::package::Manifest, Option<PackageIdent>), anyhow::Error> {
|
||||||
let manifest_path = if manifest_path.is_file() {
|
// let manifest_path = if manifest_path.is_file() {
|
||||||
manifest_path.to_owned()
|
// manifest_path.to_owned()
|
||||||
} else {
|
// } else {
|
||||||
manifest_path.join(DEFAULT_PACKAGE_MANIFEST_FILE)
|
// manifest_path.join(DEFAULT_PACKAGE_MANIFEST_FILE)
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
let dir = manifest_path
|
// let dir = manifest_path
|
||||||
.parent()
|
// .parent()
|
||||||
.context("could not determine wasmer.toml parent directory")?
|
// .context("could not determine wasmer.toml parent directory")?
|
||||||
.to_owned();
|
// .to_owned();
|
||||||
|
//
|
||||||
let new_manifest = match &manifest.package {
|
// let new_manifest = match &manifest.package {
|
||||||
None => manifest.clone(),
|
// None => manifest.clone(),
|
||||||
Some(pkg) => {
|
// Some(pkg) => {
|
||||||
let mut pkg = pkg.clone();
|
// let mut pkg = pkg.clone();
|
||||||
let name = pkg.name.clone();
|
// let name = pkg.name.clone();
|
||||||
|
//
|
||||||
let current_opt = wasmer_api::query::get_package(client, pkg.name.clone())
|
// let current_opt = wasmer_api::query::get_package(client, pkg.name.clone())
|
||||||
.await
|
// .await
|
||||||
.context("could not load package info from backend")?
|
// .context("could not load package info from backend")?
|
||||||
.and_then(|x| x.last_version);
|
// .and_then(|x| x.last_version);
|
||||||
|
//
|
||||||
let new_version = if let Some(current) = ¤t_opt {
|
// let new_version = if let Some(current) = ¤t_opt {
|
||||||
let mut v = semver::Version::parse(¤t.version).with_context(|| {
|
// let mut v = semver::Version::parse(¤t.version).with_context(|| {
|
||||||
format!("Could not parse package version: '{}'", current.version)
|
// format!("Could not parse package version: '{}'", current.version)
|
||||||
})?;
|
// })?;
|
||||||
|
//
|
||||||
v.patch += 1;
|
// v.patch += 1;
|
||||||
|
//
|
||||||
// The backend does not have a reliable way to return the latest version,
|
// // The backend does not have a reliable way to return the latest version,
|
||||||
// so we have to check each version in a loop.
|
// // so we have to check each version in a loop.
|
||||||
loop {
|
// loop {
|
||||||
let version = format!("={}", v);
|
// let version = format!("={}", v);
|
||||||
let version = wasmer_api::query::get_package_version(
|
// let version = wasmer_api::query::get_package_version(
|
||||||
client,
|
// client,
|
||||||
name.clone(),
|
// name.clone(),
|
||||||
version.clone(),
|
// version.clone(),
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.context("could not load package info from backend")?;
|
// .context("could not load package info from backend")?;
|
||||||
|
//
|
||||||
if version.is_some() {
|
// if version.is_some() {
|
||||||
v.patch += 1;
|
// v.patch += 1;
|
||||||
} else {
|
// } else {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
v
|
// v
|
||||||
} else {
|
// } else {
|
||||||
pkg.version
|
// pkg.version
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
pkg.version = new_version;
|
// pkg.version = new_version;
|
||||||
|
//
|
||||||
let mut manifest = manifest.clone();
|
// let mut manifest = manifest.clone();
|
||||||
manifest.package = Some(pkg);
|
// manifest.package = Some(pkg);
|
||||||
|
//
|
||||||
let contents = toml::to_string(&manifest).with_context(|| {
|
// let contents = toml::to_string(&manifest).with_context(|| {
|
||||||
format!(
|
// format!(
|
||||||
"could not persist manifest to '{}'",
|
// "could not persist manifest to '{}'",
|
||||||
manifest_path.display()
|
// manifest_path.display()
|
||||||
)
|
// )
|
||||||
})?;
|
// })?;
|
||||||
|
//
|
||||||
std::fs::write(manifest_path.clone(), contents).with_context(|| {
|
// std::fs::write(manifest_path.clone(), contents).with_context(|| {
|
||||||
format!("could not write manifest to '{}'", manifest_path.display())
|
// format!("could not write manifest to '{}'", manifest_path.display())
|
||||||
})?;
|
// })?;
|
||||||
|
//
|
||||||
manifest
|
// manifest
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
let registry = client.graphql_endpoint().to_string();
|
// let registry = client.graphql_endpoint().to_string();
|
||||||
let token = client
|
// let token = client
|
||||||
.auth_token()
|
// .auth_token()
|
||||||
.context("no auth token configured - run 'wasmer login'")?
|
// .context("no auth token configured - run 'wasmer login'")?
|
||||||
.to_string();
|
// .to_string();
|
||||||
|
//
|
||||||
let publish = wasmer_registry::package::builder::Publish {
|
// let publish = wasmer_registry::package::builder::Publish {
|
||||||
registry: Some(registry),
|
// registry: Some(registry),
|
||||||
dry_run: false,
|
// dry_run: false,
|
||||||
quiet: false,
|
// quiet: false,
|
||||||
package_name: None,
|
// package_name: None,
|
||||||
version: None,
|
// version: None,
|
||||||
wait: wasmer_registry::publish::PublishWait::new_none(),
|
// wait: wasmer_registry::publish::PublishWait::new_none(),
|
||||||
token,
|
// token,
|
||||||
no_validate: true,
|
// no_validate: true,
|
||||||
package_path: Some(dir.to_str().unwrap().to_string()),
|
// package_path: Some(dir.to_str().unwrap().to_string()),
|
||||||
// Use a high timeout to prevent interrupting uploads of
|
// // Use a high timeout to prevent interrupting uploads of
|
||||||
// large packages.
|
// // large packages.
|
||||||
timeout: std::time::Duration::from_secs(60 * 60 * 12),
|
// timeout: std::time::Duration::from_secs(60 * 60 * 12),
|
||||||
package_namespace: patch_owner,
|
// package_namespace: patch_owner,
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
// Publish uses a blocking http client internally, which leads to a
|
// // Publish uses a blocking http client internally, which leads to a
|
||||||
// "can't drop a runtime within an async context" error, so this has
|
// // "can't drop a runtime within an async context" error, so this has
|
||||||
// to be run in a separate thread.
|
// // to be run in a separate thread.
|
||||||
let maybe_hash = std::thread::spawn(move || publish.execute())
|
// let maybe_hash = std::thread::spawn(move || publish.execute())
|
||||||
.join()
|
// .join()
|
||||||
.map_err(|e| anyhow::format_err!("failed to publish package: {:?}", e))??;
|
// .map_err(|e| anyhow::format_err!("failed to publish package: {:?}", e))??;
|
||||||
|
//
|
||||||
Ok((new_manifest.clone(), maybe_hash))
|
// Ok((new_manifest.clone(), maybe_hash))
|
||||||
}
|
// }
|
||||||
|
|
||||||
///// Re-publish a package with an increased minor version.
|
///// Re-publish a package with an increased minor version.
|
||||||
//pub async fn republish_package_with_bumped_version(
|
//pub async fn republish_package_with_bumped_version(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ toml = "0.8"
|
|||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
semver = { version = "1", features = ["serde"] }
|
semver = { version = "1", features = ["serde"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_yaml = "0.9.0"
|
serde_yaml.workspace = true
|
||||||
serde_cbor = "0.11.2"
|
serde_cbor = "0.11.2"
|
||||||
indexmap = { workspace = true, features = ["serde"] }
|
indexmap = { workspace = true, features = ["serde"] }
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.12.0"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ wasmer-config = { workspace = true }
|
|||||||
wasmer-wasm-interface = { version = "4.2.8", path = "../wasm-interface", optional = true }
|
wasmer-wasm-interface = { version = "4.2.8", path = "../wasm-interface", optional = true }
|
||||||
wasmparser = { workspace = true, optional = true }
|
wasmparser = { workspace = true, optional = true }
|
||||||
whoami = "1.2.3"
|
whoami = "1.2.3"
|
||||||
|
webc.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.3.0"
|
pretty_assertions = "1.3.0"
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
mutation PublishPackageMutationChunked(
|
|
||||||
$name: String
|
|
||||||
$namespace: String
|
|
||||||
$version: String
|
|
||||||
$description: String
|
|
||||||
$manifest: String!
|
|
||||||
$license: String
|
|
||||||
$licenseFile: String
|
|
||||||
$readme: String
|
|
||||||
$fileName: String
|
|
||||||
$repository: String
|
|
||||||
$homepage: String
|
|
||||||
$signature: InputSignature
|
|
||||||
$signedUrl: String
|
|
||||||
$private: Boolean
|
|
||||||
$wait: Boolean
|
|
||||||
) {
|
|
||||||
publishPackage(
|
|
||||||
input: {
|
|
||||||
name: $name
|
|
||||||
namespace: $namespace
|
|
||||||
version: $version
|
|
||||||
description: $description
|
|
||||||
manifest: $manifest
|
|
||||||
license: $license
|
|
||||||
licenseFile: $licenseFile
|
|
||||||
readme: $readme
|
|
||||||
file: $fileName
|
|
||||||
signedUrl: $signedUrl
|
|
||||||
repository: $repository
|
|
||||||
homepage: $homepage
|
|
||||||
signature: $signature
|
|
||||||
clientMutationId: ""
|
|
||||||
private: $private
|
|
||||||
wait: $wait
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
success
|
|
||||||
packageVersion {
|
|
||||||
id
|
|
||||||
version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ use rusqlite::{params, Connection, OpenFlags, TransactionBehavior};
|
|||||||
use tar::Builder;
|
use tar::Builder;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use time::{self, OffsetDateTime};
|
use time::{self, OffsetDateTime};
|
||||||
|
use wasmer_config::package::PackageIdent;
|
||||||
|
|
||||||
use crate::publish::PublishWait;
|
use crate::publish::PublishWait;
|
||||||
use crate::{package::builder::validate::ValidationPolicy, publish::SignArchiveResult};
|
use crate::{package::builder::validate::ValidationPolicy, publish::SignArchiveResult};
|
||||||
@@ -22,7 +23,7 @@ const MIGRATIONS: &[(i32, &str)] = &[
|
|||||||
|
|
||||||
const CURRENT_DATA_VERSION: usize = MIGRATIONS.len();
|
const CURRENT_DATA_VERSION: usize = MIGRATIONS.len();
|
||||||
|
|
||||||
/// CLI options for the `wasmer publish` command
|
/// An abstraction for the action of publishing a named or unnamed package.
|
||||||
pub struct Publish {
|
pub struct Publish {
|
||||||
/// Registry to publish to
|
/// Registry to publish to
|
||||||
pub registry: Option<String>,
|
pub registry: Option<String>,
|
||||||
@@ -30,7 +31,9 @@ pub struct Publish {
|
|||||||
pub dry_run: bool,
|
pub dry_run: bool,
|
||||||
/// Run the publish command without any output
|
/// Run the publish command without any output
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
/// Override the package of the uploaded package in the wasmer.toml
|
/// Override the namespace of the package to upload
|
||||||
|
pub package_namespace: Option<String>,
|
||||||
|
/// Override the name of the package to upload
|
||||||
pub package_name: Option<String>,
|
pub package_name: Option<String>,
|
||||||
/// Override the package version of the uploaded package in the wasmer.toml
|
/// Override the package version of the uploaded package in the wasmer.toml
|
||||||
pub version: Option<semver::Version>,
|
pub version: Option<semver::Version>,
|
||||||
@@ -44,8 +47,6 @@ pub struct Publish {
|
|||||||
pub wait: PublishWait,
|
pub wait: PublishWait,
|
||||||
/// Timeout (in seconds) for the publish query to the registry
|
/// Timeout (in seconds) for the publish query to the registry
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
|
|
||||||
pub package_namespace: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -65,8 +66,8 @@ enum PackageBuildError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Publish {
|
impl Publish {
|
||||||
/// Executes `wasmer publish`
|
/// Publish the package to the selected (or default) registry.
|
||||||
pub fn execute(&self) -> Result<Option<String>, anyhow::Error> {
|
pub fn execute(&self) -> Result<Option<PackageIdent>, anyhow::Error> {
|
||||||
let input_path = match self.package_path.as_ref() {
|
let input_path = match self.package_path.as_ref() {
|
||||||
Some(s) => std::env::current_dir()?.join(s),
|
Some(s) => std::env::current_dir()?.join(s),
|
||||||
None => std::env::current_dir()?,
|
None => std::env::current_dir()?,
|
||||||
@@ -164,19 +165,7 @@ impl Publish {
|
|||||||
|
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
// dry run: publish is done here
|
// dry run: publish is done here
|
||||||
|
println!("🚀 Package published successfully!");
|
||||||
match manifest.package {
|
|
||||||
Some(pkg) => {
|
|
||||||
println!(
|
|
||||||
"🚀 Successfully published package `{}@{}`",
|
|
||||||
pkg.name, pkg.version
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => println!(
|
|
||||||
"🚀 Successfully published unnamed package from `{}`",
|
|
||||||
manifest_path.display()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = archive_dir.into_path();
|
let path = archive_dir.into_path();
|
||||||
eprintln!("Archive persisted at: {}", path.display());
|
eprintln!("Archive persisted at: {}", path.display());
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ use std::collections::BTreeMap;
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use wasmer_config::package::{NamedPackageIdent, PackageHash, PackageIdent};
|
||||||
|
|
||||||
static UPLOAD: Emoji<'_, '_> = Emoji("⬆️ ", "");
|
static UPLOAD: Emoji<'_, '_> = Emoji("⬆️ ", "");
|
||||||
static PACKAGE: Emoji<'_, '_> = Emoji("📦", "");
|
static PACKAGE: Emoji<'_, '_> = Emoji("📦", "");
|
||||||
@@ -91,7 +93,7 @@ pub fn try_chunked_uploading(
|
|||||||
wait: PublishWait,
|
wait: PublishWait,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
patch_namespace: Option<String>,
|
patch_namespace: Option<String>,
|
||||||
) -> Result<Option<String>, anyhow::Error> {
|
) -> Result<Option<PackageIdent>, anyhow::Error> {
|
||||||
let (registry, token) = initialize_registry_and_token(registry, token)?;
|
let (registry, token) = initialize_registry_and_token(registry, token)?;
|
||||||
|
|
||||||
let maybe_signature_data = sign_package(maybe_signature_data);
|
let maybe_signature_data = sign_package(maybe_signature_data);
|
||||||
@@ -117,11 +119,11 @@ pub fn try_chunked_uploading(
|
|||||||
version: package.as_ref().map(|p| p.version.to_string()),
|
version: package.as_ref().map(|p| p.version.to_string()),
|
||||||
description: package.as_ref().map(|p| p.description.clone()),
|
description: package.as_ref().map(|p| p.description.clone()),
|
||||||
manifest: manifest_string.to_string(),
|
manifest: manifest_string.to_string(),
|
||||||
license: package.as_ref().map(|p| p.license.clone()).flatten(),
|
license: package.as_ref().and_then(|p| p.license.clone()),
|
||||||
license_file: license_file.to_owned(),
|
license_file: license_file.to_owned(),
|
||||||
readme: readme.to_owned(),
|
readme: readme.to_owned(),
|
||||||
repository: package.as_ref().map(|p| p.repository.clone()).flatten(),
|
repository: package.as_ref().and_then(|p| p.repository.clone()),
|
||||||
homepage: package.as_ref().map(|p| p.homepage.clone()).flatten(),
|
homepage: package.as_ref().and_then(|p| p.homepage.clone()),
|
||||||
file_name: Some(archive_name.to_string()),
|
file_name: Some(archive_name.to_string()),
|
||||||
signature: maybe_signature_data,
|
signature: maybe_signature_data,
|
||||||
signed_url: Some(signed_url.url),
|
signed_url: Some(signed_url.url),
|
||||||
@@ -135,12 +137,15 @@ pub fn try_chunked_uploading(
|
|||||||
let response: publish_package_mutation_chunked::ResponseData =
|
let response: publish_package_mutation_chunked::ResponseData =
|
||||||
crate::graphql::execute_query_with_timeout(®istry, &token, timeout, &q)?;
|
crate::graphql::execute_query_with_timeout(®istry, &token, timeout, &q)?;
|
||||||
|
|
||||||
let mut package_hash = None;
|
if let Some(payload) = response.publish_package {
|
||||||
if let Some(pkg) = response.publish_package {
|
if !payload.success {
|
||||||
if !pkg.success {
|
|
||||||
return Err(anyhow::anyhow!("Could not publish package"));
|
return Err(anyhow::anyhow!("Could not publish package"));
|
||||||
}
|
}
|
||||||
if let Some(pkg_version) = pkg.package_version {
|
|
||||||
|
if let Some(pkg_version) = payload.package_version {
|
||||||
|
// Here we can assume that the package is *Some*.
|
||||||
|
let package = package.clone().unwrap();
|
||||||
|
|
||||||
if wait.is_any() {
|
if wait.is_any() {
|
||||||
let f = wait_for_package_version_to_become_ready(
|
let f = wait_for_package_version_to_become_ready(
|
||||||
®istry,
|
®istry,
|
||||||
@@ -156,22 +161,28 @@ pub fn try_chunked_uploading(
|
|||||||
tokio::runtime::Runtime::new().unwrap().block_on(f)?;
|
tokio::runtime::Runtime::new().unwrap().block_on(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(pkg_hash) = pkg.package_webc {
|
|
||||||
package_hash = Some(pkg_hash.webc.unwrap().webc_sha256);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pkg) = package {
|
let package_ident = PackageIdent::Named(NamedPackageIdent::from_str(&format!(
|
||||||
println!(
|
"{}@{}",
|
||||||
"🚀 Successfully published package `{}@{}`",
|
package.name, package.version
|
||||||
pkg.name, pkg.version,
|
))?);
|
||||||
);
|
|
||||||
|
println!("🚀 Successfully published package `{}`", package_ident);
|
||||||
|
return Ok(Some(package_ident));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pkg_hash) = payload.package_webc {
|
||||||
|
let package_ident = PackageIdent::Hash(
|
||||||
|
PackageHash::from_str(&pkg_hash.webc.unwrap().webc_sha256).unwrap(),
|
||||||
|
);
|
||||||
|
println!("🚀 Successfully published package `{}`", package_ident);
|
||||||
|
return Ok(Some(package_ident));
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!();
|
||||||
} else {
|
} else {
|
||||||
println!("🚀 Successfully published unnamed package",);
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(package_hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_registry_and_token(
|
fn initialize_registry_and_token(
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async-trait = { version = "^0.1" }
|
|||||||
urlencoding = { version = "^2" }
|
urlencoding = { version = "^2" }
|
||||||
serde_derive = { version = "^1" }
|
serde_derive = { version = "^1" }
|
||||||
serde_json = { version = "^1" }
|
serde_json = { version = "^1" }
|
||||||
serde_yaml = { version = "^0.9" }
|
serde_yaml.workspace = true
|
||||||
weezl = { version = "^0.1" }
|
weezl = { version = "^0.1" }
|
||||||
hex = { version = "^0.4" }
|
hex = { version = "^0.4" }
|
||||||
linked_hash_set = { version = "0.1" }
|
linked_hash_set = { version = "0.1" }
|
||||||
|
|||||||
Reference in New Issue
Block a user