mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 21:58:20 +00:00
feat(cli): Use registry templates to create new apps
This commit is contained in:
193
Cargo.lock
generated
193
Cargo.lock
generated
@@ -17,6 +17,17 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -450,6 +461,27 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.6"
|
||||
@@ -654,6 +686,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
@@ -921,6 +962,21 @@ dependencies = [
|
||||
"build_const",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
@@ -1254,6 +1310,12 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@@ -1407,6 +1469,17 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distance"
|
||||
version = "0.4.0"
|
||||
@@ -1695,6 +1768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-ng-sys",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
@@ -2708,6 +2782,16 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-ng-sys"
|
||||
version = "1.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -2830,7 +2914,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aba8ecb0450dfabce4ad72085eed0a75dffe8f21f7ada05638564ea9db2d7fb1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc",
|
||||
"crc 1.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc 3.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4669,6 +4763,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.4"
|
||||
@@ -5707,6 +5807,12 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@@ -6573,6 +6679,7 @@ dependencies = [
|
||||
"wasmer-wasix",
|
||||
"wasmer-wast",
|
||||
"webc",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6882,7 +6989,7 @@ dependencies = [
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
"log 0.4.21",
|
||||
"lzma-rs",
|
||||
"lzma-rs 0.2.0",
|
||||
"minisign",
|
||||
"pretty_assertions",
|
||||
"rand",
|
||||
@@ -7665,3 +7772,85 @@ name = "zeroize"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c700ea425e148de30c29c580c1f9508b93ca57ad31c9f4e96b83c194c37a7a8f"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"crossbeam-utils 0.8.19",
|
||||
"deflate64",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"indexmap 2.2.6",
|
||||
"lzma-rs 0.3.0",
|
||||
"pbkdf2",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"time 0.3.36",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"log 0.4.21",
|
||||
"simd-adler32",
|
||||
"typed-arena",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.10+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -12,7 +12,8 @@ use wasmer_config::package::PackageIdent;
|
||||
use crate::{
|
||||
types::{
|
||||
self, CreateNamespaceVars, DeployApp, DeployAppConnection, DeployAppVersion,
|
||||
DeployAppVersionConnection, DnsDomain, GetCurrentUserWithAppsVars, GetDeployAppAndVersion,
|
||||
DeployAppVersionConnection, DnsDomain, GetAppTemplateFromSlugVariables,
|
||||
GetAppTemplatesQueryVariables, GetCurrentUserWithAppsVars, GetDeployAppAndVersion,
|
||||
GetDeployAppVersionsVars, GetNamespaceAppsVars, GetSignedUrlForPackageUploadVariables, Log,
|
||||
LogStream, PackageVersionConnection, PublishDeployAppVars, PushPackageReleasePayload,
|
||||
SignedUrl, TagPackageReleasePayload, UpsertDomainFromZoneFileVars,
|
||||
@@ -55,6 +56,36 @@ pub async fn fetch_webc_package(
|
||||
webc::compat::Container::from_bytes(data).context("failed to parse webc package")
|
||||
}
|
||||
|
||||
/// Fetch app templates.
|
||||
pub async fn fetch_app_template_from_slug(
|
||||
client: &WasmerClient,
|
||||
slug: String,
|
||||
) -> Result<Option<types::AppTemplate>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql_strict(types::GetAppTemplateFromSlug::build(
|
||||
GetAppTemplateFromSlugVariables { slug },
|
||||
))
|
||||
.await
|
||||
.map(|v| v.get_app_template)
|
||||
}
|
||||
|
||||
/// Fetch app templates.
|
||||
pub async fn fetch_app_templates(
|
||||
client: &WasmerClient,
|
||||
category_slug: String,
|
||||
first: i32,
|
||||
) -> Result<Option<types::AppTemplateConnection>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql_strict(types::GetAppTemplatesQuery::build(
|
||||
GetAppTemplatesQueryVariables {
|
||||
category_slug,
|
||||
first,
|
||||
},
|
||||
))
|
||||
.await
|
||||
.map(|r| r.get_app_templates)
|
||||
}
|
||||
|
||||
/// Get a signed URL to upload packages.
|
||||
pub async fn get_signed_url_for_package_upload(
|
||||
client: &WasmerClient,
|
||||
|
||||
@@ -126,6 +126,64 @@ mod queries {
|
||||
pub package: Package,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetAppTemplateFromSlugVariables {
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetAppTemplateFromSlugVariables")]
|
||||
pub struct GetAppTemplateFromSlug {
|
||||
#[arguments(slug: $slug)]
|
||||
pub get_app_template: Option<AppTemplate>,
|
||||
}
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetAppTemplatesQueryVariables {
|
||||
pub category_slug: String,
|
||||
pub first: i32,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetAppTemplatesQueryVariables")]
|
||||
pub struct GetAppTemplatesQuery {
|
||||
#[arguments(categorySlug: $category_slug, first: $first)]
|
||||
pub get_app_templates: Option<AppTemplateConnection>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct AppTemplateConnection {
|
||||
pub edges: Vec<Option<AppTemplateEdge>>,
|
||||
pub page_info: PageInfo,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct AppTemplateEdge {
|
||||
pub node: Option<AppTemplate>,
|
||||
pub cursor: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct AppTemplate {
|
||||
pub demo_url: String,
|
||||
pub language: String,
|
||||
pub name: String,
|
||||
pub framework: String,
|
||||
pub created_at: DateTime,
|
||||
pub description: String,
|
||||
pub id: cynic::Id,
|
||||
pub is_public: bool,
|
||||
pub repo_license: String,
|
||||
pub readme: String,
|
||||
pub repo_url: String,
|
||||
pub slug: String,
|
||||
pub updated_at: DateTime,
|
||||
pub use_cases: Jsonstring,
|
||||
}
|
||||
|
||||
#[derive(cynic::Scalar, Debug, Clone)]
|
||||
#[cynic(graphql_type = "JSONString")]
|
||||
pub struct Jsonstring(pub String);
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetPackageReleaseVars {
|
||||
pub hash: String,
|
||||
|
||||
@@ -185,7 +185,7 @@ tldextract = "0.6.0"
|
||||
hex = "0.4.3"
|
||||
flate2 = "1.0.25"
|
||||
cargo_metadata = "0.15.2"
|
||||
tar = "0.4.38"
|
||||
tar = "0.4.40"
|
||||
bytes = "1"
|
||||
thiserror = "1.0.37"
|
||||
log = "0.4.17"
|
||||
@@ -229,6 +229,7 @@ tun-tap = { version = "0.1.3", features = ["tokio"], optional = true }
|
||||
|
||||
clap_complete = "4.5.2"
|
||||
clap_mangen = "0.2.20"
|
||||
zip = "1.2.3"
|
||||
|
||||
# NOTE: Must use different features for clap because the "color" feature does not
|
||||
# work on wasi due to the anstream dependency not compiling.
|
||||
|
||||
@@ -3,23 +3,17 @@
|
||||
use crate::{
|
||||
commands::AsyncCliCommand,
|
||||
opts::{ApiOpts, ItemFormatOpts, WasmerEnv},
|
||||
utils::{
|
||||
load_package_manifest,
|
||||
package_wizard::{CreateMode, PackageType, PackageWizard},
|
||||
},
|
||||
utils::load_package_manifest,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use colored::Colorize;
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, Select};
|
||||
use is_terminal::IsTerminal;
|
||||
use std::{collections::HashMap, env, path::PathBuf, str::FromStr};
|
||||
use wasmer_api::{types::UserWithNamespaces, WasmerClient};
|
||||
use wasmer_config::{
|
||||
app::AppConfigV1,
|
||||
package::{NamedPackageIdent, PackageSource, Tag},
|
||||
};
|
||||
use std::{collections::HashMap, env, io::Cursor, path::PathBuf, str::FromStr};
|
||||
use wasmer_api::{types::AppTemplate, WasmerClient};
|
||||
use wasmer_config::{app::AppConfigV1, package::PackageSource};
|
||||
|
||||
use super::deploy::CmdAppDeploy;
|
||||
use super::{deploy::CmdAppDeploy, util::login_user};
|
||||
|
||||
async fn write_app_config(app_config: &AppConfigV1, dir: Option<PathBuf>) -> anyhow::Result<()> {
|
||||
let raw_app_config = app_config.clone().to_yaml()?;
|
||||
@@ -41,8 +35,30 @@ async fn write_app_config(app_config: &AppConfigV1, dir: Option<PathBuf>) -> any
|
||||
/// Create a new Edge app.
|
||||
#[derive(clap::Parser, Debug)]
|
||||
pub struct CmdAppCreate {
|
||||
#[clap(name = "type", short = 't', long)]
|
||||
pub template: Option<AppType>,
|
||||
/// A reference to the template to use.
|
||||
///
|
||||
/// It can be either an URL to a github repository - like
|
||||
/// `https://github.com/wasmer-examples/php-wasmer-starter` - or the name of a template that
|
||||
/// will be searched for in the selected registry, like `astro-starter`.
|
||||
#[clap(
|
||||
long,
|
||||
conflicts_with = "package",
|
||||
conflicts_with = "use_local_manifest"
|
||||
)]
|
||||
pub template: Option<String>,
|
||||
|
||||
/// Name of the package to use.
|
||||
#[clap(
|
||||
long,
|
||||
conflicts_with = "template",
|
||||
conflicts_with = "use_local_manifest"
|
||||
)]
|
||||
pub package: Option<String>,
|
||||
|
||||
/// Whether or not to search (and use) a local manifest.
|
||||
#[clap(long, conflicts_with = "template", conflicts_with = "package")]
|
||||
pub use_local_manifest: bool,
|
||||
|
||||
/// Whether or not to deploy the application once it is created.
|
||||
///
|
||||
/// If selected, this might entail the step of publishing the package related to the
|
||||
@@ -90,17 +106,13 @@ pub struct CmdAppCreate {
|
||||
#[allow(missing_docs)]
|
||||
pub fmt: ItemFormatOpts,
|
||||
|
||||
/// Name of the package to use.
|
||||
#[clap(long, short = 'p')]
|
||||
pub package: Option<String>,
|
||||
|
||||
/// Whether or not to search (and use) a local manifest.
|
||||
#[clap(long)]
|
||||
pub use_local_manifest: bool,
|
||||
|
||||
/// Name to use when creating a new package from a template.
|
||||
#[clap(long)]
|
||||
pub new_package_name: Option<String>,
|
||||
|
||||
/// Don't print any message.
|
||||
#[clap(long)]
|
||||
pub quiet: bool,
|
||||
}
|
||||
|
||||
impl CmdAppCreate {
|
||||
@@ -145,7 +157,7 @@ impl CmdAppCreate {
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_owner(&self) -> anyhow::Result<String> {
|
||||
async fn get_owner(&self, client: &WasmerClient) -> anyhow::Result<String> {
|
||||
if let Some(owner) = &self.owner {
|
||||
return Ok(owner.clone());
|
||||
}
|
||||
@@ -156,21 +168,12 @@ impl CmdAppCreate {
|
||||
}
|
||||
|
||||
if !self.offline {
|
||||
match self.api.client() {
|
||||
Ok(client) => {
|
||||
let user =
|
||||
wasmer_api::query::current_user_with_namespaces(&client, None).await?;
|
||||
let user = wasmer_api::query::current_user_with_namespaces(&client, None).await?;
|
||||
crate::utils::prompts::prompt_for_namespace(
|
||||
"Who should own this app?",
|
||||
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 specify the owner of the app to deploy."
|
||||
),
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Please, user `wasmer login` before deploying an app or use the --owner <owner>
|
||||
@@ -213,10 +216,9 @@ impl CmdAppCreate {
|
||||
};
|
||||
|
||||
if self.use_local_manifest || ask_confirmation()? {
|
||||
let app_config =
|
||||
self.get_app_config(owner, app_name, manifest_path.to_string_lossy().as_ref());
|
||||
let app_config = self.get_app_config(owner, app_name, ".");
|
||||
write_app_config(&app_config, self.app_dir_path.clone()).await?;
|
||||
self.try_deploy(owner).await?;
|
||||
self.try_deploy(owner, app_name).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -231,7 +233,7 @@ impl CmdAppCreate {
|
||||
if let Some(pkg) = &self.package {
|
||||
let app_config = self.get_app_config(owner, app_name, pkg);
|
||||
write_app_config(&app_config, self.app_dir_path.clone()).await?;
|
||||
self.try_deploy(owner).await?;
|
||||
self.try_deploy(owner, app_name).await?;
|
||||
return Ok(true);
|
||||
} else if !self.non_interactive {
|
||||
let theme = ColorfulTheme::default();
|
||||
@@ -241,7 +243,7 @@ impl CmdAppCreate {
|
||||
|
||||
let app_config = self.get_app_config(owner, app_name, &package_name);
|
||||
write_app_config(&app_config, self.app_dir_path.clone()).await?;
|
||||
self.try_deploy(owner).await?;
|
||||
self.try_deploy(owner, app_name).await?;
|
||||
return Ok(true);
|
||||
} else {
|
||||
eprintln!(
|
||||
@@ -253,109 +255,202 @@ impl CmdAppCreate {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn create_from_template(&self, owner: &str, app_name: &str) -> anyhow::Result<bool> {
|
||||
let template = match self.template {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
if !self.non_interactive {
|
||||
// A utility function used to fetch the URL of the template to use.
|
||||
async fn get_template_url(&self, client: &WasmerClient) -> anyhow::Result<url::Url> {
|
||||
let mut url = if let Some(template) = &self.template {
|
||||
if let Ok(url) = url::Url::parse(&template) {
|
||||
url
|
||||
} else if let Some(template) =
|
||||
wasmer_api::query::fetch_app_template_from_slug(client, template.clone()).await?
|
||||
{
|
||||
url::Url::parse(&template.repo_url)?
|
||||
} else {
|
||||
anyhow::bail!("Template '{}' not found in the registry", template)
|
||||
}
|
||||
} else {
|
||||
if self.non_interactive {
|
||||
anyhow::bail!("No template selected")
|
||||
}
|
||||
|
||||
let templates: Vec<AppTemplate> =
|
||||
wasmer_api::query::fetch_app_templates(client, String::new(), 20)
|
||||
.await?
|
||||
.ok_or(anyhow::anyhow!("No template received from the backend"))?
|
||||
.edges
|
||||
.into_iter()
|
||||
.filter(|v| v.is_some())
|
||||
.map(|v| v.unwrap())
|
||||
.filter(|v| v.node.is_some())
|
||||
.map(|v| v.node.unwrap())
|
||||
.collect();
|
||||
|
||||
let theme = ColorfulTheme::default();
|
||||
let index = dialoguer::Select::with_theme(&theme)
|
||||
.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}'"),
|
||||
}
|
||||
let items = templates
|
||||
.iter()
|
||||
.map(|t| {
|
||||
format!(
|
||||
"{}{}{}\n",
|
||||
t.name.bold(),
|
||||
if t.language.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let allow_local_package = match template {
|
||||
AppType::HttpServer => true,
|
||||
AppType::StaticWebsite => true,
|
||||
AppType::BrowserShell => false,
|
||||
AppType::JsWorker => true,
|
||||
AppType::PyApplication => true,
|
||||
};
|
||||
|
||||
let app_dir_path = match &self.app_dir_path {
|
||||
Some(dir) => dir.clone(),
|
||||
None => std::env::current_dir()?,
|
||||
};
|
||||
|
||||
let local_package = if allow_local_package {
|
||||
match crate::utils::load_package_manifest(&app_dir_path) {
|
||||
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 user = if self.offline {
|
||||
None
|
||||
} else if let Ok(client) = &self.api.client() {
|
||||
let u = wasmer_api::query::current_user_with_namespaces(
|
||||
client,
|
||||
Some(wasmer_api::types::GrapheneRole::Admin),
|
||||
)
|
||||
.await?;
|
||||
Some(u)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let creator = AppCreator {
|
||||
app_name: String::from(app_name),
|
||||
new_package_name: self.new_package_name.clone(),
|
||||
package: self.package.clone(),
|
||||
template,
|
||||
interactive: !self.non_interactive,
|
||||
app_dir_path,
|
||||
owner: String::from(owner),
|
||||
api: if self.offline {
|
||||
None
|
||||
} else {
|
||||
self.api.client().ok()
|
||||
format!(" {}", t.language.dimmed())
|
||||
},
|
||||
user,
|
||||
local_package,
|
||||
format!(
|
||||
"\n {} {}",
|
||||
"demo url:".bold().dimmed(),
|
||||
t.demo_url.dimmed()
|
||||
)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// items.sort();
|
||||
|
||||
let dialog = dialoguer::Select::with_theme(&theme)
|
||||
.with_prompt(format!("Select a template ({} available)", items.len()))
|
||||
.items(&items)
|
||||
.max_length(3)
|
||||
.clear(true)
|
||||
.report(false)
|
||||
.default(0);
|
||||
|
||||
let selection = dialog.interact()?;
|
||||
|
||||
let selected_template = templates
|
||||
.get(selection)
|
||||
.ok_or(anyhow::anyhow!("Invalid selection!"))?;
|
||||
|
||||
if !self.quiet {
|
||||
eprintln!(
|
||||
"{} {} {} {} ({} {})",
|
||||
"✔".green().bold(),
|
||||
"Selected template".bold(),
|
||||
"·".dimmed(),
|
||||
selected_template.name.green().bold(),
|
||||
"demo url".dimmed().bold(),
|
||||
selected_template.demo_url.dimmed()
|
||||
)
|
||||
}
|
||||
|
||||
url::Url::parse(&selected_template.repo_url)?
|
||||
};
|
||||
|
||||
match template {
|
||||
AppType::HttpServer
|
||||
| AppType::StaticWebsite
|
||||
| AppType::JsWorker
|
||||
| AppType::PyApplication => creator.build_app().await?,
|
||||
AppType::BrowserShell => creator.build_browser_shell_app().await?,
|
||||
if url.path().contains("archive/refs/heads") {
|
||||
return Ok(url);
|
||||
} else if url.path().contains("/zipball/") {
|
||||
return Ok(url);
|
||||
} else {
|
||||
let old_path = url.path();
|
||||
url.set_path(&format!("{old_path}/zipball/main"));
|
||||
return Ok(url);
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_from_template(
|
||||
&self,
|
||||
client: &WasmerClient,
|
||||
owner: &str,
|
||||
app_name: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let url = self.get_template_url(client).await?;
|
||||
|
||||
tracing::info!("Downloading template from url {url}");
|
||||
|
||||
let output_path = if let Some(path) = &self.app_dir_path {
|
||||
path.clone()
|
||||
} else {
|
||||
PathBuf::from(".").canonicalize()?
|
||||
};
|
||||
|
||||
self.try_deploy(owner).await?;
|
||||
let pb = indicatif::ProgressBar::new_spinner();
|
||||
|
||||
pb.enable_steady_tick(std::time::Duration::from_millis(500));
|
||||
pb.set_style(
|
||||
indicatif::ProgressStyle::with_template("{spinner:.magenta} {msg}")
|
||||
.unwrap()
|
||||
.tick_strings(&["✶", "✸", "✹", "✺", "✹", "✷"]),
|
||||
);
|
||||
|
||||
pb.set_message("Downloading package..");
|
||||
|
||||
let response = reqwest::get(url).await?;
|
||||
let bytes = response.bytes().await?;
|
||||
pb.set_message("Unpacking the template..");
|
||||
let cursor = Cursor::new(bytes);
|
||||
let mut archive = zip::ZipArchive::new(cursor)?;
|
||||
|
||||
// Extract the files to the output path
|
||||
for entry in 0..archive.len() {
|
||||
let mut entry = archive
|
||||
.by_index(entry)
|
||||
.context(format!("Getting the archive entry #{entry}"))?;
|
||||
|
||||
let path = entry.mangled_name();
|
||||
|
||||
let path: PathBuf = {
|
||||
let mut components = path.components();
|
||||
components.next();
|
||||
components.collect()
|
||||
};
|
||||
|
||||
if path.to_str().unwrap_or_default().contains(".github") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = output_path.join(path);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.exists() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !path.exists() {
|
||||
// AsyncRead not implemented for entry..
|
||||
if entry.is_file() {
|
||||
let mut outfile = std::fs::File::create(&path)?;
|
||||
std::io::copy(&mut entry, &mut outfile)?;
|
||||
} else {
|
||||
std::fs::create_dir(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pb.finish();
|
||||
|
||||
let app_yaml_path = output_path.join(AppConfigV1::CANONICAL_FILE_NAME);
|
||||
|
||||
if app_yaml_path.exists() && app_yaml_path.is_file() {
|
||||
let contents = tokio::fs::read_to_string(&app_yaml_path).await?;
|
||||
let contents = format!("{contents}\nname: {app_name}");
|
||||
let mut app_config = AppConfigV1::parse_yaml(&contents)?;
|
||||
app_config.owner = Some(owner.to_string());
|
||||
let raw_app = serde_yaml::to_string(&app_config)?;
|
||||
tokio::fs::write(&app_yaml_path, raw_app).await?;
|
||||
}
|
||||
|
||||
let build_md_path = output_path.join("BUILD.md");
|
||||
if build_md_path.exists() {
|
||||
let contents = tokio::fs::read_to_string(build_md_path).await?;
|
||||
eprintln!(
|
||||
"{}: {}
|
||||
{}",
|
||||
"NOTE".bold(),
|
||||
"The selected template has a `BUILD.md` file.
|
||||
This means there are likely additional build
|
||||
steps that you need to perform before deploying
|
||||
the app:\n"
|
||||
.bold(),
|
||||
contents
|
||||
);
|
||||
} else {
|
||||
self.try_deploy(owner, app_name).await?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_deploy(&self, owner: &str) -> anyhow::Result<()> {
|
||||
async fn try_deploy(&self, owner: &str, app_name: &str) -> anyhow::Result<()> {
|
||||
let interactive = !self.non_interactive;
|
||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||
|
||||
@@ -380,8 +475,11 @@ impl CmdAppCreate {
|
||||
no_default: false,
|
||||
no_persist_id: false,
|
||||
owner: Some(String::from(owner)),
|
||||
app_name: None,
|
||||
app_name: Some(app_name.into()),
|
||||
bump: false,
|
||||
template: self.template.clone(),
|
||||
package: self.package.clone(),
|
||||
use_local_manifest: self.use_local_manifest,
|
||||
};
|
||||
cmd_deploy.run_async().await?;
|
||||
}
|
||||
@@ -395,15 +493,24 @@ impl AsyncCliCommand for CmdAppCreate {
|
||||
type Output = ();
|
||||
|
||||
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
|
||||
let client = login_user(
|
||||
&self.api,
|
||||
&self.env,
|
||||
!self.non_interactive,
|
||||
"retrieve informations about the owner of the app",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Get the future owner of the app.
|
||||
let owner = self.get_owner().await?;
|
||||
let owner = self.get_owner(&client).await?;
|
||||
|
||||
// Get the name of the app.
|
||||
let app_name = self.get_app_name().await?;
|
||||
|
||||
if !self.create_from_local_manifest(&owner, &app_name).await? {
|
||||
if self.template.is_some() {
|
||||
self.create_from_template(&owner, &app_name).await?;
|
||||
self.create_from_template(&client, &owner, &app_name)
|
||||
.await?;
|
||||
} else if self.package.is_some() {
|
||||
self.create_from_package(&owner, &app_name).await?;
|
||||
} else if !self.non_interactive {
|
||||
@@ -414,7 +521,10 @@ impl AsyncCliCommand for CmdAppCreate {
|
||||
.default(0)
|
||||
.interact()?;
|
||||
match choice {
|
||||
0 => self.create_from_template(&owner, &app_name).await?,
|
||||
0 => {
|
||||
self.create_from_template(&client, &owner, &app_name)
|
||||
.await?
|
||||
}
|
||||
1 => self.create_from_package(&owner, &app_name).await?,
|
||||
x => panic!("unhandled selection {x}"),
|
||||
};
|
||||
@@ -427,270 +537,6 @@ impl AsyncCliCommand for CmdAppCreate {
|
||||
}
|
||||
}
|
||||
|
||||
/// App type.
|
||||
#[derive(clap::ValueEnum, Clone, Copy, Debug)]
|
||||
pub enum AppType {
|
||||
/// A HTTP server.
|
||||
#[clap(name = "http")]
|
||||
HttpServer,
|
||||
/// A static website.
|
||||
#[clap(name = "static-website")]
|
||||
StaticWebsite,
|
||||
/// Wraps another package to run in the browser.
|
||||
#[clap(name = "browser-shell")]
|
||||
BrowserShell,
|
||||
/// Winter-js based JS-Worker
|
||||
#[clap(name = "js-worker")]
|
||||
JsWorker,
|
||||
/// Python worker
|
||||
#[clap(name = "py-application")]
|
||||
PyApplication,
|
||||
}
|
||||
|
||||
struct AppCreator {
|
||||
package: Option<String>,
|
||||
new_package_name: Option<String>,
|
||||
app_name: String,
|
||||
template: AppType,
|
||||
interactive: bool,
|
||||
app_dir_path: PathBuf,
|
||||
owner: String,
|
||||
api: Option<WasmerClient>,
|
||||
user: Option<UserWithNamespaces>,
|
||||
local_package: Option<(PathBuf, wasmer_config::package::Manifest)>,
|
||||
}
|
||||
|
||||
impl AppCreator {
|
||||
async fn build_browser_shell_app(self) -> Result<(), anyhow::Error> {
|
||||
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!("Select the package to wrap.");
|
||||
|
||||
let (inner_pkg, _inner_pkg_api) = crate::utils::prompt_for_package(
|
||||
"Package",
|
||||
None,
|
||||
Some(crate::utils::PackageCheckMode::MustExist),
|
||||
self.api.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let app_name = self.app_name;
|
||||
eprintln!("What should be the name of the package?");
|
||||
|
||||
let default_name = format!(
|
||||
"{}-{}-webshell",
|
||||
self.owner,
|
||||
inner_pkg.to_string().replace('/', "-")
|
||||
);
|
||||
|
||||
let outer_pkg_name =
|
||||
crate::utils::prompts::prompt_for_ident("Package name", Some(&default_name))?;
|
||||
let outer_pkg_full_name = format!("{}/{}", self.owner, outer_pkg_name);
|
||||
|
||||
// Build the package.
|
||||
|
||||
let public_dir = self.app_dir_path.join("public");
|
||||
if !public_dir.exists() {
|
||||
std::fs::create_dir_all(&public_dir)?;
|
||||
}
|
||||
|
||||
let init = serde_json::json!({
|
||||
"init": format!("{}/{}", inner_pkg.namespace.as_ref().unwrap(), inner_pkg.name),
|
||||
"prompt": inner_pkg.name,
|
||||
"no_welcome": true,
|
||||
"connect": format!("wss://{app_name}.wasmer.app/.well-known/edge-vpn"),
|
||||
});
|
||||
let init_path = public_dir.join("init.json");
|
||||
std::fs::write(&init_path, init.to_string())
|
||||
.with_context(|| format!("Failed to write to '{}'", init_path.display()))?;
|
||||
|
||||
let package = wasmer_config::package::PackageBuilder::new(
|
||||
outer_pkg_full_name,
|
||||
"0.1.0".parse().unwrap(),
|
||||
format!("{} web shell", inner_pkg.name),
|
||||
)
|
||||
.rename_commands_to_raw_command_name(false)
|
||||
.build()?;
|
||||
|
||||
let manifest = wasmer_config::package::ManifestBuilder::new(package)
|
||||
.with_dependency(
|
||||
WASM_BROWSER_CONTAINER_PACKAGE,
|
||||
WASM_BROWSER_CONTAINER_VERSION.to_string().parse().unwrap(),
|
||||
)
|
||||
.map_fs("public", PathBuf::from("public"))
|
||||
.build()?;
|
||||
|
||||
let manifest_path = self.app_dir_path.join("wasmer.toml");
|
||||
|
||||
let raw = manifest.to_string()?;
|
||||
eprintln!(
|
||||
"Writing wasmer.toml package to '{}'",
|
||||
manifest_path.display()
|
||||
);
|
||||
std::fs::write(&manifest_path, raw)?;
|
||||
|
||||
let app_config = AppConfigV1 {
|
||||
name: app_name,
|
||||
app_id: None,
|
||||
owner: Some(self.owner.clone()),
|
||||
package: PackageSource::Path(".".into()),
|
||||
domains: None,
|
||||
env: Default::default(),
|
||||
cli_args: None,
|
||||
capabilities: None,
|
||||
scheduled_tasks: None,
|
||||
volumes: None,
|
||||
health_checks: None,
|
||||
debug: Some(false),
|
||||
scaling: None,
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
write_app_config(&app_config, Some(self.app_dir_path.clone())).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn build_app(self) -> Result<(), anyhow::Error> {
|
||||
let package_opt: Option<NamedPackageIdent> = if let Some(package) = self.package {
|
||||
Some(NamedPackageIdent::from_str(&package)?)
|
||||
} else if let Some((_, local)) = self.local_package.as_ref() {
|
||||
let pkg = match &local.package {
|
||||
Some(pkg) => pkg.clone(),
|
||||
None => anyhow::bail!(
|
||||
"Error while building app: template manifest has no package field!"
|
||||
),
|
||||
};
|
||||
|
||||
if let (Some(name), Some(version)) = (pkg.name, pkg.version) {
|
||||
let full = format!("{}@{}", name, version);
|
||||
let mut pkg_ident = NamedPackageIdent::from_str(&name).with_context(|| {
|
||||
format!("local package manifest has invalid name: '{full}'")
|
||||
})?;
|
||||
|
||||
// Pin the version.
|
||||
pkg_ident.tag = Some(Tag::from_str(&version.to_string()).unwrap());
|
||||
|
||||
if self.interactive {
|
||||
eprintln!("Found local package: '{}'", full.green());
|
||||
|
||||
let msg = format!("Use package '{pkg_ident}'");
|
||||
|
||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||
let should_use = Confirm::with_theme(&theme)
|
||||
.with_prompt(&msg)
|
||||
.interact_opt()?
|
||||
.unwrap_or_default();
|
||||
|
||||
if should_use {
|
||||
Some(pkg_ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(pkg_ident)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (package, _api_pkg, _local_package) = if let Some(pkg) = package_opt {
|
||||
if let Some(api) = &self.api {
|
||||
let p2 = wasmer_api::query::get_package(
|
||||
api,
|
||||
format!("{}/{}", pkg.namespace.as_ref().unwrap(), pkg.name),
|
||||
)
|
||||
.await?;
|
||||
|
||||
(
|
||||
PackageSource::Ident(wasmer_config::package::PackageIdent::Named(pkg)),
|
||||
p2,
|
||||
self.local_package,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
PackageSource::Ident(wasmer_config::package::PackageIdent::Named(pkg)),
|
||||
None,
|
||||
self.local_package,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let ty = match self.template {
|
||||
AppType::HttpServer => None,
|
||||
AppType::StaticWebsite => Some(PackageType::StaticWebsite),
|
||||
AppType::BrowserShell => None,
|
||||
AppType::JsWorker => Some(PackageType::JsWorker),
|
||||
AppType::PyApplication => Some(PackageType::PyApplication),
|
||||
};
|
||||
|
||||
let create_mode = match ty {
|
||||
Some(PackageType::StaticWebsite)
|
||||
| Some(PackageType::JsWorker)
|
||||
| Some(PackageType::PyApplication) => CreateMode::Create,
|
||||
// Only static website creation is currently supported.
|
||||
_ => CreateMode::SelectExisting,
|
||||
};
|
||||
|
||||
let w = PackageWizard {
|
||||
path: self.app_dir_path.clone(),
|
||||
name: self.new_package_name.clone(),
|
||||
type_: ty,
|
||||
create_mode,
|
||||
namespace: Some(self.owner.clone()),
|
||||
namespace_default: self.user.as_ref().map(|u| u.username.clone()),
|
||||
user: self.user.clone(),
|
||||
};
|
||||
|
||||
let output = w.run(self.api.as_ref()).await?;
|
||||
(
|
||||
PackageSource::Path(".".into()),
|
||||
output.api,
|
||||
output
|
||||
.local_path
|
||||
.and_then(move |x| Some((x, output.local_manifest?))),
|
||||
)
|
||||
};
|
||||
|
||||
let name = self.app_name;
|
||||
|
||||
let cli_args = match self.template {
|
||||
AppType::PyApplication => Some(vec!["/src/main.py".to_string()]),
|
||||
AppType::JsWorker => Some(vec!["/src/index.js".to_string()]),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// TODO: check if name already exists.
|
||||
let app_config = AppConfigV1 {
|
||||
name,
|
||||
app_id: None,
|
||||
owner: Some(self.owner.clone()),
|
||||
package,
|
||||
domains: None,
|
||||
env: Default::default(),
|
||||
// CLI args are only set for JS and Py workers for now.
|
||||
cli_args,
|
||||
// TODO: allow setting the description.
|
||||
// description: Some("".to_string()),
|
||||
capabilities: None,
|
||||
scheduled_tasks: None,
|
||||
volumes: None,
|
||||
health_checks: None,
|
||||
debug: Some(false),
|
||||
scaling: None,
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
write_app_config(&app_config, Some(self.app_dir_path.clone())).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -700,6 +546,7 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let cmd = CmdAppCreate {
|
||||
quiet: true,
|
||||
template: Some(AppType::StaticWebsite),
|
||||
deploy_app: false,
|
||||
no_validate: false,
|
||||
@@ -735,6 +582,7 @@ debug: false
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let cmd = CmdAppCreate {
|
||||
quiet: true,
|
||||
template: Some(AppType::HttpServer),
|
||||
deploy_app: false,
|
||||
no_validate: false,
|
||||
@@ -769,6 +617,7 @@ debug: false
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let cmd = CmdAppCreate {
|
||||
quiet: true,
|
||||
template: Some(AppType::JsWorker),
|
||||
deploy_app: false,
|
||||
no_validate: false,
|
||||
@@ -806,6 +655,7 @@ debug: false
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let cmd = CmdAppCreate {
|
||||
quiet: true,
|
||||
template: Some(AppType::PyApplication),
|
||||
deploy_app: false,
|
||||
no_validate: false,
|
||||
|
||||
@@ -76,7 +76,7 @@ pub struct CmdAppDeploy {
|
||||
/// If specified via this flag, the app_name will be overridden. Otherwise, the `app.yaml` is
|
||||
/// inspected and, if there is no `name` field in the spec file, if running interactive the
|
||||
/// user will be prompted to insert an app name, otherwise the deployment will fail.
|
||||
#[clap(long)]
|
||||
#[clap(long, name = "name")]
|
||||
pub app_name: Option<String>,
|
||||
|
||||
/// Whether or not to automatically bump the package version if publishing.
|
||||
@@ -89,6 +89,31 @@ pub struct CmdAppDeploy {
|
||||
/// operation.
|
||||
#[clap(long)]
|
||||
pub quiet: bool,
|
||||
|
||||
// - App creation -
|
||||
/// A reference to the template to use when creating an app to deploy.
|
||||
///
|
||||
/// It can be either an URL to a github repository - like
|
||||
/// `https://github.com/wasmer-examples/php-wasmer-starter` - or the name of a template that
|
||||
/// will be searched for in the selected registry, like `astro-starter`.
|
||||
#[clap(
|
||||
long,
|
||||
conflicts_with = "package",
|
||||
conflicts_with = "use_local_manifest"
|
||||
)]
|
||||
pub template: Option<String>,
|
||||
|
||||
/// Name of the package to use when creating an app to deploy.
|
||||
#[clap(
|
||||
long,
|
||||
conflicts_with = "template",
|
||||
conflicts_with = "use_local_manifest"
|
||||
)]
|
||||
pub package: Option<String>,
|
||||
|
||||
/// Whether or not to search (and use) a local manifest when creating an app to deploy.
|
||||
#[clap(long, conflicts_with = "template", conflicts_with = "package")]
|
||||
pub use_local_manifest: bool,
|
||||
}
|
||||
|
||||
impl CmdAppDeploy {
|
||||
@@ -172,7 +197,7 @@ impl CmdAppDeploy {
|
||||
eprintln!("It seems you are trying to create a new app!");
|
||||
|
||||
let create_cmd = CmdAppCreate {
|
||||
template: None,
|
||||
quiet: self.quiet,
|
||||
deploy_app: false,
|
||||
no_validate: false,
|
||||
non_interactive: false,
|
||||
@@ -185,9 +210,10 @@ impl CmdAppDeploy {
|
||||
fmt: ItemFormatOpts {
|
||||
format: self.fmt.format,
|
||||
},
|
||||
package: None,
|
||||
package: self.package.clone(),
|
||||
template: self.template.clone(),
|
||||
app_dir_path: None,
|
||||
use_local_manifest: false,
|
||||
use_local_manifest: self.use_local_manifest,
|
||||
new_package_name: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,11 +10,8 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use dialoguer::theme::ColorfulTheme;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use wasmer_api::WasmerClient;
|
||||
use wasmer_config::package::NamedPackageIdent;
|
||||
use wasmer_wasix::runners::MappedDirectory;
|
||||
|
||||
fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<MappedDirectory> {
|
||||
@@ -111,79 +108,79 @@ pub fn load_package_manifest(
|
||||
Ok(Some((file_path, manifest)))
|
||||
}
|
||||
|
||||
/// Ask a user for a package name.
|
||||
///
|
||||
/// Will continue looping until the user provides a valid name.
|
||||
pub fn prompt_for_package_name(
|
||||
message: &str,
|
||||
default: Option<&str>,
|
||||
) -> Result<NamedPackageIdent, anyhow::Error> {
|
||||
loop {
|
||||
let theme = ColorfulTheme::default();
|
||||
let raw: String = dialoguer::Input::with_theme(&theme)
|
||||
.with_prompt(message)
|
||||
.with_initial_text(default.unwrap_or_default())
|
||||
.interact_text()
|
||||
.context("could not read user input")?;
|
||||
///// Ask a user for a package name.
|
||||
/////
|
||||
///// Will continue looping until the user provides a valid name.
|
||||
//pub fn prompt_for_package_name(
|
||||
// message: &str,
|
||||
// default: Option<&str>,
|
||||
//) -> Result<NamedPackageIdent, anyhow::Error> {
|
||||
// loop {
|
||||
// let theme = ColorfulTheme::default();
|
||||
// let raw: String = dialoguer::Input::with_theme(&theme)
|
||||
// .with_prompt(message)
|
||||
// .with_initial_text(default.unwrap_or_default())
|
||||
// .interact_text()
|
||||
// .context("could not read user input")?;
|
||||
//
|
||||
// match raw.parse::<NamedPackageIdent>() {
|
||||
// Ok(p) => break Ok(p),
|
||||
// Err(err) => {
|
||||
// eprintln!("invalid package name: {err}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
match raw.parse::<NamedPackageIdent>() {
|
||||
Ok(p) => break Ok(p),
|
||||
Err(err) => {
|
||||
eprintln!("invalid package name: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines how to check for a package.
|
||||
pub enum PackageCheckMode {
|
||||
/// The package must exist in the registry.
|
||||
MustExist,
|
||||
/// The package must NOT exist in the registry.
|
||||
#[allow(dead_code)]
|
||||
MustNotExist,
|
||||
}
|
||||
|
||||
/// Ask for a package name.
|
||||
///
|
||||
/// Will continue looping until the user provides a valid name.
|
||||
///
|
||||
/// If an API is provided, will check if the package exists.
|
||||
pub async fn prompt_for_package(
|
||||
message: &str,
|
||||
default: Option<&str>,
|
||||
check: Option<PackageCheckMode>,
|
||||
client: Option<&WasmerClient>,
|
||||
) -> Result<(NamedPackageIdent, Option<wasmer_api::types::Package>), anyhow::Error> {
|
||||
loop {
|
||||
let name = prompt_for_package_name(message, default)?;
|
||||
|
||||
if let Some(check) = &check {
|
||||
let api = client.expect("Check mode specified, but no API provided");
|
||||
|
||||
let pkg = wasmer_api::query::get_package(api, name.to_string())
|
||||
.await
|
||||
.context("could not query backend for package")?;
|
||||
|
||||
match check {
|
||||
PackageCheckMode::MustExist => {
|
||||
if let Some(pkg) = pkg {
|
||||
break Ok((name, Some(pkg)));
|
||||
} else {
|
||||
eprintln!("Package '{name}' does not exist");
|
||||
}
|
||||
}
|
||||
PackageCheckMode::MustNotExist => {
|
||||
if pkg.is_none() {
|
||||
break Ok((name, None));
|
||||
} else {
|
||||
eprintln!("Package '{name}' already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// /// Defines how to check for a package.
|
||||
// pub enum PackageCheckMode {
|
||||
// /// The package must exist in the registry.
|
||||
// MustExist,
|
||||
// /// The package must NOT exist in the registry.
|
||||
// #[allow(dead_code)]
|
||||
// MustNotExist,
|
||||
// }
|
||||
//
|
||||
// /// Ask for a package name.
|
||||
// ///
|
||||
// /// Will continue looping until the user provides a valid name.
|
||||
// ///
|
||||
// /// If an API is provided, will check if the package exists.
|
||||
// pub async fn prompt_for_package(
|
||||
// message: &str,
|
||||
// default: Option<&str>,
|
||||
// check: Option<PackageCheckMode>,
|
||||
// client: Option<&WasmerClient>,
|
||||
// ) -> Result<(NamedPackageIdent, Option<wasmer_api::types::Package>), anyhow::Error> {
|
||||
// loop {
|
||||
// let name = prompt_for_package_name(message, default)?;
|
||||
//
|
||||
// if let Some(check) = &check {
|
||||
// let api = client.expect("Check mode specified, but no API provided");
|
||||
//
|
||||
// let pkg = wasmer_api::query::get_package(api, name.to_string())
|
||||
// .await
|
||||
// .context("could not query backend for package")?;
|
||||
//
|
||||
// match check {
|
||||
// PackageCheckMode::MustExist => {
|
||||
// if let Some(pkg) = pkg {
|
||||
// break Ok((name, Some(pkg)));
|
||||
// } else {
|
||||
// eprintln!("Package '{name}' does not exist");
|
||||
// }
|
||||
// }
|
||||
// PackageCheckMode::MustNotExist => {
|
||||
// if pkg.is_none() {
|
||||
// break Ok((name, None));
|
||||
// } else {
|
||||
// eprintln!("Package '{name}' already exists");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Republish the package described by the [`wasmer_config::package::Manifest`] given as argument and return a
|
||||
// /// [`Result<wasmer_config::package::Manifest>`].
|
||||
|
||||
@@ -1,426 +1,426 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Context;
|
||||
use dialoguer::{theme::ColorfulTheme, Select};
|
||||
use wasmer_api::{types::UserWithNamespaces, WasmerClient};
|
||||
|
||||
use super::prompts::PackageCheckMode;
|
||||
|
||||
const WASM_STATIC_SERVER_PACKAGE: &str = "wasmer/static-web-server";
|
||||
const WASM_STATIC_SERVER_VERSION: &str = "1";
|
||||
|
||||
const WASMER_WINTER_JS_PACKAGE: &str = "wasmer/winterjs";
|
||||
const WASMER_WINTER_JS_VERSION: &str = "*";
|
||||
|
||||
const WASM_PYTHON_PACKAGE: &str = "wasmer/python";
|
||||
const WASM_PYTHON_VERSION: &str = "3.12.6";
|
||||
|
||||
const SAMPLE_INDEX_HTML: &str = include_str!("./templates/static-site/index.html");
|
||||
const SAMPLE_JS_WORKER: &str = include_str!("./templates/js-worker/index.js");
|
||||
const SAMPLE_PY_APPLICATION: &str = include_str!("./templates/py-application/main.py");
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Copy, Debug)]
|
||||
pub enum PackageType {
|
||||
#[clap(name = "regular")]
|
||||
Regular,
|
||||
/// A static website.
|
||||
#[clap(name = "static-website")]
|
||||
StaticWebsite,
|
||||
/// A js-worker
|
||||
#[clap(name = "js-worker")]
|
||||
JsWorker,
|
||||
/// A py-worker
|
||||
#[clap(name = "py-application")]
|
||||
PyApplication,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CreateMode {
|
||||
Create,
|
||||
SelectExisting,
|
||||
#[allow(dead_code)]
|
||||
CreateOrSelect,
|
||||
}
|
||||
|
||||
fn prompt_for_package_type() -> Result<PackageType, anyhow::Error> {
|
||||
let theme = ColorfulTheme::default();
|
||||
Select::with_theme(&theme)
|
||||
.with_prompt("What type of package do you want to create?")
|
||||
.items(&["Basic pacakge", "Static website"])
|
||||
.interact()
|
||||
.map(|idx| match idx {
|
||||
0 => PackageType::Regular,
|
||||
1 => PackageType::StaticWebsite,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageWizard {
|
||||
pub path: PathBuf,
|
||||
pub type_: Option<PackageType>,
|
||||
|
||||
pub create_mode: CreateMode,
|
||||
|
||||
/// Namespace to use.
|
||||
pub namespace: Option<String>,
|
||||
/// Default namespace to use.
|
||||
/// Will still show a prompt, with this as the default value.
|
||||
/// Ignored if [`Self::namespace`] is set.
|
||||
pub namespace_default: Option<String>,
|
||||
|
||||
/// Pre-configured package name.
|
||||
pub name: Option<String>,
|
||||
|
||||
pub user: Option<UserWithNamespaces>,
|
||||
}
|
||||
|
||||
pub struct PackageWizardOutput {
|
||||
pub api: Option<wasmer_api::types::Package>,
|
||||
pub local_path: Option<PathBuf>,
|
||||
pub local_manifest: Option<wasmer_config::package::Manifest>,
|
||||
}
|
||||
|
||||
impl PackageWizard {
|
||||
fn build_new_package(&self) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
let ty = match self.type_ {
|
||||
Some(t) => t,
|
||||
None => prompt_for_package_type()?,
|
||||
};
|
||||
|
||||
if !self.path.is_dir() {
|
||||
std::fs::create_dir_all(&self.path).with_context(|| {
|
||||
format!("Failed to create directory: '{}'", self.path.display())
|
||||
})?;
|
||||
}
|
||||
|
||||
let manifest = match ty {
|
||||
PackageType::Regular => todo!(),
|
||||
PackageType::StaticWebsite => initialize_static_site(&self.path)?,
|
||||
PackageType::JsWorker => initialize_js_worker(&self.path)?,
|
||||
PackageType::PyApplication => initialize_py_worker(&self.path)?,
|
||||
};
|
||||
|
||||
let manifest_path = self.path.join("wasmer.toml");
|
||||
let manifest_raw = manifest
|
||||
.to_string()
|
||||
.context("could not serialize package manifest")?;
|
||||
std::fs::write(manifest_path, manifest_raw)
|
||||
.with_context(|| format!("Failed to write manifest to '{}'", self.path.display()))?;
|
||||
|
||||
Ok(PackageWizardOutput {
|
||||
api: None,
|
||||
local_path: Some(self.path.clone()),
|
||||
local_manifest: Some(manifest),
|
||||
})
|
||||
}
|
||||
|
||||
async fn prompt_existing_package(
|
||||
&self,
|
||||
api: Option<&WasmerClient>,
|
||||
) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
// Existing package
|
||||
let check = if api.is_some() {
|
||||
Some(PackageCheckMode::MustExist)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
eprintln!("Enter the name of an existing package:");
|
||||
let (_ident, api) = super::prompts::prompt_for_package("Package", None, check, api).await?;
|
||||
Ok(PackageWizardOutput {
|
||||
api,
|
||||
local_path: None,
|
||||
local_manifest: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
self,
|
||||
api: Option<&WasmerClient>,
|
||||
) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
match self.create_mode {
|
||||
CreateMode::Create => self.build_new_package(),
|
||||
CreateMode::SelectExisting => self.prompt_existing_package(api).await,
|
||||
CreateMode::CreateOrSelect => {
|
||||
let theme = ColorfulTheme::default();
|
||||
let index = Select::with_theme(&theme)
|
||||
.with_prompt("What package do you want to use?")
|
||||
.items(&["Create new package", "Use existing package"])
|
||||
.default(0)
|
||||
.interact()?;
|
||||
|
||||
match index {
|
||||
0 => self.build_new_package(),
|
||||
1 => self.prompt_existing_package(api).await,
|
||||
other => {
|
||||
unreachable!("Unexpected index: {other}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_static_site(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
let pubdir_name = "public";
|
||||
let pubdir = path.join(pubdir_name);
|
||||
if !pubdir.is_dir() {
|
||||
std::fs::create_dir_all(&pubdir)
|
||||
.with_context(|| format!("Failed to create directory: '{}'", pubdir.display()))?;
|
||||
}
|
||||
let index = pubdir.join("index.html");
|
||||
|
||||
let static_html = SAMPLE_INDEX_HTML.replace("{{title}}", "My static website");
|
||||
|
||||
if !index.is_file() {
|
||||
std::fs::write(&index, static_html.as_str())
|
||||
.with_context(|| "Could not write index.html file".to_string())?;
|
||||
} else {
|
||||
// The index.js file already exists, so we can ask the user if they want to overwrite it
|
||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||
let should_overwrite = dialoguer::Confirm::with_theme(&theme)
|
||||
.with_prompt("index.html already exists. Do you want to overwrite it?")
|
||||
.interact()
|
||||
.unwrap();
|
||||
if should_overwrite {
|
||||
std::fs::write(&index, static_html.as_str())
|
||||
.with_context(|| "Could not write index.html file".to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
let raw_static_site_toml = format!(
|
||||
r#"
|
||||
[dependencies]
|
||||
"{}" = "{}"
|
||||
|
||||
[fs]
|
||||
public = "{}"
|
||||
"#,
|
||||
WASM_STATIC_SERVER_PACKAGE, WASM_STATIC_SERVER_VERSION, pubdir_name
|
||||
);
|
||||
|
||||
let manifest = wasmer_config::package::Manifest::parse(raw_static_site_toml.as_str())
|
||||
.map_err(|e| anyhow::anyhow!("Could not parse js worker manifest: {}", e))?;
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
fn initialize_js_worker(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
let srcdir_name = "src";
|
||||
let srcdir = path.join(srcdir_name);
|
||||
if !srcdir.is_dir() {
|
||||
std::fs::create_dir_all(&srcdir)
|
||||
.with_context(|| format!("Failed to create directory: '{}'", srcdir.display()))?;
|
||||
}
|
||||
|
||||
let index_js = srcdir.join("index.js");
|
||||
|
||||
let sample_js = SAMPLE_JS_WORKER.replace("{{package}}", "My JS worker");
|
||||
|
||||
if !index_js.is_file() {
|
||||
std::fs::write(&index_js, sample_js.as_str())
|
||||
.with_context(|| "Could not write index.js file".to_string())?;
|
||||
}
|
||||
|
||||
// get the remote repository if it exists
|
||||
// Todo: add this to the manifest
|
||||
// let remote_repo_url = Command::new("git")
|
||||
// .arg("remote")
|
||||
// .arg("get-url")
|
||||
// .arg("origin")
|
||||
// .output()
|
||||
// .map_or("".to_string(), |f| String::from_utf8(f.stdout).unwrap());
|
||||
|
||||
let raw_js_worker_toml = format!(
|
||||
r#"
|
||||
[dependencies]
|
||||
"{winterjs_pkg}" = "{winterjs_version}"
|
||||
|
||||
[fs]
|
||||
"/src" = "./src"
|
||||
|
||||
[[command]]
|
||||
name = "script"
|
||||
module = "{winterjs_pkg}:winterjs"
|
||||
runner = "https://webc.org/runner/wasi"
|
||||
|
||||
[command.annotations.wasi]
|
||||
main-args = ["/src/index.js"]
|
||||
env = ["JS_PATH=/src/index.js"]
|
||||
"#,
|
||||
winterjs_pkg = WASMER_WINTER_JS_PACKAGE,
|
||||
winterjs_version = WASMER_WINTER_JS_VERSION,
|
||||
);
|
||||
|
||||
let manifest = wasmer_config::package::Manifest::parse(raw_js_worker_toml.as_str())
|
||||
.map_err(|e| anyhow::anyhow!("Could not parse js worker manifest: {}", e))?;
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
fn initialize_py_worker(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
let appdir_name = "src";
|
||||
let appdir = path.join(appdir_name);
|
||||
if !appdir.is_dir() {
|
||||
std::fs::create_dir_all(&appdir)
|
||||
.with_context(|| format!("Failed to create directory: '{}'", appdir.display()))?;
|
||||
}
|
||||
let main_py = appdir.join("main.py");
|
||||
|
||||
let sample_main = SAMPLE_PY_APPLICATION.replace("{{package}}", "My Python Worker");
|
||||
|
||||
if !main_py.is_file() {
|
||||
std::fs::write(&main_py, sample_main.as_str())
|
||||
.with_context(|| "Could not write main.py file".to_string())?;
|
||||
}
|
||||
|
||||
// Todo: add this to the manifest
|
||||
// let remote_repo_url = Command::new("git")
|
||||
// .arg("remote")
|
||||
// .arg("get-url")
|
||||
// .arg("origin")
|
||||
// .output()
|
||||
// .map_or("".to_string(), |f| String::from_utf8(f.stdout).unwrap());
|
||||
|
||||
let raw_py_worker_toml = format!(
|
||||
r#"
|
||||
[dependencies]
|
||||
"{}" = "{}"
|
||||
|
||||
[fs]
|
||||
"/src" = "./src"
|
||||
# "/.env" = "./.env/" # Bundle the virtualenv
|
||||
|
||||
[[command]]
|
||||
name = "script"
|
||||
module = "{}:python" # The "python" atom from "wasmer/python"
|
||||
runner = "wasi"
|
||||
|
||||
[command.annotations.wasi]
|
||||
main-args = ["/src/main.py"]
|
||||
# env = ["PYTHON_PATH=/app/.env:/etc/python3.12/site-packages"] # Make our virtualenv accessible
|
||||
"#,
|
||||
WASM_PYTHON_PACKAGE, WASM_PYTHON_VERSION, WASM_PYTHON_PACKAGE
|
||||
);
|
||||
|
||||
let manifest = wasmer_config::package::Manifest::parse(raw_py_worker_toml.as_str())
|
||||
.map_err(|e| anyhow::anyhow!("Could not parse py worker manifest: {}", e))?;
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_package_wizard_create_static_site() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
PackageWizard {
|
||||
path: dir.path().to_owned(),
|
||||
type_: Some(PackageType::StaticWebsite),
|
||||
create_mode: CreateMode::Create,
|
||||
namespace: None,
|
||||
namespace_default: None,
|
||||
name: None,
|
||||
user: None,
|
||||
}
|
||||
.run(None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
manifest,
|
||||
r#"[dependencies]
|
||||
"wasmer/static-web-server" = "^1"
|
||||
|
||||
[fs]
|
||||
public = "public"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert!(dir.path().join("public").join("index.html").is_file());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_package_wizard_create_js_worker() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
PackageWizard {
|
||||
path: dir.path().to_owned(),
|
||||
type_: Some(PackageType::JsWorker),
|
||||
create_mode: CreateMode::Create,
|
||||
namespace: None,
|
||||
namespace_default: None,
|
||||
name: None,
|
||||
user: None,
|
||||
}
|
||||
.run(None)
|
||||
.await
|
||||
.unwrap();
|
||||
let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
manifest,
|
||||
r#"[dependencies]
|
||||
"wasmer/winterjs" = "*"
|
||||
|
||||
[fs]
|
||||
"/src" = "./src"
|
||||
|
||||
[[command]]
|
||||
name = "script"
|
||||
module = "wasmer/winterjs:winterjs"
|
||||
runner = "https://webc.org/runner/wasi"
|
||||
|
||||
[command.annotations.wasi]
|
||||
env = ["JS_PATH=/src/index.js"]
|
||||
main-args = ["/src/index.js"]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert!(dir.path().join("src").join("index.js").is_file());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_package_wizard_create_py_worker() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
PackageWizard {
|
||||
path: dir.path().to_owned(),
|
||||
type_: Some(PackageType::PyApplication),
|
||||
create_mode: CreateMode::Create,
|
||||
namespace: None,
|
||||
namespace_default: None,
|
||||
name: None,
|
||||
user: None,
|
||||
}
|
||||
.run(None)
|
||||
.await
|
||||
.unwrap();
|
||||
let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
manifest,
|
||||
r#"[dependencies]
|
||||
"wasmer/python" = "^3.12.6"
|
||||
|
||||
[fs]
|
||||
"/src" = "./src"
|
||||
|
||||
[[command]]
|
||||
name = "script"
|
||||
module = "wasmer/python:python"
|
||||
runner = "wasi"
|
||||
|
||||
[command.annotations.wasi]
|
||||
main-args = ["/src/main.py"]
|
||||
"#,
|
||||
);
|
||||
|
||||
assert!(dir.path().join("src").join("main.py").is_file());
|
||||
}
|
||||
}
|
||||
// use std::path::{Path, PathBuf};
|
||||
//
|
||||
// use anyhow::Context;
|
||||
// use dialoguer::{theme::ColorfulTheme, Select};
|
||||
// use wasmer_api::{types::UserWithNamespaces, WasmerClient};
|
||||
//
|
||||
// use super::prompts::PackageCheckMode;
|
||||
//
|
||||
// const WASM_STATIC_SERVER_PACKAGE: &str = "wasmer/static-web-server";
|
||||
// const WASM_STATIC_SERVER_VERSION: &str = "1";
|
||||
//
|
||||
// const WASMER_WINTER_JS_PACKAGE: &str = "wasmer/winterjs";
|
||||
// const WASMER_WINTER_JS_VERSION: &str = "*";
|
||||
//
|
||||
// const WASM_PYTHON_PACKAGE: &str = "wasmer/python";
|
||||
// const WASM_PYTHON_VERSION: &str = "3.12.6";
|
||||
//
|
||||
// const SAMPLE_INDEX_HTML: &str = include_str!("./templates/static-site/index.html");
|
||||
// const SAMPLE_JS_WORKER: &str = include_str!("./templates/js-worker/index.js");
|
||||
// const SAMPLE_PY_APPLICATION: &str = include_str!("./templates/py-application/main.py");
|
||||
//
|
||||
// #[derive(clap::ValueEnum, Clone, Copy, Debug)]
|
||||
// pub enum PackageType {
|
||||
// #[clap(name = "regular")]
|
||||
// Regular,
|
||||
// /// A static website.
|
||||
// #[clap(name = "static-website")]
|
||||
// StaticWebsite,
|
||||
// /// A js-worker
|
||||
// #[clap(name = "js-worker")]
|
||||
// JsWorker,
|
||||
// /// A py-worker
|
||||
// #[clap(name = "py-application")]
|
||||
// PyApplication,
|
||||
// }
|
||||
//
|
||||
// #[derive(Clone, Copy, Debug)]
|
||||
// pub enum CreateMode {
|
||||
// Create,
|
||||
// SelectExisting,
|
||||
// #[allow(dead_code)]
|
||||
// CreateOrSelect,
|
||||
// }
|
||||
//
|
||||
// fn prompt_for_package_type() -> Result<PackageType, anyhow::Error> {
|
||||
// let theme = ColorfulTheme::default();
|
||||
// Select::with_theme(&theme)
|
||||
// .with_prompt("What type of package do you want to create?")
|
||||
// .items(&["Basic pacakge", "Static website"])
|
||||
// .interact()
|
||||
// .map(|idx| match idx {
|
||||
// 0 => PackageType::Regular,
|
||||
// 1 => PackageType::StaticWebsite,
|
||||
// _ => unreachable!(),
|
||||
// })
|
||||
// .map_err(anyhow::Error::from)
|
||||
// }
|
||||
//
|
||||
// #[derive(Debug)]
|
||||
// pub struct PackageWizard {
|
||||
// pub path: PathBuf,
|
||||
// pub type_: Option<PackageType>,
|
||||
//
|
||||
// pub create_mode: CreateMode,
|
||||
//
|
||||
// /// Namespace to use.
|
||||
// pub namespace: Option<String>,
|
||||
// /// Default namespace to use.
|
||||
// /// Will still show a prompt, with this as the default value.
|
||||
// /// Ignored if [`Self::namespace`] is set.
|
||||
// pub namespace_default: Option<String>,
|
||||
//
|
||||
// /// Pre-configured package name.
|
||||
// pub name: Option<String>,
|
||||
//
|
||||
// pub user: Option<UserWithNamespaces>,
|
||||
// }
|
||||
//
|
||||
// pub struct PackageWizardOutput {
|
||||
// pub api: Option<wasmer_api::types::Package>,
|
||||
// pub local_path: Option<PathBuf>,
|
||||
// pub local_manifest: Option<wasmer_config::package::Manifest>,
|
||||
// }
|
||||
//
|
||||
// impl PackageWizard {
|
||||
// fn build_new_package(&self) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
// let ty = match self.type_ {
|
||||
// Some(t) => t,
|
||||
// None => prompt_for_package_type()?,
|
||||
// };
|
||||
//
|
||||
// if !self.path.is_dir() {
|
||||
// std::fs::create_dir_all(&self.path).with_context(|| {
|
||||
// format!("Failed to create directory: '{}'", self.path.display())
|
||||
// })?;
|
||||
// }
|
||||
//
|
||||
// let manifest = match ty {
|
||||
// PackageType::Regular => todo!(),
|
||||
// PackageType::StaticWebsite => initialize_static_site(&self.path)?,
|
||||
// PackageType::JsWorker => initialize_js_worker(&self.path)?,
|
||||
// PackageType::PyApplication => initialize_py_worker(&self.path)?,
|
||||
// };
|
||||
//
|
||||
// let manifest_path = self.path.join("wasmer.toml");
|
||||
// let manifest_raw = manifest
|
||||
// .to_string()
|
||||
// .context("could not serialize package manifest")?;
|
||||
// std::fs::write(manifest_path, manifest_raw)
|
||||
// .with_context(|| format!("Failed to write manifest to '{}'", self.path.display()))?;
|
||||
//
|
||||
// Ok(PackageWizardOutput {
|
||||
// api: None,
|
||||
// local_path: Some(self.path.clone()),
|
||||
// local_manifest: Some(manifest),
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// async fn prompt_existing_package(
|
||||
// &self,
|
||||
// api: Option<&WasmerClient>,
|
||||
// ) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
// // Existing package
|
||||
// let check = if api.is_some() {
|
||||
// Some(PackageCheckMode::MustExist)
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
//
|
||||
// eprintln!("Enter the name of an existing package:");
|
||||
// let (_ident, api) = super::prompts::prompt_for_package("Package", None, check, api).await?;
|
||||
// Ok(PackageWizardOutput {
|
||||
// api,
|
||||
// local_path: None,
|
||||
// local_manifest: None,
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// pub async fn run(
|
||||
// self,
|
||||
// api: Option<&WasmerClient>,
|
||||
// ) -> Result<PackageWizardOutput, anyhow::Error> {
|
||||
// match self.create_mode {
|
||||
// CreateMode::Create => self.build_new_package(),
|
||||
// CreateMode::SelectExisting => self.prompt_existing_package(api).await,
|
||||
// CreateMode::CreateOrSelect => {
|
||||
// let theme = ColorfulTheme::default();
|
||||
// let index = Select::with_theme(&theme)
|
||||
// .with_prompt("What package do you want to use?")
|
||||
// .items(&["Create new package", "Use existing package"])
|
||||
// .default(0)
|
||||
// .interact()?;
|
||||
//
|
||||
// match index {
|
||||
// 0 => self.build_new_package(),
|
||||
// 1 => self.prompt_existing_package(api).await,
|
||||
// other => {
|
||||
// unreachable!("Unexpected index: {other}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn initialize_static_site(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
// let pubdir_name = "public";
|
||||
// let pubdir = path.join(pubdir_name);
|
||||
// if !pubdir.is_dir() {
|
||||
// std::fs::create_dir_all(&pubdir)
|
||||
// .with_context(|| format!("Failed to create directory: '{}'", pubdir.display()))?;
|
||||
// }
|
||||
// let index = pubdir.join("index.html");
|
||||
//
|
||||
// let static_html = SAMPLE_INDEX_HTML.replace("{{title}}", "My static website");
|
||||
//
|
||||
// if !index.is_file() {
|
||||
// std::fs::write(&index, static_html.as_str())
|
||||
// .with_context(|| "Could not write index.html file".to_string())?;
|
||||
// } else {
|
||||
// // The index.js file already exists, so we can ask the user if they want to overwrite it
|
||||
// let theme = dialoguer::theme::ColorfulTheme::default();
|
||||
// let should_overwrite = dialoguer::Confirm::with_theme(&theme)
|
||||
// .with_prompt("index.html already exists. Do you want to overwrite it?")
|
||||
// .interact()
|
||||
// .unwrap();
|
||||
// if should_overwrite {
|
||||
// std::fs::write(&index, static_html.as_str())
|
||||
// .with_context(|| "Could not write index.html file".to_string())?;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let raw_static_site_toml = format!(
|
||||
// r#"
|
||||
// [dependencies]
|
||||
// "{}" = "{}"
|
||||
//
|
||||
// [fs]
|
||||
// public = "{}"
|
||||
// "#,
|
||||
// WASM_STATIC_SERVER_PACKAGE, WASM_STATIC_SERVER_VERSION, pubdir_name
|
||||
// );
|
||||
//
|
||||
// let manifest = wasmer_config::package::Manifest::parse(raw_static_site_toml.as_str())
|
||||
// .map_err(|e| anyhow::anyhow!("Could not parse js worker manifest: {}", e))?;
|
||||
//
|
||||
// Ok(manifest)
|
||||
// }
|
||||
//
|
||||
// fn initialize_js_worker(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
// let srcdir_name = "src";
|
||||
// let srcdir = path.join(srcdir_name);
|
||||
// if !srcdir.is_dir() {
|
||||
// std::fs::create_dir_all(&srcdir)
|
||||
// .with_context(|| format!("Failed to create directory: '{}'", srcdir.display()))?;
|
||||
// }
|
||||
//
|
||||
// let index_js = srcdir.join("index.js");
|
||||
//
|
||||
// let sample_js = SAMPLE_JS_WORKER.replace("{{package}}", "My JS worker");
|
||||
//
|
||||
// if !index_js.is_file() {
|
||||
// std::fs::write(&index_js, sample_js.as_str())
|
||||
// .with_context(|| "Could not write index.js file".to_string())?;
|
||||
// }
|
||||
//
|
||||
// // get the remote repository if it exists
|
||||
// // Todo: add this to the manifest
|
||||
// // let remote_repo_url = Command::new("git")
|
||||
// // .arg("remote")
|
||||
// // .arg("get-url")
|
||||
// // .arg("origin")
|
||||
// // .output()
|
||||
// // .map_or("".to_string(), |f| String::from_utf8(f.stdout).unwrap());
|
||||
//
|
||||
// let raw_js_worker_toml = format!(
|
||||
// r#"
|
||||
// [dependencies]
|
||||
// "{winterjs_pkg}" = "{winterjs_version}"
|
||||
//
|
||||
// [fs]
|
||||
// "/src" = "./src"
|
||||
//
|
||||
// [[command]]
|
||||
// name = "script"
|
||||
// module = "{winterjs_pkg}:winterjs"
|
||||
// runner = "https://webc.org/runner/wasi"
|
||||
//
|
||||
// [command.annotations.wasi]
|
||||
// main-args = ["/src/index.js"]
|
||||
// env = ["JS_PATH=/src/index.js"]
|
||||
// "#,
|
||||
// winterjs_pkg = WASMER_WINTER_JS_PACKAGE,
|
||||
// winterjs_version = WASMER_WINTER_JS_VERSION,
|
||||
// );
|
||||
//
|
||||
// let manifest = wasmer_config::package::Manifest::parse(raw_js_worker_toml.as_str())
|
||||
// .map_err(|e| anyhow::anyhow!("Could not parse js worker manifest: {}", e))?;
|
||||
//
|
||||
// Ok(manifest)
|
||||
// }
|
||||
//
|
||||
// fn initialize_py_worker(path: &Path) -> Result<wasmer_config::package::Manifest, anyhow::Error> {
|
||||
// let appdir_name = "src";
|
||||
// let appdir = path.join(appdir_name);
|
||||
// if !appdir.is_dir() {
|
||||
// std::fs::create_dir_all(&appdir)
|
||||
// .with_context(|| format!("Failed to create directory: '{}'", appdir.display()))?;
|
||||
// }
|
||||
// let main_py = appdir.join("main.py");
|
||||
//
|
||||
// let sample_main = SAMPLE_PY_APPLICATION.replace("{{package}}", "My Python Worker");
|
||||
//
|
||||
// if !main_py.is_file() {
|
||||
// std::fs::write(&main_py, sample_main.as_str())
|
||||
// .with_context(|| "Could not write main.py file".to_string())?;
|
||||
// }
|
||||
//
|
||||
// // Todo: add this to the manifest
|
||||
// // let remote_repo_url = Command::new("git")
|
||||
// // .arg("remote")
|
||||
// // .arg("get-url")
|
||||
// // .arg("origin")
|
||||
// // .output()
|
||||
// // .map_or("".to_string(), |f| String::from_utf8(f.stdout).unwrap());
|
||||
//
|
||||
// let raw_py_worker_toml = format!(
|
||||
// r#"
|
||||
// [dependencies]
|
||||
// "{}" = "{}"
|
||||
//
|
||||
// [fs]
|
||||
// "/src" = "./src"
|
||||
// # "/.env" = "./.env/" # Bundle the virtualenv
|
||||
//
|
||||
// [[command]]
|
||||
// name = "script"
|
||||
// module = "{}:python" # The "python" atom from "wasmer/python"
|
||||
// runner = "wasi"
|
||||
//
|
||||
// [command.annotations.wasi]
|
||||
// main-args = ["/src/main.py"]
|
||||
// # env = ["PYTHON_PATH=/app/.env:/etc/python3.12/site-packages"] # Make our virtualenv accessible
|
||||
// "#,
|
||||
// WASM_PYTHON_PACKAGE, WASM_PYTHON_VERSION, WASM_PYTHON_PACKAGE
|
||||
// );
|
||||
//
|
||||
// let manifest = wasmer_config::package::Manifest::parse(raw_py_worker_toml.as_str())
|
||||
// .map_err(|e| anyhow::anyhow!("Could not parse py worker manifest: {}", e))?;
|
||||
//
|
||||
// Ok(manifest)
|
||||
// }
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn test_package_wizard_create_static_site() {
|
||||
// let dir = tempfile::tempdir().unwrap();
|
||||
//
|
||||
// PackageWizard {
|
||||
// path: dir.path().to_owned(),
|
||||
// type_: Some(PackageType::StaticWebsite),
|
||||
// create_mode: CreateMode::Create,
|
||||
// namespace: None,
|
||||
// namespace_default: None,
|
||||
// name: None,
|
||||
// user: None,
|
||||
// }
|
||||
// .run(None)
|
||||
// .await
|
||||
// .unwrap();
|
||||
//
|
||||
// let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
// pretty_assertions::assert_eq!(
|
||||
// manifest,
|
||||
// r#"[dependencies]
|
||||
// "wasmer/static-web-server" = "^1"
|
||||
//
|
||||
// [fs]
|
||||
// public = "public"
|
||||
// "#,
|
||||
// );
|
||||
//
|
||||
// assert!(dir.path().join("public").join("index.html").is_file());
|
||||
// }
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn test_package_wizard_create_js_worker() {
|
||||
// let dir = tempfile::tempdir().unwrap();
|
||||
//
|
||||
// PackageWizard {
|
||||
// path: dir.path().to_owned(),
|
||||
// type_: Some(PackageType::JsWorker),
|
||||
// create_mode: CreateMode::Create,
|
||||
// namespace: None,
|
||||
// namespace_default: None,
|
||||
// name: None,
|
||||
// user: None,
|
||||
// }
|
||||
// .run(None)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
//
|
||||
// pretty_assertions::assert_eq!(
|
||||
// manifest,
|
||||
// r#"[dependencies]
|
||||
// "wasmer/winterjs" = "*"
|
||||
//
|
||||
// [fs]
|
||||
// "/src" = "./src"
|
||||
//
|
||||
// [[command]]
|
||||
// name = "script"
|
||||
// module = "wasmer/winterjs:winterjs"
|
||||
// runner = "https://webc.org/runner/wasi"
|
||||
//
|
||||
// [command.annotations.wasi]
|
||||
// env = ["JS_PATH=/src/index.js"]
|
||||
// main-args = ["/src/index.js"]
|
||||
// "#,
|
||||
// );
|
||||
//
|
||||
// assert!(dir.path().join("src").join("index.js").is_file());
|
||||
// }
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn test_package_wizard_create_py_worker() {
|
||||
// let dir = tempfile::tempdir().unwrap();
|
||||
//
|
||||
// PackageWizard {
|
||||
// path: dir.path().to_owned(),
|
||||
// type_: Some(PackageType::PyApplication),
|
||||
// create_mode: CreateMode::Create,
|
||||
// namespace: None,
|
||||
// namespace_default: None,
|
||||
// name: None,
|
||||
// user: None,
|
||||
// }
|
||||
// .run(None)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
|
||||
//
|
||||
// pretty_assertions::assert_eq!(
|
||||
// manifest,
|
||||
// r#"[dependencies]
|
||||
// "wasmer/python" = "^3.12.6"
|
||||
//
|
||||
// [fs]
|
||||
// "/src" = "./src"
|
||||
//
|
||||
// [[command]]
|
||||
// name = "script"
|
||||
// module = "wasmer/python:python"
|
||||
// runner = "wasi"
|
||||
//
|
||||
// [command.annotations.wasi]
|
||||
// main-args = ["/src/main.py"]
|
||||
// "#,
|
||||
// );
|
||||
//
|
||||
// assert!(dir.path().join("src").join("main.py").is_file());
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -2,7 +2,6 @@ use anyhow::Context;
|
||||
use colored::Colorize;
|
||||
use dialoguer::{theme::ColorfulTheme, Select};
|
||||
use wasmer_api::WasmerClient;
|
||||
use wasmer_config::package::NamedPackageIdent;
|
||||
|
||||
pub fn prompt_for_ident(message: &str, default: Option<&str>) -> Result<String, anyhow::Error> {
|
||||
loop {
|
||||
@@ -23,38 +22,38 @@ pub fn prompt_for_ident(message: &str, default: Option<&str>) -> Result<String,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask a user for a package name.
|
||||
///
|
||||
/// Will continue looping until the user provides a valid name.
|
||||
pub fn prompt_for_package_ident(
|
||||
message: &str,
|
||||
default: Option<&str>,
|
||||
) -> Result<NamedPackageIdent, anyhow::Error> {
|
||||
loop {
|
||||
let theme = ColorfulTheme::default();
|
||||
let raw: String = dialoguer::Input::with_theme(&theme)
|
||||
.with_prompt(message)
|
||||
.with_initial_text(default.unwrap_or_default())
|
||||
.interact_text()
|
||||
.context("could not read user input")?;
|
||||
// /// Ask a user for a package name.
|
||||
// ///
|
||||
// /// Will continue looping until the user provides a valid name.
|
||||
// pub fn prompt_for_package_ident(
|
||||
// message: &str,
|
||||
// default: Option<&str>,
|
||||
// ) -> Result<NamedPackageIdent, anyhow::Error> {
|
||||
// loop {
|
||||
// let theme = ColorfulTheme::default();
|
||||
// let raw: String = dialoguer::Input::with_theme(&theme)
|
||||
// .with_prompt(message)
|
||||
// .with_initial_text(default.unwrap_or_default())
|
||||
// .interact_text()
|
||||
// .context("could not read user input")?;
|
||||
//
|
||||
// match raw.parse::<NamedPackageIdent>() {
|
||||
// Ok(p) => break Ok(p),
|
||||
// Err(err) => {
|
||||
// eprintln!("invalid package name: {err}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
match raw.parse::<NamedPackageIdent>() {
|
||||
Ok(p) => break Ok(p),
|
||||
Err(err) => {
|
||||
eprintln!("invalid package name: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines how to check for a package.
|
||||
pub enum PackageCheckMode {
|
||||
/// The package must exist in the registry.
|
||||
MustExist,
|
||||
/// The package must NOT exist in the registry.
|
||||
#[allow(dead_code)]
|
||||
MustNotExist,
|
||||
}
|
||||
// /// Defines how to check for a package.
|
||||
// pub enum PackageCheckMode {
|
||||
// /// The package must exist in the registry.
|
||||
// MustExist,
|
||||
// /// The package must NOT exist in the registry.
|
||||
// #[allow(dead_code)]
|
||||
// MustNotExist,
|
||||
// }
|
||||
|
||||
/// Ask a user for a package version.
|
||||
///
|
||||
@@ -80,51 +79,51 @@ pub fn prompt_for_package_version(
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask for a package name.
|
||||
///
|
||||
/// Will continue looping until the user provides a valid name.
|
||||
///
|
||||
/// If an API is provided, will check if the package exists.
|
||||
pub async fn prompt_for_package(
|
||||
message: &str,
|
||||
default: Option<&str>,
|
||||
check: Option<PackageCheckMode>,
|
||||
client: Option<&WasmerClient>,
|
||||
) -> Result<(NamedPackageIdent, Option<wasmer_api::types::Package>), anyhow::Error> {
|
||||
loop {
|
||||
let ident = prompt_for_package_ident(message, default)?;
|
||||
|
||||
if let Some(check) = &check {
|
||||
let api = client.expect("Check mode specified, but no API provided");
|
||||
|
||||
let pkg = wasmer_api::query::get_package(api, ident.to_string())
|
||||
.await
|
||||
.context("could not query backend for package")?;
|
||||
|
||||
match check {
|
||||
PackageCheckMode::MustExist => {
|
||||
if let Some(pkg) = pkg {
|
||||
let mut ident = ident;
|
||||
if let Some(v) = &pkg.last_version {
|
||||
ident.tag =
|
||||
Some(wasmer_config::package::Tag::VersionReq(v.version.parse()?));
|
||||
}
|
||||
break Ok((ident, Some(pkg)));
|
||||
} else {
|
||||
eprintln!("Package '{ident}' does not exist");
|
||||
}
|
||||
}
|
||||
PackageCheckMode::MustNotExist => {
|
||||
if pkg.is_none() {
|
||||
break Ok((ident, None));
|
||||
} else {
|
||||
eprintln!("Package '{ident}' already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// /// Ask for a package name.
|
||||
// ///
|
||||
// /// Will continue looping until the user provides a valid name.
|
||||
// ///
|
||||
// /// If an API is provided, will check if the package exists.
|
||||
// pub async fn prompt_for_package(
|
||||
// message: &str,
|
||||
// default: Option<&str>,
|
||||
// check: Option<PackageCheckMode>,
|
||||
// client: Option<&WasmerClient>,
|
||||
// ) -> Result<(NamedPackageIdent, Option<wasmer_api::types::Package>), anyhow::Error> {
|
||||
// loop {
|
||||
// let ident = prompt_for_package_ident(message, default)?;
|
||||
//
|
||||
// if let Some(check) = &check {
|
||||
// let api = client.expect("Check mode specified, but no API provided");
|
||||
//
|
||||
// let pkg = wasmer_api::query::get_package(api, ident.to_string())
|
||||
// .await
|
||||
// .context("could not query backend for package")?;
|
||||
//
|
||||
// match check {
|
||||
// PackageCheckMode::MustExist => {
|
||||
// if let Some(pkg) = pkg {
|
||||
// let mut ident = ident;
|
||||
// if let Some(v) = &pkg.last_version {
|
||||
// ident.tag =
|
||||
// Some(wasmer_config::package::Tag::VersionReq(v.version.parse()?));
|
||||
// }
|
||||
// break Ok((ident, Some(pkg)));
|
||||
// } else {
|
||||
// eprintln!("Package '{ident}' does not exist");
|
||||
// }
|
||||
// }
|
||||
// PackageCheckMode::MustNotExist => {
|
||||
// if pkg.is_none() {
|
||||
// break Ok((ident, None));
|
||||
// } else {
|
||||
// eprintln!("Package '{ident}' already exists");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Prompt for a namespace.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user