wasmer_cli/commands/package/
get.rs1use std::path::Path;
2
3use anyhow::{Context, bail};
4use dialoguer::console::style;
5use wasmer_backend_api::WasmerClient;
6use wasmer_config::package::{Manifest, PackageIdent, PackageSource};
7
8use super::common::{
9 get_manifest, manifest_from_webc_metadata, package_web_url, registry_web_host,
10};
11use crate::commands::AsyncCliCommand;
12use crate::config::WasmerEnv;
13
14#[derive(clap::Parser, Debug)]
20pub struct PackageGet {
21 #[clap(flatten)]
22 pub env: WasmerEnv,
23
24 #[clap(default_value = ".")]
29 pub package: String,
30}
31
32#[async_trait::async_trait]
33impl AsyncCliCommand for PackageGet {
34 type Output = ();
35
36 async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
37 let (manifest, web_url) = self.load_manifest().await?;
38
39 let print_field = |label: &str, value: &str| {
40 println!("{:<13} {value}", style(format!("{label}:")).bold().dim());
41 };
42
43 if let Some(package) = manifest.package.as_ref() {
44 print_field("Name", package.name.as_deref().unwrap_or("<unnamed>"));
45 print_field(
46 "Version",
47 &package
48 .version
49 .as_ref()
50 .map(|v| v.to_string())
51 .unwrap_or_else(|| "<none>".to_string()),
52 );
53
54 if let Some(description) = &package.description {
55 print_field("Description", description);
56 }
57 if let Some(license) = &package.license {
58 print_field("License", license);
59 }
60 if let Some(homepage) = &package.homepage {
61 print_field("Homepage", homepage);
62 }
63 if let Some(repository) = &package.repository {
64 print_field("Repository", repository);
65 }
66 if let Some(entrypoint) = &package.entrypoint {
67 print_field("Entrypoint", entrypoint);
68 }
69 if package.private {
70 print_field("Private", "true");
71 }
72 } else {
73 println!("{}", style("Package has no metadata.").dim());
74 }
75
76 if !manifest.commands.is_empty() {
77 let commands = manifest
78 .commands
79 .iter()
80 .map(|c| c.get_name())
81 .collect::<Vec<_>>()
82 .join(", ");
83 print_field("Commands", &commands);
84 }
85
86 if !manifest.dependencies.is_empty() {
87 print_field("Dependencies", &manifest.dependencies.len().to_string());
88 for (name, version) in &manifest.dependencies {
89 println!(" {name} = {version}");
90 }
91 }
92
93 if let Some(web_url) = web_url {
94 print_field("URL", &web_url);
95 }
96
97 Ok(())
98 }
99}
100
101impl PackageGet {
102 async fn load_manifest(&self) -> anyhow::Result<(Manifest, Option<String>)> {
109 let path = Path::new(&self.package);
112 if path.exists() {
113 let (_, manifest) = get_manifest(path)?;
114 return Ok((manifest, None));
115 }
116
117 let source: PackageSource = self.package.parse().with_context(|| {
118 format!(
119 "'{}' is not a file or directory on disk, or a valid package name",
120 self.package
121 )
122 })?;
123
124 match source {
125 PackageSource::Ident(PackageIdent::Named(id)) => {
126 let client = self.env.client_unauthennticated()?;
127
128 let version = id.version_or_default().to_string();
129 let version = if version == "*" {
130 String::from("latest")
131 } else {
132 version
133 };
134 let full_name = id.full_name();
135
136 let package = match wasmer_backend_api::query::get_package_version(
137 &client,
138 full_name.clone(),
139 version.clone(),
140 )
141 .await?
142 {
143 Some(package) => package,
144 None => {
145 let requested = (version != "latest").then(|| {
150 self.package
151 .rsplit_once('@')
152 .map_or(version.as_str(), |(_, v)| v)
153 });
154 return Err(version_not_found_error(&client, &full_name, requested).await);
155 }
156 };
157
158 let json = package
159 .pirita_manifest
160 .as_ref()
161 .context("the registry did not return a manifest for this package")?;
162 let webc_manifest: webc::metadata::Manifest = serde_json::from_str(&json.0)
163 .context("could not parse the manifest returned by the registry")?;
164 let mut manifest = manifest_from_webc_metadata(&webc_manifest)?;
165
166 let web_url = package_web_url(&client, &full_name, Some(&package.version));
169
170 if let Some(pkg) = manifest.package.as_mut() {
177 pkg.name = Some(full_name);
178 pkg.version = Some(package.version.parse().with_context(|| {
179 format!(
180 "invalid version returned by the registry: '{}'",
181 package.version
182 )
183 })?);
184 if !package.description.is_empty() {
188 pkg.description = Some(package.description);
189 }
190 if package.license.is_some() {
191 pkg.license = package.license;
192 }
193 if package.homepage.is_some() {
194 pkg.homepage = package.homepage;
195 }
196 if package.repository.is_some() {
197 pkg.repository = package.repository;
198 }
199 }
200
201 Ok((manifest, Some(web_url)))
202 }
203 PackageSource::Ident(PackageIdent::Hash(hash)) => {
204 let client = self.env.client_unauthennticated()?;
205
206 let pkg = wasmer_backend_api::query::get_package_release(
207 &client,
208 &hash.to_string(),
209 )
210 .await?
211 .with_context(|| {
212 format!(
213 "Package with {hash} does not exist in the registry, or is not accessible"
214 )
215 })?;
216
217 let image = pkg
218 .webc_v3
219 .or(pkg.webc)
220 .context("the registry did not return a WebC image for this package")?;
221 let webc_manifest: webc::metadata::Manifest =
222 serde_json::from_str(&image.manifest.0)
223 .context("could not parse the manifest returned by the registry")?;
224
225 Ok((manifest_from_webc_metadata(&webc_manifest)?, None))
226 }
227 PackageSource::Path(p) => {
228 bail!("no file or directory found at '{p}'")
232 }
233 PackageSource::Url(url) => {
234 bail!("showing a package directly from a URL is not supported: '{url}'")
235 }
236 }
237 }
238}
239
240async fn version_not_found_error(
247 client: &WasmerClient,
248 full_name: &str,
249 requested: Option<&str>,
250) -> anyhow::Error {
251 let registry = registry_web_host(client);
252
253 match wasmer_backend_api::query::get_package_version_numbers(client, full_name.to_string())
254 .await
255 {
256 Ok(Some(mut versions)) if !versions.is_empty() => {
257 sort_versions(&mut versions);
258 let available = versions.join(", ");
259 match requested {
260 Some(version) => anyhow::anyhow!(
261 "package '{full_name}' has no version matching '{version}' in registry '{registry}'.\nAvailable versions: {available}"
262 ),
263 None => anyhow::anyhow!(
264 "could not resolve the latest version of package '{full_name}' from registry '{registry}'.\nAvailable versions: {available}"
265 ),
266 }
267 }
268 Ok(_) => {
270 anyhow::anyhow!("package '{full_name}' was not found in registry '{registry}'")
271 }
272 Err(_) => anyhow::anyhow!(
274 "could not retrieve package information for package '{full_name}' from registry '{registry}'"
275 ),
276 }
277}
278
279fn sort_versions(versions: &mut [String]) {
282 versions.sort_by(
283 |a, b| match (semver::Version::parse(a), semver::Version::parse(b)) {
284 (Ok(a), Ok(b)) => a.cmp(&b),
285 _ => a.cmp(b),
286 },
287 );
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[tokio::test]
295 async fn test_cmd_package_get_dir() {
296 let dir = tempfile::tempdir().unwrap();
297 std::fs::write(
298 dir.path().join("wasmer.toml"),
299 r#"
300[package]
301name = "wasmer/test"
302version = "1.2.3"
303description = "A test package"
304license = "MIT"
305"#,
306 )
307 .unwrap();
308
309 let cmd = PackageGet {
310 env: WasmerEnv::new(
311 crate::config::DEFAULT_WASMER_CACHE_DIR.clone(),
312 crate::config::DEFAULT_WASMER_CACHE_DIR.clone(),
313 None,
314 None,
315 ),
316 package: dir.path().to_str().unwrap().to_owned(),
317 };
318
319 cmd.run_async().await.unwrap();
320 }
321}