1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
11
12pub mod api;
13mod client;
14pub mod config;
15pub mod graphql;
16pub mod interface;
17pub mod login;
18pub mod package;
19pub mod publish;
20pub mod subscriptions;
21pub mod types;
22pub mod utils;
23pub mod wasmer_env;
24
25pub use crate::client::RegistryClient;
26
27use std::{
28 fmt,
29 path::{Path, PathBuf},
30 time::Duration,
31};
32
33use anyhow::Context;
34use graphql_client::GraphQLQuery;
35use tar::EntryType;
36
37use crate::utils::normalize_path;
38pub use crate::{
39 config::{format_graphql, WasmerConfig},
40 graphql::queries::get_bindings_query::ProgrammingLanguage,
41 package::Package,
42};
43
44pub static PACKAGE_TOML_FILE_NAME: &str = "wasmer.toml";
45pub static PACKAGE_TOML_FALLBACK_NAME: &str = "wapm.toml";
46pub static GLOBAL_CONFIG_FILE_NAME: &str = "wasmer.toml";
47
48#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
49#[non_exhaustive]
50pub struct PackageDownloadInfo {
51 pub registry: String,
52 pub package: String,
53 pub version: String,
54 pub is_latest_version: bool,
55 pub is_private: bool,
57 pub commands: String,
58 pub manifest: String,
59 pub url: String,
60 pub pirita_url: Option<String>,
61}
62
63pub fn query_command_from_registry(
64 registry_url: &str,
65 command_name: &str,
66) -> Result<PackageDownloadInfo, String> {
67 use crate::graphql::{
68 execute_query,
69 queries::{get_package_by_command_query, GetPackageByCommandQuery},
70 };
71
72 let q = GetPackageByCommandQuery::build_query(get_package_by_command_query::Variables {
73 command_name: command_name.to_string(),
74 });
75
76 let response: get_package_by_command_query::ResponseData = execute_query(registry_url, "", &q)
77 .map_err(|e| format!("Error sending GetPackageByCommandQuery: {e}"))?;
78
79 let command = response
80 .get_command
81 .ok_or_else(|| "GetPackageByCommandQuery: no get_command".to_string())?;
82
83 let package = command.package_version.package.display_name;
84 let version = command.package_version.version;
85 let url = command.package_version.distribution.download_url;
86 let pirita_url = command.package_version.distribution.pirita_download_url;
87
88 Ok(PackageDownloadInfo {
89 registry: registry_url.to_string(),
90 package,
91 version,
92 is_latest_version: command.package_version.is_last_version,
93 manifest: command.package_version.manifest,
94 commands: command_name.to_string(),
95 url,
96 pirita_url,
97 is_private: command.package_version.package.private,
98 })
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
102pub enum QueryPackageError {
103 ErrorSendingQuery(String),
104 NoPackageFound {
105 name: String,
106 version: Option<String>,
107 },
108}
109
110impl fmt::Display for QueryPackageError {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 match self {
113 QueryPackageError::ErrorSendingQuery(q) => write!(f, "error sending query: {q}"),
114 QueryPackageError::NoPackageFound { name, version } => {
115 write!(f, "no package found for {name:?}")?;
116 if let Some(version) = version {
117 write!(f, " (version = {version:?})")?;
118 }
119
120 Ok(())
121 }
122 }
123 }
124}
125
126impl std::error::Error for QueryPackageError {}
127
128#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
129pub enum GetIfPackageHasNewVersionResult {
130 UseLocalAlreadyInstalled {
132 registry_host: String,
133 namespace: String,
134 name: String,
135 version: String,
136 path: PathBuf,
137 },
138 LocalVersionMayBeOutdated {
140 registry_host: String,
141 namespace: String,
142 name: String,
143 installed_versions: Vec<(String, bool)>,
146 },
147 PackageNotInstalledYet {
149 registry_url: String,
150 namespace: String,
151 name: String,
152 version: Option<String>,
153 },
154}
155
156pub fn query_package_from_registry(
159 registry_url: &str,
160 name: &str,
161 version: Option<&str>,
162 auth_token: Option<&str>,
163) -> Result<PackageDownloadInfo, QueryPackageError> {
164 use crate::graphql::{
165 execute_query,
166 queries::{get_package_version_query, GetPackageVersionQuery},
167 };
168
169 let q = GetPackageVersionQuery::build_query(get_package_version_query::Variables {
170 name: name.to_string(),
171 version: version.map(|s| s.to_string()),
172 });
173
174 let response: get_package_version_query::ResponseData =
175 execute_query(registry_url, auth_token.unwrap_or_default(), &q).map_err(|e| {
176 QueryPackageError::ErrorSendingQuery(format!("Error sending GetPackagesQuery: {e}"))
177 })?;
178
179 let v = response
180 .package_version
181 .as_ref()
182 .ok_or_else(|| QueryPackageError::NoPackageFound {
183 name: name.to_string(),
184 version: None,
185 })?;
186
187 let manifest =
188 toml::from_str::<wasmer_config::package::Manifest>(&v.manifest).map_err(|e| {
189 QueryPackageError::ErrorSendingQuery(format!(
190 "Invalid manifest for crate {name:?}: {e}"
191 ))
192 })?;
193
194 Ok(PackageDownloadInfo {
195 registry: registry_url.to_string(),
196 package: v.package.name.clone(),
197
198 version: v.version.clone(),
199 is_latest_version: v.is_last_version,
200 is_private: v.package.private,
201 manifest: v.manifest.clone(),
202
203 commands: manifest
204 .commands
205 .iter()
206 .map(|s| s.get_name())
207 .collect::<Vec<_>>()
208 .join(", "),
209
210 url: v.distribution.download_url.clone(),
211 pirita_url: v.distribution.pirita_download_url.clone(),
212 })
213}
214
215pub fn get_checkouts_dir(wasmer_dir: &Path) -> PathBuf {
216 wasmer_dir.join("checkouts")
217}
218
219pub fn get_webc_dir(wasmer_dir: &Path) -> PathBuf {
220 wasmer_dir.join("webc")
221}
222
223pub fn try_unpack_targz<P: AsRef<Path>>(
226 target_targz_path: P,
227 target_path: P,
228 strip_toplevel: bool,
229) -> Result<PathBuf, anyhow::Error> {
230 let target_targz_path = target_targz_path.as_ref().to_string_lossy().to_string();
231 let target_targz_path = crate::utils::normalize_path(&target_targz_path);
232 let target_targz_path = Path::new(&target_targz_path);
233
234 let target_path = target_path.as_ref().to_string_lossy().to_string();
235 let target_path = crate::utils::normalize_path(&target_path);
236 let target_path = Path::new(&target_path);
237
238 let open_file = || {
239 std::fs::File::open(target_targz_path)
240 .map_err(|e| anyhow::anyhow!("failed to open {}: {e}", target_targz_path.display()))
241 };
242
243 let try_decode_gz = || {
244 let file = open_file()?;
245 let gz_decoded = flate2::read::GzDecoder::new(&file);
246 let ar = tar::Archive::new(gz_decoded);
247 if strip_toplevel {
248 unpack_sans_parent(ar, target_path).map_err(|e| {
249 anyhow::anyhow!(
250 "failed to unpack (gz_ar_unpack_sans_parent) {}: {e}",
251 target_targz_path.display()
252 )
253 })
254 } else {
255 unpack_with_parent(ar, target_path).map_err(|e| {
256 anyhow::anyhow!(
257 "failed to unpack (gz_ar_unpack) {}: {e}",
258 target_targz_path.display()
259 )
260 })
261 }
262 };
263
264 let try_decode_xz = || {
265 let file = open_file()?;
266 let mut decomp: Vec<u8> = Vec::new();
267 let mut bufread = std::io::BufReader::new(&file);
268 lzma_rs::xz_decompress(&mut bufread, &mut decomp).map_err(|e| {
269 anyhow::anyhow!(
270 "failed to unpack (try_decode_xz) {}: {e}",
271 target_targz_path.display()
272 )
273 })?;
274
275 let cursor = std::io::Cursor::new(decomp);
276 let mut ar = tar::Archive::new(cursor);
277 if strip_toplevel {
278 unpack_sans_parent(ar, target_path).map_err(|e| {
279 anyhow::anyhow!(
280 "failed to unpack (sans parent) {}: {e}",
281 target_targz_path.display()
282 )
283 })
284 } else {
285 ar.unpack(target_path).map_err(|e| {
286 anyhow::anyhow!(
287 "failed to unpack (with parent) {}: {e}",
288 target_targz_path.display()
289 )
290 })
291 }
292 };
293
294 try_decode_gz().or_else(|e1| {
295 try_decode_xz()
296 .map_err(|e2| anyhow::anyhow!("could not decode gz: {e1}, could not decode xz: {e2}"))
297 })?;
298
299 Ok(Path::new(&target_targz_path).to_path_buf())
300}
301
302pub fn download_and_unpack_targz(
304 url: &str,
305 target_path: &Path,
306 strip_toplevel: bool,
307) -> Result<PathBuf, anyhow::Error> {
308 let tempdir = tempfile::TempDir::new()?;
309
310 let target_targz_path = tempdir.path().join("package.tar.gz");
311
312 let mut resp = reqwest::blocking::get(url)
313 .map_err(|e| anyhow::anyhow!("failed to download {url}: {e}"))?;
314
315 {
316 let mut file = std::fs::File::create(&target_targz_path).map_err(|e| {
317 anyhow::anyhow!(
318 "failed to download {url} into {}: {e}",
319 target_targz_path.display()
320 )
321 })?;
322
323 resp.copy_to(&mut file)
324 .map_err(|e| anyhow::anyhow!("{e}"))?;
325 }
326
327 try_unpack_targz(target_targz_path.as_path(), target_path, strip_toplevel)
328 .with_context(|| anyhow::anyhow!("Could not download {url}"))?;
329
330 Ok(target_path.to_path_buf())
331}
332
333pub fn unpack_with_parent<R>(mut archive: tar::Archive<R>, dst: &Path) -> Result<(), anyhow::Error>
334where
335 R: std::io::Read,
336{
337 use std::path::Component::Normal;
338
339 let dst_normalized = normalize_path(dst.to_string_lossy().as_ref());
340
341 for entry in archive.entries()? {
342 let mut entry = entry?;
343 let path: PathBuf = entry
344 .path()?
345 .components()
346 .skip(1) .filter(|c| matches!(c, Normal(_))) .collect();
349 if entry.header().entry_type().is_file() {
350 entry.unpack_in(&dst_normalized)?;
351 } else if entry.header().entry_type() == EntryType::Directory {
352 std::fs::create_dir_all(Path::new(&dst_normalized).join(&path))?;
353 }
354 }
355 Ok(())
356}
357
358pub fn unpack_sans_parent<R>(mut archive: tar::Archive<R>, dst: &Path) -> std::io::Result<()>
359where
360 R: std::io::Read,
361{
362 use std::path::Component::Normal;
363
364 let dst_normalized = normalize_path(dst.to_string_lossy().as_ref());
365
366 for entry in archive.entries()? {
367 let mut entry = entry?;
368 let path: PathBuf = entry
369 .path()?
370 .components()
371 .skip(1) .filter(|c| matches!(c, Normal(_))) .collect();
374 entry.unpack(Path::new(&dst_normalized).join(path))?;
375 }
376 Ok(())
377}
378
379pub fn whoami(
380 wasmer_dir: &Path,
381 registry: Option<&str>,
382 token: Option<&str>,
383) -> Result<(String, String), anyhow::Error> {
384 use crate::graphql::queries::{who_am_i_query, WhoAmIQuery};
385
386 let config = WasmerConfig::from_file(wasmer_dir);
387
388 let config = config
389 .map_err(|e| anyhow::anyhow!("{e}"))
390 .with_context(|| format!("{registry:?}"))?;
391
392 let registry = match registry {
393 Some(s) => format_graphql(s),
394 None => config.registry.get_current_registry(),
395 };
396
397 let login_token = token
398 .map(|s| s.to_string())
399 .or_else(|| config.registry.get_login_token_for_registry(®istry))
400 .ok_or_else(|| anyhow::anyhow!("not logged into registry {:?}", registry))?;
401
402 let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
403 let response: who_am_i_query::ResponseData =
404 crate::graphql::execute_query(®istry, &login_token, &q)
405 .with_context(|| format!("{registry:?}"))?;
406
407 let username = response
408 .viewer
409 .as_ref()
410 .ok_or_else(|| anyhow::anyhow!("not logged into registry {:?}", registry))?
411 .username
412 .to_string();
413
414 Ok((registry, username))
415}
416
417pub fn test_if_registry_present(registry: &str) -> Result<bool, String> {
418 use crate::graphql::queries::{test_if_registry_present, TestIfRegistryPresent};
419
420 let q = TestIfRegistryPresent::build_query(test_if_registry_present::Variables {});
421 crate::graphql::execute_query_modifier_inner_check_json(
422 registry,
423 "",
424 &q,
425 Some(Duration::from_secs(1)),
426 |f| f,
427 )
428 .map_err(|e| format!("{e}"))?;
429
430 Ok(true)
431}
432
433pub fn get_all_available_registries(wasmer_dir: &Path) -> Result<Vec<String>, String> {
434 let config = WasmerConfig::from_file(wasmer_dir)?;
435 let mut registries = Vec::new();
436 for login in config.registry.tokens {
437 registries.push(format_graphql(&login.registry));
438 }
439 Ok(registries)
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct Bindings {
445 pub id: String,
447 pub url: String,
450 pub language: ProgrammingLanguage,
452 pub generator: BindingsGenerator,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq)]
458pub struct BindingsGenerator {
459 pub id: String,
461 pub package_name: String,
463 pub version: String,
465 pub command: String,
467}
468
469impl fmt::Display for BindingsGenerator {
470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471 let BindingsGenerator {
472 package_name,
473 version,
474 command,
475 ..
476 } = self;
477
478 write!(f, "{package_name}@{version}:{command}")?;
479
480 Ok(())
481 }
482}
483
484pub fn list_bindings(
489 registry: &str,
490 name: &str,
491 version: Option<&str>,
492) -> Result<Vec<Bindings>, anyhow::Error> {
493 use crate::graphql::queries::{
494 get_bindings_query::{ResponseData, Variables},
495 GetBindingsQuery,
496 };
497
498 let variables = Variables {
499 name: name.to_string(),
500 version: version.map(String::from),
501 };
502
503 let q = GetBindingsQuery::build_query(variables);
504 let response: ResponseData = crate::graphql::execute_query(registry, "", &q)?;
505
506 let package_version = response.package_version.context("Package not found")?;
507
508 let mut bindings_packages = Vec::new();
509
510 for b in package_version.bindings.into_iter().flatten() {
511 let pkg = Bindings {
512 id: b.id,
513 url: b.url,
514 language: b.language,
515 generator: BindingsGenerator {
516 id: b.generator.package_version.id,
517 package_name: b.generator.package_version.package.name,
518 version: b.generator.package_version.version,
519 command: b.generator.command_name,
520 },
521 };
522 bindings_packages.push(pkg);
523 }
524
525 Ok(bindings_packages)
526}