diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4917c018..8f467d7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ on: push: branches: - main - tags: ['[0-9]*'] + tags: ["[0-9]*"] pull_request: branches: - main @@ -16,14 +16,14 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: | - rustup update stable --no-self-update - rustup default stable - shell: bash - - name: Run all tests - run: cargo test --all + - uses: actions/checkout@v3 + - name: Install Rust + run: | + rustup update stable --no-self-update + rustup default stable + shell: bash + - name: Run all tests + run: cargo test --all check: name: Check feature combinations @@ -32,29 +32,27 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: | - rustup update stable --no-self-update - rustup default stable - shell: bash - - name: Build without default features - run: cargo check --no-default-features - - name: Build the `wat` feature - run: cargo check --no-default-features --features wat - - name: Build the `wit` feature - run: cargo check --no-default-features --features wit - - name: Build the `registry` feature - run: cargo check --no-default-features --features registry - - name: Build all features - run: cargo check --all-features + - uses: actions/checkout@v3 + - name: Install Rust + run: | + rustup update stable --no-self-update + rustup default stable + shell: bash + - name: Build without default features + run: cargo check --no-default-features + - name: Build the `wat` feature + run: cargo check --no-default-features --features wat + - name: Build the `wit` feature + run: cargo check --no-default-features --features wit + - name: Build all features + run: cargo check --all-features rustfmt: name: Format source code runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update stable && rustup default stable && rustup component add rustfmt - - name: Run `cargo fmt` - run: cargo fmt -- --check + - uses: actions/checkout@v3 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - name: Run `cargo fmt` + run: cargo fmt -- --check diff --git a/Cargo.lock b/Cargo.lock index 3e12d94f..31809758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,6 +775,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" @@ -1428,7 +1441,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -3036,9 +3049,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -3174,6 +3187,12 @@ dependencies = [ "lazy_static 1.4.0", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3847,6 +3866,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "dialoguer", "indexmap 2.2.6", "indicatif", "log", @@ -3862,6 +3882,7 @@ dependencies = [ "wac-parser", "wac-resolver", "wac-types", + "warg-client", "wasmprinter 0.202.0", "wat", ] @@ -3929,6 +3950,7 @@ dependencies = [ "pretty_assertions", "secrecy", "semver", + "serde_json", "tempdir", "thiserror", "tokio", @@ -3987,9 +4009,8 @@ dependencies = [ [[package]] name = "warg-api" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bf1e22e1e396b98a2181219b06d1a49a3478c1b9d87a29cd9cd819d714e6c3" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "indexmap 2.2.6", "itertools 0.12.1", @@ -4002,9 +4023,8 @@ dependencies = [ [[package]] name = "warg-client" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56cfaf9781ca2d084468bbdd8bbc1e35947bb2a19f8d3940d899852f6dd78aa" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "async-recursion", @@ -4046,9 +4066,8 @@ dependencies = [ [[package]] name = "warg-credentials" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626224ba1a00965282b669d2611654fd6292a15396ed8c850ce91684678fe19f" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "indexmap 2.2.6", @@ -4060,9 +4079,8 @@ dependencies = [ [[package]] name = "warg-crypto" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a8c47e96a7f1903931b34db9a1f0d22bcb3761a203ee6861db686daaedcb4b" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "base64", @@ -4081,9 +4099,8 @@ dependencies = [ [[package]] name = "warg-protobuf" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed0e698efd0fab8bb747efd452156a65149eb389f7fe2a6b6b3ced4e25ab24" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "pbjson", @@ -4100,9 +4117,8 @@ dependencies = [ [[package]] name = "warg-protocol" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69be98a2e9e0aeace7cbd62184b11462d259c5e391e6208d59506c9a2d33571c" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "base64", @@ -4123,9 +4139,8 @@ dependencies = [ [[package]] name = "warg-server" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f15457ced83df5c2298f225fc83b6700e93c7bf320a2e4ef01114c0b34d7ce" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "axum", @@ -4154,9 +4169,8 @@ dependencies = [ [[package]] name = "warg-transparency" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d272b3002b9e5f6f636817089ba091e1ba7b85858e72529f96e24bc9827f530" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "indexmap 2.2.6", diff --git a/Cargo.toml b/Cargo.toml index cafdad15..47fca7ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,13 +37,14 @@ thiserror = { workspace = true } indexmap = { workspace = true } miette = { workspace = true, features = ["fancy"] } semver = { workspace = true } -indicatif = { workspace = true, optional = true } +indicatif = { workspace = true } +warg-client = { workspace = true } +dialoguer = "0.11.0" [features] default = ["wit"] wat = ["wac-resolver/wat"] wit = ["wac-resolver/wit"] -registry = ["wac-resolver/registry", "indicatif"] [workspace.dependencies] wac-parser = { path = "crates/wac-parser", version = "0.1.0", default-features = false } @@ -71,11 +72,11 @@ wat = "1.202.0" logos = "0.14.0" miette = "7.2.0" thiserror = "1.0.58" -warg-client = "0.4.1" -warg-protocol = "0.4.1" -warg-crypto = "0.4.1" -warg-server = "0.4.1" -warg-credentials = "0.4.1" +warg-protocol = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-crypto = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-client = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-credentials = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-server = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } secrecy = "0.8.0" futures = "0.3.30" indicatif = "0.17.8" diff --git a/crates/wac-resolver/Cargo.toml b/crates/wac-resolver/Cargo.toml index 7ff8349e..0c816a56 100644 --- a/crates/wac-resolver/Cargo.toml +++ b/crates/wac-resolver/Cargo.toml @@ -21,13 +21,14 @@ indexmap = { workspace = true } wat = { workspace = true, optional = true } wit-parser = { workspace = true, optional = true } miette = { workspace = true } -warg-client = { workspace = true, optional = true } -warg-protocol = { workspace = true, optional = true } -warg-crypto = { workspace = true, optional = true } -warg-credentials = { workspace = true, optional = true } -secrecy = { workspace = true, optional = true } -tokio = { workspace = true, optional = true } -futures = { workspace = true, optional = true } +warg-client = { workspace = true } +warg-protocol = { workspace = true } +warg-crypto = { workspace = true } +warg-credentials = { workspace = true } +secrecy = { workspace = true } +tokio = { workspace = true } +futures = { workspace = true } +serde_json = "1.0.116" [dev-dependencies] wac-graph = { workspace = true } @@ -38,7 +39,7 @@ tokio-util = "0.7.10" tempdir = "0.3.7" [features] -default = ["registry"] +default = [] wat = ["dep:wat"] wit = ["wit-parser"] -registry = ["warg-client", "warg-protocol", "warg-crypto", "warg-credentials", "secrecy", "tokio", "futures"] + diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index 94763518..443b61eb 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -1,19 +1,20 @@ //! Modules for package resolvers. +use crate::Error as WacResolutionError; use indexmap::IndexMap; use miette::{Diagnostic, SourceSpan}; -use wac_parser::Document; +use thiserror::Error; +use wac_parser::{resolution::Error as WacError, Document}; mod fs; -#[cfg(feature = "registry")] mod registry; mod visitor; pub use fs::*; -#[cfg(feature = "registry")] pub use registry::*; pub use visitor::*; use wac_types::BorrowedPackageKey; +use warg_client::ClientError; /// Represents a package resolution error. #[derive(thiserror::Error, Diagnostic, Debug)] @@ -29,7 +30,6 @@ pub enum Error { span: SourceSpan, }, /// An unknown package version was encountered. - #[cfg(feature = "registry")] #[error("version {version} of package `{name}` does not exist")] UnknownPackageVersion { /// The name of the package. @@ -57,7 +57,6 @@ pub enum Error { span: SourceSpan, }, /// The requested package version has been yanked. - #[cfg(feature = "registry")] #[error("version {version} of package `{name}` has been yanked")] PackageVersionYanked { /// The name of the package. @@ -69,7 +68,6 @@ pub enum Error { span: SourceSpan, }, /// A package log was empty. - #[cfg(feature = "registry")] #[error("a release for package `{name}` has not yet been published")] PackageLogEmpty { /// The name of the package. @@ -79,7 +77,6 @@ pub enum Error { span: SourceSpan, }, /// A failure occurred while updating logs from the registry. - #[cfg(feature = "registry")] #[error("failed to update registry logs")] RegistryUpdateFailure { /// The underlying error. @@ -87,7 +84,6 @@ pub enum Error { source: anyhow::Error, }, /// A failure occurred while downloading content from the registry. - #[cfg(feature = "registry")] #[error("failed to download content from the registry")] RegistryDownloadFailure { /// The underlying error. @@ -95,7 +91,6 @@ pub enum Error { source: anyhow::Error, }, /// A failure occurred while reading content from registry storage. - #[cfg(feature = "registry")] #[error("failed to read content path `{path}`", path = .path.display())] RegistryContentFailure { /// The path to the content. @@ -149,3 +144,35 @@ pub fn packages<'a>( visitor.visit(document)?; Ok(keys) } + +#[derive(Debug, Error)] +/// Error for WAC commands +pub enum CommandError { + /// General errors + #[error(transparent)] + General(#[from] anyhow::Error), + /// Serde errors + #[error(transparent)] + Serde(#[from] serde_json::Error), + /// Wac resolution errors + #[error(transparent)] + WacResolution(#[from] WacResolutionError), + /// Wac errors + #[error(transparent)] + Wac(#[from] WacError), + /// Client Error + #[error("warg client error ({0}): {1}")] + WargClient(String, ClientError), + /// Client Error With Hint + #[error("warg client error with hint ({0}): {1}")] + WargHint(String, ClientError), +} + +/// Error from warg client +pub struct WargClientError(pub ClientError); + +impl std::fmt::Debug for WargClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("WargClientError").field(&self.0).finish() + } +} diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index 6bb20d8a..4fdb49ff 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -1,4 +1,4 @@ -use super::Error; +use super::{CommandError, Error}; use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; use indexmap::{IndexMap, IndexSet}; @@ -9,10 +9,11 @@ use std::{fs, path::Path, sync::Arc}; use wac_types::BorrowedPackageKey; use warg_client::{ storage::{ContentStorage, RegistryStorage}, - Client, ClientError, Config, FileSystemClient, RegistryUrl, + Client, ClientError, Config, FileSystemClient, RegistryUrl, Retry, }; use warg_credentials::keyring::get_auth_token; use warg_crypto::hash::AnyHash; +use warg_protocol::registry::PackageName; /// Implemented by progress bars. /// @@ -45,14 +46,23 @@ impl RegistryPackageResolver { /// client configuration file. /// /// If `url` is `None`, the default URL will be used. - pub fn new(url: Option<&str>, bar: Option>) -> Result { + /// + /// The `Retry` argument indicates that this was created after an interactive retry. + /// This occurs when the CLI attempted to leverage a package from the wrong registry. + /// The server responds with a hint as to which registry is used and the client maps that package namespace to the domain provided. + /// After this, the CLI command is retried + pub async fn new( + url: Option<&str>, + bar: Option>, + retry: Option, + ) -> Result { let config = Config::from_default_file()?.unwrap_or_default(); + let client = Client::new_with_config(url, &config, Self::auth_token(&config, url)?).await?; + if let Some(retry) = retry { + retry.store_namespace(&client).await?; + } Ok(Self { - client: Arc::new(Client::new_with_config( - url, - &config, - Self::auth_token(&config, url)?, - )?), + client: Arc::new(client), bar, }) } @@ -60,17 +70,15 @@ impl RegistryPackageResolver { /// Creates a new registry package resolver with the given configuration. /// /// If `url` is `None`, the default URL will be used. - pub fn new_with_config( + pub async fn new_with_config( url: Option<&str>, config: &Config, bar: Option>, ) -> Result { Ok(Self { - client: Arc::new(Client::new_with_config( - url, - config, - Self::auth_token(config, url)?, - )?), + client: Arc::new( + Client::new_with_config(url, config, Self::auth_token(config, url)?).await?, + ), bar, }) } @@ -81,7 +89,7 @@ impl RegistryPackageResolver { pub async fn resolve<'a>( &self, keys: &IndexMap, SourceSpan>, - ) -> Result, Vec>, Error> { + ) -> Result, Vec>, CommandError> { // Start by fetching any required package logs self.fetch(keys).await?; @@ -89,7 +97,6 @@ impl RegistryPackageResolver { // is missing from local storage. let mut packages = IndexMap::new(); let missing = self.find_missing_content(keys, &mut packages).await?; - if !missing.is_empty() { if let Some(bar) = self.bar.as_ref() { bar.init(missing.len()); @@ -101,7 +108,7 @@ impl RegistryPackageResolver { let client = self.client.clone(); let hash = hash.clone(); tasks.push(tokio::spawn(async move { - Ok((index, client.download_content(&hash).await?)) + Ok((index, client.download_content(None, &hash).await?)) })); } @@ -145,32 +152,33 @@ impl RegistryPackageResolver { async fn fetch<'a>( &self, keys: &IndexMap, SourceSpan>, - ) -> Result<(), Error> { + ) -> Result<(), CommandError> { // First check if we already have the packages in client storage. // If not, we'll fetch the logs from the registry. let mut fetch = IndexMap::new(); for (key, span) in keys { - let id = - key.name - .parse() - .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })?; + let id: PackageName = key.name.parse()?; // Load the package from client storage to see if we already // have a matching version present. if let Some(info) = self .client .registry() - .load_package(self.client.get_warg_registry(), &id) - .await - .map_err(|e| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })? + .load_package( + self.client + .get_warg_registry(id.namespace()) + .await + .map_err(|e| { + CommandError::WargClient( + "failed getting warg registry domain from package namespace" + .to_string(), + e, + ) + })? + .as_ref(), + &id, + ) + .await? { if let Some(version) = key.version { let req = VersionReq { @@ -217,15 +225,20 @@ impl RegistryPackageResolver { bar.finish(); } } - Err(ClientError::PackageDoesNotExist { name }) => { - return Err(Error::PackageDoesNotExist { - name: name.to_string(), - span: *fetch[&name], - }) - } - Err(e) => { - return Err(Error::RegistryUpdateFailure { source: e.into() }); - } + Err(e) => match &e { + ClientError::PackageDoesNotExistWithHint { .. } => { + return Err(CommandError::WargHint( + "failed updating warg logs".to_string(), + e, + )); + } + _ => { + return Err(CommandError::WargClient( + "failed updating warg logs".to_string(), + e, + )) + } + }, } } @@ -236,29 +249,30 @@ impl RegistryPackageResolver { &self, keys: &IndexMap, SourceSpan>, packages: &mut IndexMap, Vec>, - ) -> Result>)>, Error> { + ) -> Result>)>, CommandError> { let mut downloads: IndexMap)> = IndexMap::new(); for (key, span) in keys { - let id = - key.name - .parse() - .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })?; + let id: PackageName = key.name.parse()?; let info = self .client .registry() - .load_package(self.client.get_warg_registry(), &id) - .await - .map_err(|e| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })? + .load_package( + self.client + .get_warg_registry(id.namespace()) + .await + .map_err(|e| { + CommandError::WargClient( + "failed getting warg resistry domain from package namespace" + .to_string(), + e, + ) + })? + .as_ref(), + &id, + ) + .await? .expect("package log should be present after fetching"); let req = match key.version { @@ -277,24 +291,24 @@ impl RegistryPackageResolver { let release = match info.state.find_latest_release(&req) { Some(release) if !release.yanked() => release, Some(release) => { - return Err(Error::PackageVersionYanked { + return Err(CommandError::WacResolution(Error::PackageVersionYanked { name: key.name.to_string(), version: release.version.clone(), span: *span, - }); + })) } None => { if let Some(version) = key.version { - return Err(Error::UnknownPackageVersion { + return Err(CommandError::WacResolution(Error::UnknownPackageVersion { name: key.name.to_string(), version: version.clone(), span: *span, - }); + })); } else { - return Err(Error::PackageLogEmpty { + return Err(CommandError::WacResolution(Error::PackageLogEmpty { name: key.name.to_string(), span: *span, - }); + })); } } }; diff --git a/crates/wac-resolver/tests/registry.rs b/crates/wac-resolver/tests/registry.rs index 2b124a39..04a1c6d3 100644 --- a/crates/wac-resolver/tests/registry.rs +++ b/crates/wac-resolver/tests/registry.rs @@ -61,7 +61,7 @@ export i2.foo as "bar"; "#, )?; - let resolver = RegistryPackageResolver::new_with_config(None, &config, None)?; + let resolver = RegistryPackageResolver::new_with_config(None, &config, None).await?; let packages = resolver.resolve(&packages(&document)?).await?; let resolution = document.resolve(packages)?; diff --git a/crates/wac-resolver/tests/support/mod.rs b/crates/wac-resolver/tests/support/mod.rs index afe77c59..a6f79618 100644 --- a/crates/wac-resolver/tests/support/mod.rs +++ b/crates/wac-resolver/tests/support/mod.rs @@ -64,7 +64,7 @@ pub async fn publish( content: Vec, init: bool, ) -> Result<()> { - let client = FileSystemClient::new_with_config(None, config, None)?; + let client = FileSystemClient::new_with_config(None, config, None).await?; let digest = client .content() diff --git a/crates/wac-types/src/package.rs b/crates/wac-types/src/package.rs index cf249dd5..81a56c48 100644 --- a/crates/wac-types/src/package.rs +++ b/crates/wac-types/src/package.rs @@ -56,7 +56,7 @@ impl fmt::Display for PackageKey { } /// A borrowed package key. -#[derive(Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct BorrowedPackageKey<'a> { /// The package name. pub name: &'a str, diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 73abeaa2..0ace54b5 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -1,7 +1,10 @@ use anyhow::Result; use clap::Parser; +use dialoguer::{theme::ColorfulTheme, Confirm}; use owo_colors::{OwoColorize, Stream, Style}; use wac_cli::commands::{EncodeCommand, ParseCommand, ResolveCommand}; +use wac_resolver::CommandError; +use warg_client::{ClientError, Retry}; fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -25,20 +28,113 @@ enum Wac { #[tokio::main] async fn main() -> Result<()> { pretty_env_logger::init(); - - if let Err(e) = match Wac::parse() { + if let Err(err) = match Wac::parse() { Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => cmd.exec().await, - Wac::Encode(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => cmd.exec(None).await, + Wac::Encode(cmd) => cmd.exec(None).await, } { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } + if let CommandError::WargHint(_, ClientError::PackageDoesNotExistWithHint { name, hint }) = + &err + { + if let Some((namespace, registry)) = hint.to_str().unwrap().split_once('=') { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using. + However, the package namespace `{namespace}` does exist in the registry at {registry}. + Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name(), + ); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(err) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + } { + if let CommandError::WargHint( + _, + ClientError::PackageDoesNotExistWithHint { name, hint }, + ) = &err + { + if let Some((namespace, registry)) = + hint.to_str().unwrap().split_once('=') + { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using. + However, the package namespace `{namespace}` does exist in the registry at {registry}. + Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name(), + ); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + } { + eprintln!( + "{error}: {e:?}", + error = "error" + .if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } else { + return Ok(()); + } + } + } + } + eprintln!( + "{error}: {err:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + } + } + } else { + eprintln!( + "{error}: {err:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + } Ok(()) } diff --git a/src/commands/encode.rs b/src/commands/encode.rs index 8a157a5f..6a8b0c1c 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -1,5 +1,5 @@ use crate::{fmt_err, PackageResolver}; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Args; use std::{ fs, @@ -8,6 +8,8 @@ use std::{ }; use wac_graph::EncodeOptions; use wac_parser::Document; +use wac_resolver::CommandError; +use warg_client::Retry; use wasmprinter::print_bytes; fn parse(s: &str) -> Result<(T, U)> @@ -60,7 +62,6 @@ pub struct EncodeCommand { pub output: Option, /// The URL of the registry to use. - #[cfg(feature = "registry")] #[clap(long, value_name = "URL")] pub registry: Option, @@ -71,7 +72,7 @@ pub struct EncodeCommand { impl EncodeCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self, retry: Option) -> Result<(), CommandError> { log::debug!("executing encode command"); let contents = fs::read_to_string(&self.path) @@ -82,28 +83,30 @@ impl EncodeCommand { let resolver = PackageResolver::new( self.deps_dir, self.deps.into_iter().collect(), - #[cfg(feature = "registry")] self.registry.as_deref(), - )?; + retry, + ) + .await?; - let packages = resolver - .resolve(&document) - .await - .map_err(|e| fmt_err(e, &self.path, &contents))?; + let packages = resolver.resolve(&document).await?; let resolution = document .resolve(packages) .map_err(|e| fmt_err(e, &self.path, &contents))?; if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() { - bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead"); + return Err(anyhow!( + "cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead" + ))?; } - let mut bytes = resolution.encode(EncodeOptions { - define_components: !self.import_dependencies, - validate: !self.no_validate, - ..Default::default() - })?; + let mut bytes = resolution + .encode(EncodeOptions { + define_components: !self.import_dependencies, + validate: !self.no_validate, + ..Default::default() + }) + .map_err(CommandError::Wac)?; if self.wat { bytes = print_bytes(&bytes) .context("failed to convert binary wasm output to text")? diff --git a/src/commands/parse.rs b/src/commands/parse.rs index f68ad102..b2ffb6f2 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; use wac_parser::Document; +use wac_resolver::CommandError; /// Parses a WAC source file into a JSON AST representation. #[derive(Args)] @@ -15,7 +16,7 @@ pub struct ParseCommand { impl ParseCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self) -> Result<(), CommandError> { log::debug!("executing parse command"); let contents = fs::read_to_string(&self.path) @@ -23,7 +24,7 @@ impl ParseCommand { let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?; - serde_json::to_writer_pretty(std::io::stdout(), &document)?; + serde_json::to_writer_pretty(std::io::stdout(), &document).map_err(CommandError::Serde)?; println!(); Ok(()) diff --git a/src/commands/resolve.rs b/src/commands/resolve.rs index 92b9e13d..a18585ea 100644 --- a/src/commands/resolve.rs +++ b/src/commands/resolve.rs @@ -3,6 +3,8 @@ use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; use wac_parser::Document; +use wac_resolver::CommandError; +use warg_client::Retry; fn parse(s: &str) -> Result<(T, U)> where @@ -32,7 +34,6 @@ pub struct ResolveCommand { pub deps: Vec<(String, PathBuf)>, /// The URL of the registry to use. - #[cfg(feature = "registry")] #[clap(long, value_name = "URL")] pub registry: Option, @@ -43,7 +44,7 @@ pub struct ResolveCommand { impl ResolveCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self, retry: Option) -> Result<(), CommandError> { log::debug!("executing resolve command"); let contents = fs::read_to_string(&self.path) @@ -54,14 +55,12 @@ impl ResolveCommand { let resolver = PackageResolver::new( self.deps_dir, self.deps.into_iter().collect(), - #[cfg(feature = "registry")] self.registry.as_deref(), - )?; + retry, + ) + .await?; - let packages = resolver - .resolve(&document) - .await - .map_err(|e| fmt_err(e, &self.path, &contents))?; + let packages = resolver.resolve(&document).await?; let resolution = document .resolve(packages) diff --git a/src/lib.rs b/src/lib.rs index e94271c7..43d1fe24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,12 +11,12 @@ use std::{ path::{Path, PathBuf}, }; use wac_parser::Document; -use wac_resolver::{packages, Error, FileSystemPackageResolver}; +use wac_resolver::{packages, CommandError, Error, FileSystemPackageResolver}; use wac_types::BorrowedPackageKey; +use warg_client::Retry; pub mod commands; -#[cfg(feature = "registry")] mod progress; fn fmt_err(e: impl Into, path: &Path, source: &str) -> anyhow::Error { @@ -46,24 +46,25 @@ fn fmt_err(e: impl Into, path: &Path, source: &str) -> anyhow::Error { /// If it cannot find a matching package, it will check the registry. pub struct PackageResolver { fs: FileSystemPackageResolver, - #[cfg(feature = "registry")] registry: wac_resolver::RegistryPackageResolver, } impl PackageResolver { /// Creates a new package resolver. - pub fn new( + pub async fn new( dir: impl Into, overrides: HashMap, - #[cfg(feature = "registry")] registry: Option<&str>, + registry: Option<&str>, + retry: Option, ) -> Result { Ok(Self { fs: FileSystemPackageResolver::new(dir, overrides, false), - #[cfg(feature = "registry")] registry: wac_resolver::RegistryPackageResolver::new( registry, Some(Box::new(progress::ProgressBar::new())), - )?, + retry, + ) + .await?, }) } @@ -71,7 +72,7 @@ impl PackageResolver { pub async fn resolve<'a>( &self, document: &'a Document<'a>, - ) -> Result, Vec>, Error> { + ) -> Result, Vec>, CommandError> { let mut keys = packages(document)?; // Next, we resolve as many of the packages from the file system as possible @@ -82,7 +83,6 @@ impl PackageResolver { // Next resolve the remaining packages from the registry // The registry resolver will error on missing package - #[cfg(feature = "registry")] if !keys.is_empty() { let reg_packages = self.registry.resolve(&keys).await?; keys.retain(|key, _| !reg_packages.contains_key(key)); @@ -91,10 +91,10 @@ impl PackageResolver { // At this point keys should be empty, otherwise we have an unknown package if let Some((key, span)) = keys.first() { - return Err(Error::UnknownPackage { + return Err(CommandError::WacResolution(Error::UnknownPackage { name: key.name.to_string(), span: *span, - }); + })); } Ok(packages)