From 16ad5bae83d15155571464c5dfca1c7de3544057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Fri, 10 Mar 2023 13:45:09 -0800 Subject: [PATCH] feat(wasm): get nassun and node-maintainer working well in wasm (#131) --- Cargo.lock | 54 +++ Cargo.toml | 2 +- crates/nassun/Cargo.toml | 8 +- crates/nassun/src/client.rs | 18 +- crates/nassun/src/entries.rs | 3 +- crates/nassun/src/error.rs | 6 + crates/nassun/src/lib.rs | 19 +- crates/nassun/src/package.rs | 9 +- crates/nassun/src/resolver.rs | 4 +- crates/nassun/src/tarball.rs | 13 +- crates/nassun/src/wasm.rs | 386 ++++++++++++------ crates/node-maintainer/Cargo.toml | 1 + crates/node-maintainer/src/error.rs | 8 +- crates/node-maintainer/src/graph.rs | 8 +- crates/node-maintainer/src/into_kdl.rs | 2 +- crates/node-maintainer/src/lib.rs | 4 + crates/node-maintainer/src/lockfile.rs | 4 +- crates/node-maintainer/src/maintainer.rs | 55 ++- crates/node-maintainer/src/node.rs | 2 +- crates/node-maintainer/src/wasm.rs | 197 ++++++--- .../node-maintainer/tests/resolver_basic.rs | 6 +- crates/oro-client/src/api/packument.rs | 2 +- 22 files changed, 588 insertions(+), 223 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 519e3699..2312b453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1124,6 +1124,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-utils" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.3.16" @@ -1676,6 +1689,7 @@ dependencies = [ "backon", "bincode", "cacache", + "console_error_panic_hook", "dashmap", "flate2", "futures 0.3.26", @@ -1694,6 +1708,7 @@ dependencies = [ "tempfile", "thiserror", "tracing", + "tsify", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -1746,6 +1761,7 @@ dependencies = [ "tempfile", "thiserror", "tracing", + "tsify", "unicase", "url", "wasm-bindgen", @@ -2539,6 +2555,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.93" @@ -3043,6 +3070,33 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tsify" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfafeca19605f1239ad9a30b9f15b58e53abedfbf237390506276029329a32ee" +dependencies = [ + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d637b54262b1c6b8c2c88ce30fbef4e72613cb71c0e0b6fc09747052eb59a152" +dependencies = [ + "darling 0.14.3", + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "typenum" version = "1.16.0" diff --git a/Cargo.toml b/Cargo.toml index 71996b7d..779e79b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,6 @@ members = [ async-compression = "0.3.5" async-process = "1.0.1" async-std = "1.12.0" -async-tar-wasm = "0.4.2-wasm.1" async-trait = "0.1.64" backon = "0.4.0" bincode = "1.3.1" @@ -99,6 +98,7 @@ thiserror = "1.0.38" tracing = "0.1.37" tracing-appender = "0.2.2" tracing-subscriber = "0.3.16" +tsify = "0.4.3" url = "2.3.1" wasm-bindgen = "0.2.84" wasm-bindgen-futures = "0.4.34" diff --git a/crates/nassun/Cargo.toml b/crates/nassun/Cargo.toml index 29aa0830..c30213c0 100644 --- a/crates/nassun/Cargo.toml +++ b/crates/nassun/Cargo.toml @@ -27,10 +27,11 @@ serde_json = { workspace = true } ssri = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +tsify = { workspace = true, default-features = false, features = ["js"] } url = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-tar-wasm = { workspace = true } +async-tar-wasm = "0.4.2-wasm.1" async-process = { workspace = true } async-std = { workspace = true, features = ["attributes", "std"] } backon = { workspace = true } @@ -42,7 +43,10 @@ tempfile = { workspace = true } which = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -async-tar-wasm = { workspace = true, default-features = false } +# NOTE: This async-tar-wasm version can't be configured at the workspace +# level, because otherwise the conditional features don't work. +async-tar-wasm = { version = "0.4.2-wasm.1", default-features = false } +console_error_panic_hook = { workspace = true } js-sys = { workspace = true } serde-wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true } diff --git a/crates/nassun/src/client.rs b/crates/nassun/src/client.rs index 4e19df0c..e847d3c3 100644 --- a/crates/nassun/src/client.rs +++ b/crates/nassun/src/client.rs @@ -8,6 +8,7 @@ use url::Url; pub use oro_package_spec::{PackageSpec, VersionSpec}; +use crate::entries::Entries; use crate::error::Result; #[cfg(not(target_arch = "wasm32"))] use crate::fetch::DirFetcher; @@ -15,12 +16,13 @@ use crate::fetch::DirFetcher; use crate::fetch::GitFetcher; use crate::fetch::{DummyFetcher, NpmFetcher, PackageFetcher}; use crate::package::Package; -use crate::resolver::PackageResolver; -use crate::{Entries, PackageResolution, Tarball}; +use crate::resolver::{PackageResolution, PackageResolver}; +use crate::tarball::Tarball; /// Build a new Nassun instance with specified options. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct NassunOpts { + #[cfg(not(target_arch = "wasm32"))] cache: Option, base_dir: Option, default_tag: Option, @@ -32,6 +34,8 @@ impl NassunOpts { Default::default() } + /// Cache directory to use for requests. + #[cfg(not(target_arch = "wasm32"))] pub fn cache(mut self, cache: impl AsRef) -> Self { self.cache = Some(PathBuf::from(cache.as_ref())); self @@ -42,29 +46,37 @@ impl NassunOpts { self } + /// Adds a registry to use for a specific scope. pub fn scope_registry(mut self, scope: impl AsRef, registry: Url) -> Self { self.registries .insert(Some(scope.as_ref().into()), registry); self } + /// Base directory to use for resolving relative paths. Defaults to `"."`. pub fn base_dir(mut self, base_dir: impl AsRef) -> Self { self.base_dir = Some(PathBuf::from(base_dir.as_ref())); self } + /// Default tag to use when resolving package versions. Defaults to `latest`. pub fn default_tag(mut self, default_tag: impl AsRef) -> Self { self.default_tag = Some(default_tag.as_ref().into()); self } + /// Build a new Nassun instance from this options object. pub fn build(self) -> Nassun { let registry = self .registries .get(&None) .cloned() .unwrap_or_else(|| "https://registry.npmjs.org/".parse().unwrap()); + #[cfg(target_arch = "wasm32")] + let client_builder = OroClient::builder().registry(registry); + #[cfg(not(target_arch = "wasm32"))] let mut client_builder = OroClient::builder().registry(registry); + #[cfg(not(target_arch = "wasm32"))] let cache = if let Some(cache) = self.cache { client_builder = client_builder.cache(cache.clone()); Arc::new(Some(cache)) @@ -75,6 +87,8 @@ impl NassunOpts { Nassun { #[cfg(not(target_arch = "wasm32"))] cache, + #[cfg(target_arch = "wasm32")] + cache: Arc::new(None), resolver: PackageResolver { #[cfg(target_arch = "wasm32")] base_dir: PathBuf::from("."), diff --git a/crates/nassun/src/entries.rs b/crates/nassun/src/entries.rs index 473ee8a5..9282d338 100644 --- a/crates/nassun/src/entries.rs +++ b/crates/nassun/src/entries.rs @@ -7,7 +7,8 @@ use futures::{AsyncRead, Stream}; pub use async_tar_wasm::Header; -use crate::{error::Result, Tarball}; +use crate::error::Result; +use crate::tarball::Tarball; #[cfg(not(target_arch = "wasm32"))] type EntriesStream = Box> + Unpin + Send + Sync>; diff --git a/crates/nassun/src/error.rs b/crates/nassun/src/error.rs index c76e889a..3cbcc41a 100644 --- a/crates/nassun/src/error.rs +++ b/crates/nassun/src/error.rs @@ -45,6 +45,7 @@ pub enum NassunError { #[diagnostic(code(nassun::io::extract))] ExtractIoError(#[source] std::io::Error, Option, String), + #[cfg(not(target_arch = "wasm32"))] #[error("Failed to extract tarball to cache. {0}{}", if let Some(path) = .1 { format!(" (file: {})", path.to_string_lossy()) } else { @@ -89,6 +90,11 @@ pub enum NassunError { versions: Vec, }, + #[cfg(target_arch = "wasm32")] + #[error(transparent)] + #[diagnostic(code(node_maintainer::serde_wasm_bindgen::error))] + SerdeWasmBindgenError(#[from] serde_wasm_bindgen::Error), + #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] #[diagnostic( diff --git a/crates/nassun/src/lib.rs b/crates/nassun/src/lib.rs index bcc910e8..64522276 100644 --- a/crates/nassun/src/lib.rs +++ b/crates/nassun/src/lib.rs @@ -4,21 +4,26 @@ use futures::AsyncRead; pub use oro_package_spec::{GitHost, GitInfo, PackageSpec, VersionSpec}; -mod client; -mod entries; -mod error; -mod fetch; -mod package; -mod resolver; -mod tarball; +pub mod client; +pub mod entries; +pub mod error; +pub mod fetch; +pub mod package; +pub mod resolver; +pub mod tarball; #[cfg(target_arch = "wasm32")] mod wasm; +#[cfg(not(target_arch = "wasm32"))] pub use client::*; +#[cfg(not(target_arch = "wasm32"))] pub use entries::*; +#[cfg(not(target_arch = "wasm32"))] pub use error::NassunError; +#[cfg(not(target_arch = "wasm32"))] pub use package::*; pub use resolver::*; +#[cfg(not(target_arch = "wasm32"))] pub use tarball::*; #[cfg(target_arch = "wasm32")] pub use wasm::*; diff --git a/crates/nassun/src/package.rs b/crates/nassun/src/package.rs index a6b3efd3..a072a46d 100644 --- a/crates/nassun/src/package.rs +++ b/crates/nassun/src/package.rs @@ -1,5 +1,7 @@ use std::fmt; -use std::path::{Path, PathBuf}; +#[cfg(not(target_arch = "wasm32"))] +use std::path::Path; +use std::path::PathBuf; use async_std::sync::Arc; use oro_common::{CorgiPackument, CorgiVersionMetadata, Packument, VersionMetadata}; @@ -7,7 +9,9 @@ use oro_package_spec::PackageSpec; use ssri::Integrity; use crate::entries::Entries; -use crate::error::{NassunError, Result}; +#[cfg(not(target_arch = "wasm32"))] +use crate::error::NassunError; +use crate::error::Result; use crate::fetch::PackageFetcher; use crate::resolver::PackageResolution; use crate::tarball::Tarball; @@ -21,6 +25,7 @@ pub struct Package { pub(crate) resolved: PackageResolution, pub(crate) fetcher: Arc, pub(crate) base_dir: PathBuf, + #[cfg_attr(target_arch = "wasm32", allow(dead_code))] pub(crate) cache: Arc>, } diff --git a/crates/nassun/src/resolver.rs b/crates/nassun/src/resolver.rs index ab4db53a..684986de 100644 --- a/crates/nassun/src/resolver.rs +++ b/crates/nassun/src/resolver.rs @@ -6,7 +6,9 @@ use oro_package_spec::{GitInfo, PackageSpec, VersionSpec}; use ssri::Integrity; use url::Url; -use crate::{fetch::PackageFetcher, package::Package, NassunError}; +use crate::error::NassunError; +use crate::fetch::PackageFetcher; +use crate::package::Package; /// Represents a fully-resolved, specific version of a package as it would be fetched. #[derive(Clone, PartialEq, Eq)] diff --git a/crates/nassun/src/tarball.rs b/crates/nassun/src/tarball.rs index 7f80202c..8864a30a 100644 --- a/crates/nassun/src/tarball.rs +++ b/crates/nassun/src/tarball.rs @@ -1,10 +1,12 @@ #[cfg(not(target_arch = "wasm32"))] use std::io::Write; +#[cfg(not(target_arch = "wasm32"))] use std::io::{Read, Seek}; #[cfg(not(target_arch = "wasm32"))] use std::path::{Path, PathBuf}; use std::pin::Pin; use std::task::{Context, Poll}; +#[cfg(not(target_arch = "wasm32"))] use std::time::Duration; use async_compression::futures::bufread::GzipDecoder; @@ -12,22 +14,28 @@ use async_std::io::BufReader; use async_tar_wasm::Archive; #[cfg(not(target_arch = "wasm32"))] use backon::{BlockingRetryable, ConstantBuilder}; +#[cfg(not(target_arch = "wasm32"))] use cacache::WriteOpts; -use futures::{AsyncRead, AsyncReadExt, StreamExt}; +#[cfg(not(target_arch = "wasm32"))] +use futures::AsyncReadExt; +use futures::{AsyncRead, StreamExt}; #[cfg(not(target_arch = "wasm32"))] use ssri::IntegrityOpts; use ssri::{Integrity, IntegrityChecker}; +#[cfg(not(target_arch = "wasm32"))] use tempfile::NamedTempFile; use crate::entries::{Entries, Entry}; use crate::error::{NassunError, Result}; use crate::TarballStream; +#[cfg(not(target_arch = "wasm32"))] const MAX_IN_MEMORY_TARBALL_SIZE: usize = 1024 * 1024 * 5; pub struct Tarball { checker: Option, reader: TarballStream, + #[cfg(not(target_arch = "wasm32"))] integrity: Option, } @@ -36,6 +44,7 @@ impl Tarball { Self { reader, checker: Some(IntegrityChecker::new(integrity.clone())), + #[cfg(not(target_arch = "wasm32"))] integrity: Some(integrity), } } @@ -44,6 +53,7 @@ impl Tarball { Self { reader, checker: None, + #[cfg(not(target_arch = "wasm32"))] integrity: None, } } @@ -351,6 +361,7 @@ fn strip_one(path: &Path) -> Option<&Path> { comps.next().map(|_| comps.as_path()) } +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn tarball_key(integrity: &Integrity) -> String { format!("nassun::package::{integrity}") } diff --git a/crates/nassun/src/wasm.rs b/crates/nassun/src/wasm.rs index cf48748a..a5421878 100644 --- a/crates/nassun/src/wasm.rs +++ b/crates/nassun/src/wasm.rs @@ -5,112 +5,242 @@ use std::collections::HashMap; use futures::StreamExt; use miette::Diagnostic; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::Url; +use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_streams::ReadableStream; -use crate::{Nassun, NassunError, NassunOpts, Package}; +use crate::error::NassunError; -type Result = std::result::Result; +#[wasm_bindgen(typescript_custom_section)] +const TS_APPEND_CONTENT: &'static str = r#" +/** + * Error type thrown by the Nassun API. + */ +export interface NassunError { + message: string; + code?: string; +} + +/** + * An entry extracted from a package tarball. + */ +export interface Entry { + type: number; + mtime: number; + size: number; + path: string; + contents: ReadableStream; +} +"#; + +type Result = std::result::Result; + +impl From for JsValue { + fn from(e: NassunError) -> Self { + let obj = js_sys::Object::new(); + let msg = format!("{e}"); + js_sys::Reflect::set(&obj, &"message".into(), &JsValue::from_str(&msg)) + .expect(&format!("failed to set error message: {e}")); + if let Some(code) = e.code() { + let code = format!("{code}"); + js_sys::Reflect::set(&obj, &"code".into(), &JsValue::from_str(&code)) + .expect(&format!("failed to set error code: {e:#?}")); + } + obj.into() + } +} +/// Resolves a `Packument` for the given package `spec`. +/// +/// This uses default `Nassun` options and does not cache the result. +/// To configure `Nassun`, and/or enable more efficient caching/reuse, +/// look at `Package#packument` instead. #[wasm_bindgen] pub async fn packument(spec: &str, opts: JsValue) -> Result { - JsNassun::new(opts)?.resolve(spec).await?.packument().await + Nassun::new(opts)?.resolve(spec).await?.packument().await +} + +/// Resolves a partial ("corgi") version of the `Packument` for the given +/// package `spec`. +/// +/// This uses default `Nassun` options and does not cache the result. +/// To configure `Nassun`, and/or enable more efficient caching/reuse, +/// look at `Package#packument` instead. +#[wasm_bindgen(js_name = "corgiPackument")] +pub async fn corgi_packument(spec: &str, opts: JsValue) -> Result { + Nassun::new(opts)? + .resolve(spec) + .await? + .corgi_packument() + .await } +/// Resolves version metadata from the given package `spec`, using the default +/// resolution algorithm. +/// +/// This uses default `Nassun` options and does not cache the result. To +/// configure `Nassun`, and/or enable more efficient caching/reuse, look at +/// `Package#metadata` instead. #[wasm_bindgen] pub async fn metadata(spec: &str, opts: JsValue) -> Result { - JsNassun::new(opts)?.resolve(spec).await?.metadata().await + Nassun::new(opts)?.resolve(spec).await?.metadata().await +} + +/// Resolves a partial ("corgi") version of the version metadata from the +/// given package `spec`, using the default resolution algorithm. +/// +/// This uses default `Nassun` settings and does not cache the result. To +/// configure `Nassun`, and/or enable more efficient caching/reuse, look at +/// `Package#metadata` instead. +#[wasm_bindgen(js_name = "corgiMetadata")] +pub async fn corgi_metadata(spec: &str, opts: JsValue) -> Result { + Nassun::new(opts)? + .resolve(spec) + .await? + .corgi_metadata() + .await } +/// Resolves a tarball from the given package `spec`, using the +/// default resolution algorithm. This tarball will have its data checked +/// if the package metadata fetched includes integrity information. +/// +/// This uses default `Nassun` settings and does not cache the result. +/// To configure `Nassun`, and/or enable more efficient caching/reuse, +/// look at `Package#tarball` instead. #[wasm_bindgen] -pub async fn tarball(spec: &str, opts: JsValue) -> Result { - JsNassun::new(opts)?.resolve(spec).await?.tarball().await +pub async fn tarball( + spec: &str, + opts: JsValue, +) -> Result { + Nassun::new(opts)?.resolve(spec).await?.tarball().await } +/// Resolves to a `ReadableStream` of entries from the given package +/// `spec`, using the default resolution algorithm. The source tarball will +/// have its data checked if the package metadata fetched includes integrity +/// information. +/// +/// This uses default `Nassun` settings and does not cache the result. To +/// configure `Nassun`, and/or enable more efficient caching/reuse, look at +/// `Package#entries` instead. #[wasm_bindgen] -pub async fn entries(spec: &str, opts: JsValue) -> Result { - JsNassun::new(opts)?.resolve(spec).await?.entries().await +pub async fn entries( + spec: &str, + opts: JsValue, +) -> Result { + Nassun::new(opts)?.resolve(spec).await?.entries().await } -#[wasm_bindgen(js_name = NassunError)] -#[derive(Error, Debug)] -#[error("{0}")] -pub struct JsNassunError(#[from] NassunError); +/// Options for configuration for various `Nassun` operations. +#[derive(Debug, Deserialize, Tsify)] +#[allow(non_snake_case)] +struct NassunOpts { + /// Registry to use for unscoped packages, and as a default for scoped + /// packages. Defaults to `https://registry.npmjs.org/`. + #[tsify(optional)] + pub registry: Option, + /// A map of scope prefixes to registries. + #[tsify(optional)] + pub scopedRegistries: Option>, +} -#[wasm_bindgen(js_class = NassunError)] -impl JsNassunError { - #[wasm_bindgen(getter)] - pub fn code(&self) -> Option { - self.0.code().map(|c| c.to_string()) - } - - #[wasm_bindgen(js_name = toString)] - pub fn to_js_string(&self) -> String { - format!( - "JsNasunError({}: {})", - self.0 - .code() - .unwrap_or_else(|| Box::new("nassun::code_unavailable")), - self.0 - ) - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct JsNassunOpts { - use_corgi: Option, - registry: Option, - scoped_registries: Option>, -} - -#[wasm_bindgen(js_name = Nassun)] -pub struct JsNassun(Nassun); - -#[wasm_bindgen(js_class = Nassun)] -impl JsNassun { - #[wasm_bindgen(constructor, variadic)] - pub fn new(opts: JsValue) -> Result { - if opts.is_object() { - let mut opts_builder = NassunOpts::new(); - let opts: JsNassunOpts = serde_wasm_bindgen::from_value(opts) - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}"))))?; - if let Some(use_corgi) = opts.use_corgi { - opts_builder = opts_builder.use_corgi(use_corgi); - } +/// NPM package client used to resolve and fetch package data and metadata. +#[wasm_bindgen] +pub struct Nassun { + #[wasm_bindgen(skip)] + pub inner: crate::client::Nassun, +} + +impl Nassun { + fn new_inner(inner: crate::client::Nassun) -> Self { + Self { inner } + } +} + +#[wasm_bindgen] +impl Nassun { + /// Create a new Nassun instance with the given options. + #[wasm_bindgen(constructor)] + pub fn new(opts: JsValue) -> Result { + console_error_panic_hook::set_once(); + let mut opts_builder = crate::client::NassunOpts::new(); + let opts: Option = serde_wasm_bindgen::from_value(opts)?; + if let Some(opts) = opts { if let Some(registry) = opts.registry { - opts_builder = opts_builder.registry(registry); + opts_builder = opts_builder.registry(registry.parse()?); } - if let Some(scopes) = opts.scoped_registries { + if let Some(scopes) = opts.scopedRegistries { for (scope, registry) in scopes { - opts_builder = opts_builder.scope_registry(scope, registry); + opts_builder = opts_builder.scope_registry(scope, registry.parse()?); } } - Ok(JsNassun(opts_builder.build())) - } else { - Ok(JsNassun(Nassun::new())) } + Ok(Nassun::new_inner(opts_builder.build())) + } + + /// Resolve a spec (e.g. `foo@^1.2.3`, `github:foo/bar`, etc), to a + /// `Package` that can be used for further operations. + pub async fn resolve(&self, spec: &str) -> Result { + Ok(Package::from_core_package(self.inner.resolve(spec).await?)) } - pub async fn resolve(&self, spec: &str) -> Result { - Ok(JsPackage::from_core_package(self.0.resolve(spec).await?)) + /// Resolves a packument object for the given package `spec`. + pub async fn packument(&self, spec: &str) -> Result { + self.resolve(spec).await?.packument().await + } + + /// Resolves version metadata from the given package `spec`. + pub async fn metadata(&self, spec: &str) -> Result { + self.resolve(spec).await?.metadata().await + } + + /// Resolves a partial (corgi) version of the packument object for the + /// given package `spec`. + #[wasm_bindgen(js_name = "corgiPackument")] + pub async fn corgi_packument(&self, spec: &str) -> Result { + self.resolve(spec).await?.corgi_packument().await + } + + /// Resolves a partial (corgi) version of the version metadata from the + /// given package `spec`. + #[wasm_bindgen(js_name = "corgiMetadata")] + pub async fn corgi_metadata(&self, spec: &str) -> Result { + self.resolve(spec).await?.corgi_metadata().await + } + + /// Resolves a `ReadableStream` tarball from the given package + /// `spec`. This tarball will have its data checked if the package + /// metadata fetched includes integrity information. + pub async fn tarball(&self, spec: &str) -> Result { + self.resolve(spec).await?.tarball().await + } + + /// Resolves to a `ReadableStream` of entries from the given package + /// `spec`, using the default resolution algorithm. The source tarball will + /// have its data checked if the package metadata fetched includes integrity + /// information. + pub async fn entries(&self, spec: &str) -> Result { + self.resolve(spec).await?.entries().await } } -#[wasm_bindgen(js_name = Package)] -pub struct JsPackage { - from: JsValue, - name: JsValue, - resolved: JsValue, - package: Package, +/// A resolved package. A concrete version has been determined from its +/// PackageSpec by the version resolver. +#[wasm_bindgen] +pub struct Package { + #[wasm_bindgen(skip)] + pub from: JsValue, + #[wasm_bindgen(skip)] + pub name: JsValue, + #[wasm_bindgen(skip)] + pub resolved: JsValue, + package: crate::package::Package, serializer: serde_wasm_bindgen::Serializer, } -#[wasm_bindgen(js_class = Package)] -impl JsPackage { - pub fn from_core_package(package: RsPackage) -> Package { +impl Package { + pub fn from_core_package(package: crate::package::Package) -> Package { let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true); Package { from: JsValue::from_str(&package.from().to_string()), @@ -122,62 +252,79 @@ impl JsPackage { } } -#[wasm_bindgen(js_class = Package)] -impl JsPackage { +#[wasm_bindgen] +impl Package { + /// Original package spec that this `Package` was resolved from. #[wasm_bindgen(getter)] pub fn from(&self) -> JsValue { self.from.clone() } + /// Name of the package, as it should be used in the dependency graph. #[wasm_bindgen(getter)] pub fn name(&self) -> JsValue { self.name.clone() } + /// The package resolution information that this `Package` was created from. #[wasm_bindgen(getter)] pub fn resolved(&self) -> JsValue { self.resolved.clone() } + /// The partial (corgi) version of the packument that this `Package` was + /// resolved from. + #[wasm_bindgen(js_name = "corgiPackument")] + pub async fn corgi_packument(&self) -> Result { + Ok(self + .package + .corgi_packument() + .await? + .serialize(&self.serializer)?) + } + + /// The partial (corgi) version of the version metadata, aka roughly the + /// metadata defined in `package.json`. + #[wasm_bindgen(js_name = "corgiMetadata")] + pub async fn corgi_metadata(&self) -> Result { + Ok(self + .package + .corgi_metadata() + .await? + .serialize(&self.serializer)?) + } + + /// The full packument that this `Package` was resolved from. pub async fn packument(&self) -> Result { - self.package + Ok(self + .package .packument() - .await - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}"))))? - .serialize(&self.serializer) - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}")))) + .await? + .serialize(&self.serializer)?) } + /// The version metadata, aka roughly the metadata defined in + /// `package.json`. pub async fn metadata(&self) -> Result { - self.package - .metadata() - .await - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}"))))? - .serialize(&self.serializer) - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}")))) + Ok(self.package.metadata().await?.serialize(&self.serializer)?) } - pub async fn tarball(&self) -> Result { - let tarball = self - .package - .tarball() - .await - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}"))))?; - Ok(ReadableStream::from_async_read(tarball, 1024) - .into_raw() - .into()) + /// A `ReadableStream` tarball for this package. This tarball + /// will have its data checked if the package metadata fetched includes + /// integrity information. + pub async fn tarball(&self) -> Result { + Ok(ReadableStream::from_async_read(self.package.tarball().await?, 1024).into_raw()) } - pub async fn entries(&self) -> Result { - let entries = self - .package - .entries() - .await - .map_err(|e| JsNassunError(NassunError::MiscError(format!("{e}"))))? - .then(|entry| async move { - entry - .map_err(|e| JsValue::from_str(&format!("{e}"))) - .and_then(|entry| { + /// A `ReadableStream` of entries for this package. The source + /// tarball will have its data checked if the package metadata fetched + /// includes integrity information. + pub async fn entries(&self) -> Result { + let entries = self.package.entries().await?.then(|entry| async move { + entry + .map_err(|e| e.into()) + .and_then( + |entry: crate::entries::Entry| -> std::result::Result { let header = entry.header(); let obj = js_sys::Object::new(); js_sys::Reflect::set( @@ -190,7 +337,13 @@ impl JsPackage { &"mtime".into(), &header .mtime() - .map_err(|e| JsValue::from_str(&format!("{e}")))? + .map(|mut x| { + if x > (u32::MAX as u64) { + x = u32::MAX as u64; + } + x as u32 + }) + .map_err(|e| -> NassunError { e.into() })? .into(), )?; js_sys::Reflect::set( @@ -198,18 +351,19 @@ impl JsPackage { &"size".into(), &header .entry_size() - .map_err(|e| JsValue::from_str(&format!("{e}")))? + .map(|mut x| { + if x > (u32::MAX as u64) { + x = u32::MAX as u64; + } + x as u32 + }) + .map_err(|e| -> NassunError { e.into() })? .into(), )?; js_sys::Reflect::set( &obj, &"path".into(), - &header - .path() - .map_err(|e| JsValue::from_str(&format!("{e}")))? - .to_string_lossy() - .into_owned() - .into(), + &entry.path()?.to_string_lossy().into_owned().into(), )?; js_sys::Reflect::set( &obj, @@ -219,8 +373,10 @@ impl JsPackage { .into(), )?; Ok(obj.into()) - }) - }); - Ok(ReadableStream::from_stream(entries).into_raw().into()) + }, + ) + .map_err(|e| e.into()) + }); + Ok(ReadableStream::from_stream(entries).into_raw()) } } diff --git a/crates/node-maintainer/Cargo.toml b/crates/node-maintainer/Cargo.toml index fd34b11f..26be8111 100644 --- a/crates/node-maintainer/Cargo.toml +++ b/crates/node-maintainer/Cargo.toml @@ -39,6 +39,7 @@ console_error_panic_hook = { workspace = true } js-sys = { workspace = true } serde = { workspace = true } serde-wasm-bindgen = { workspace = true } +tsify = { workspace = true } wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } diff --git a/crates/node-maintainer/src/error.rs b/crates/node-maintainer/src/error.rs index ff9aea8f..f592d4e5 100644 --- a/crates/node-maintainer/src/error.rs +++ b/crates/node-maintainer/src/error.rs @@ -82,6 +82,12 @@ pub enum NodeMaintainerError { #[diagnostic(code(node_maintainer::kdl::invalid_lockfile_version))] InvalidLockfileVersion, + /// Error from serde_wasm_bindgen + #[cfg(target_arch = "wasm32")] + #[error(transparent)] + #[diagnostic(code(node_maintainer::serde_wasm_bindgen::error))] + SerdeWasmBindgenError(#[from] serde_wasm_bindgen::Error), + /// Generic package spec error. #[error(transparent)] #[diagnostic(transparent)] @@ -95,7 +101,7 @@ pub enum NodeMaintainerError { /// Generic error returned from Nassun. #[error(transparent)] #[diagnostic(transparent)] - NassunError(#[from] nassun::NassunError), + NassunError(#[from] nassun::error::NassunError), /// Generic serde_json error. #[error(transparent)] diff --git a/crates/node-maintainer/src/graph.rs b/crates/node-maintainer/src/graph.rs index ad28225b..daf1d563 100644 --- a/crates/node-maintainer/src/graph.rs +++ b/crates/node-maintainer/src/graph.rs @@ -6,14 +6,15 @@ use std::{ }; use kdl::KdlDocument; -use nassun::{Package, PackageResolution}; +use nassun::{package::Package, PackageResolution}; use petgraph::{ dot::Dot, stable_graph::{EdgeIndex, NodeIndex, StableGraph}, }; use unicase::UniCase; -use crate::{DepType, Edge, Lockfile, LockfileNode, Node, NodeMaintainerError}; +use crate::{error::NodeMaintainerError, DepType, Edge, Lockfile, LockfileNode, Node}; + #[cfg(debug_assertions)] use NodeMaintainerError::GraphValidationError; @@ -177,6 +178,9 @@ impl Graph { break; } } + if current == Some(self.root) { + return Ok(None); + } Ok(current.map(|idx| self.inner[idx].package.clone())) } diff --git a/crates/node-maintainer/src/into_kdl.rs b/crates/node-maintainer/src/into_kdl.rs index 11086b9f..02e015cc 100644 --- a/crates/node-maintainer/src/into_kdl.rs +++ b/crates/node-maintainer/src/into_kdl.rs @@ -1,6 +1,6 @@ use kdl::KdlDocument; -use crate::NodeMaintainerError; +use crate::error::NodeMaintainerError; pub trait IntoKdl: IntoKdlSealed {} diff --git a/crates/node-maintainer/src/lib.rs b/crates/node-maintainer/src/lib.rs index 3442962f..3caaa60c 100644 --- a/crates/node-maintainer/src/lib.rs +++ b/crates/node-maintainer/src/lib.rs @@ -1,6 +1,10 @@ //! An NPM dependency resolver for building `node_modules/` trees and //! extracting them to their final resting place. +pub use nassun::Nassun; +#[cfg(not(target_arch = "wasm32"))] +pub use nassun::{NassunError, NassunOpts}; + pub use edge::*; pub use error::*; pub use graph::*; diff --git a/crates/node-maintainer/src/lockfile.rs b/crates/node-maintainer/src/lockfile.rs index d3e350fe..f177b5b6 100644 --- a/crates/node-maintainer/src/lockfile.rs +++ b/crates/node-maintainer/src/lockfile.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use kdl::{KdlDocument, KdlNode}; -use nassun::{Nassun, Package, PackageResolution}; +use nassun::{client::Nassun, package::Package, PackageResolution}; use node_semver::Version; use oro_common::CorgiManifest; use oro_package_spec::PackageSpec; @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use ssri::Integrity; use unicase::UniCase; -use crate::{DepType, IntoKdl, NodeMaintainerError}; +use crate::{error::NodeMaintainerError, DepType, IntoKdl}; /// A representation of a resolved lockfile. #[derive(Default, Debug, Clone, PartialEq, Eq)] diff --git a/crates/node-maintainer/src/maintainer.rs b/crates/node-maintainer/src/maintainer.rs index 46f1f22f..b88ae83c 100644 --- a/crates/node-maintainer/src/maintainer.rs +++ b/crates/node-maintainer/src/maintainer.rs @@ -1,16 +1,24 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet, VecDeque}; -use std::path::{Path, PathBuf}; +use std::path::Path; +#[cfg(not(target_arch = "wasm32"))] +use std::path::PathBuf; +#[cfg(not(target_arch = "wasm32"))] use std::sync::atomic::{self, AtomicUsize}; #[cfg(not(target_arch = "wasm32"))] use async_std::fs; use async_std::sync::{Arc, Mutex}; +#[cfg(not(target_arch = "wasm32"))] use colored::*; -use futures::{StreamExt, TryFutureExt, TryStreamExt}; +#[cfg(not(target_arch = "wasm32"))] +use futures::TryStreamExt; +use futures::{StreamExt, TryFutureExt}; #[cfg(not(target_arch = "wasm32"))] use indicatif::{ProgressBar, ProgressStyle}; -use nassun::{Nassun, NassunOpts, Package, PackageSpec}; +use nassun::client::{Nassun, NassunOpts}; +use nassun::package::Package; +use nassun::PackageSpec; use oro_common::{CorgiManifest, CorgiVersionMetadata}; use petgraph::stable_graph::NodeIndex; use petgraph::visit::EdgeRef; @@ -22,12 +30,12 @@ use crate::edge::{DepType, Edge}; use crate::error::NodeMaintainerError; use crate::{Graph, IntoKdl, Lockfile, LockfileNode, Node}; -const DEFAULT_PARALLELISM: usize = 50; +const DEFAULT_CONCURRENCY: usize = 50; #[derive(Debug, Clone)] pub struct NodeMaintainerOptions { nassun_opts: NassunOpts, - parallelism: usize, + concurrency: usize, kdl_lock: Option, npm_lock: Option, @@ -50,14 +58,15 @@ impl NodeMaintainerOptions { self } + #[cfg(not(target_arch = "wasm32"))] pub fn cache(mut self, cache: impl AsRef) -> Self { self.nassun_opts = self.nassun_opts.cache(PathBuf::from(cache.as_ref())); self.cache = Some(PathBuf::from(cache.as_ref())); self } - pub fn parallelism(mut self, parallelism: usize) -> Self { - self.parallelism = parallelism; + pub fn concurrency(mut self, concurrency: usize) -> Self { + self.concurrency = concurrency; self } @@ -83,6 +92,7 @@ impl NodeMaintainerOptions { self } + #[cfg(not(target_arch = "wasm32"))] pub fn base_dir(mut self, path: impl AsRef) -> Self { self.nassun_opts = self.nassun_opts.base_dir(path); self @@ -113,7 +123,7 @@ impl NodeMaintainerOptions { let mut nm = NodeMaintainer { nassun, graph: Default::default(), - parallelism: DEFAULT_PARALLELISM, + concurrency: DEFAULT_CONCURRENCY, #[cfg(not(target_arch = "wasm32"))] progress_bar: self.progress_bar, cache: self.cache, @@ -136,7 +146,7 @@ impl NodeMaintainerOptions { let mut nm = NodeMaintainer { nassun, graph: Default::default(), - parallelism: DEFAULT_PARALLELISM, + concurrency: DEFAULT_CONCURRENCY, #[cfg(not(target_arch = "wasm32"))] progress_bar: self.progress_bar, cache: self.cache, @@ -156,7 +166,7 @@ impl Default for NodeMaintainerOptions { fn default() -> Self { NodeMaintainerOptions { nassun_opts: Default::default(), - parallelism: DEFAULT_PARALLELISM, + concurrency: DEFAULT_CONCURRENCY, kdl_lock: None, npm_lock: None, #[cfg(not(target_arch = "wasm32"))] @@ -178,7 +188,7 @@ struct NodeDependency { pub struct NodeMaintainer { nassun: Nassun, graph: Graph, - parallelism: usize, + concurrency: usize, #[cfg(not(target_arch = "wasm32"))] progress_bar: bool, cache: Option, @@ -190,12 +200,14 @@ impl NodeMaintainer { NodeMaintainerOptions::new() } + #[cfg(not(target_arch = "wasm32"))] pub async fn resolve_manifest( root: CorgiManifest, ) -> Result { Self::builder().resolve_manifest(root).await } + #[cfg(not(target_arch = "wasm32"))] pub async fn resolve_spec( root_spec: impl AsRef, ) -> Result { @@ -214,6 +226,7 @@ impl NodeMaintainer { Ok(()) } + #[cfg(not(target_arch = "wasm32"))] pub fn to_lockfile(&self) -> Result { self.graph.to_lockfile() } @@ -222,6 +235,7 @@ impl NodeMaintainer { self.graph.to_kdl() } + #[cfg(not(target_arch = "wasm32"))] pub fn render(&self) -> String { self.graph.render() } @@ -230,6 +244,7 @@ impl NodeMaintainer { self.graph.package_at_path(path) } + #[cfg(not(target_arch = "wasm32"))] pub async fn extract_to(&self, path: impl AsRef) -> Result<(), NodeMaintainerError> { #[cfg(not(target_arch = "wasm32"))] let pb = if self.progress_bar { @@ -262,7 +277,7 @@ impl NodeMaintainer { stream .map(|idx| Ok((idx, concurrent_count.clone(), total_completed.clone()))) .try_for_each_concurrent( - me.parallelism, + me.concurrency, move |(child_idx, concurrent_count, total_completed)| async move { if child_idx == me.graph.root { return Ok(()); @@ -329,6 +344,7 @@ impl NodeMaintainer { &mut self, lockfile: Option, ) -> Result<(), NodeMaintainerError> { + #[cfg(not(target_arch = "wasm32"))] let start = std::time::Instant::now(); #[cfg(not(target_arch = "wasm32"))] let pb = if self.progress_bar { @@ -378,8 +394,8 @@ impl NodeMaintainer { }) .filter_map(|maybe_spec| maybe_spec) .map(|spec| self.nassun.resolve(spec.clone()).map_ok(move |p| (p, spec))) - .buffer_unordered(self.parallelism) - .ready_chunks(self.parallelism); + .buffer_unordered(self.concurrency) + .ready_chunks(self.concurrency); // Start iterating over the queue. We'll be adding things to it as we find them. while !q.is_empty() || in_flight != 0 { @@ -412,13 +428,13 @@ impl NodeMaintainer { let requested = format!("{}@{}", dep.name, dep.spec).parse()?; - if let Some(child_idx) = Self::satisfy_dependency(&mut self.graph, &dep)? { + if let Some(_child_idx) = Self::satisfy_dependency(&mut self.graph, &dep)? { #[cfg(not(target_arch = "wasm32"))] { pb.inc(1); pb.set_message(format!( "{:?}", - self.graph[child_idx].package.resolved() + self.graph[_child_idx].package.resolved() )); }; } @@ -490,10 +506,12 @@ impl NodeMaintainer { let CorgiVersionMetadata { manifest, + #[cfg(not(target_arch = "wasm32"))] deprecated, .. } = &package.corgi_metadata().await?; + #[cfg(not(target_arch = "wasm32"))] if let Some(deprecated) = deprecated { pb.suspend(|| { tracing::warn!( @@ -511,7 +529,7 @@ impl NodeMaintainer { } for dep in deps { - if let Some(child_idx) = + if let Some(_child_idx) = Self::satisfy_dependency(&mut self.graph, &dep)? { #[cfg(not(target_arch = "wasm32"))] @@ -519,7 +537,7 @@ impl NodeMaintainer { pb.inc(1); pb.set_message(format!( "{:?}", - self.graph[child_idx].package.resolved() + self.graph[_child_idx].package.resolved() )); }; continue; @@ -571,6 +589,7 @@ impl NodeMaintainer { println!("🔍 Resolved!"); }; + #[cfg(not(target_arch = "wasm32"))] tracing::info!( "Resolved graph of {} nodes in {}ms", self.graph.inner.node_count(), diff --git a/crates/node-maintainer/src/node.rs b/crates/node-maintainer/src/node.rs index 17b49b84..b8f6364b 100644 --- a/crates/node-maintainer/src/node.rs +++ b/crates/node-maintainer/src/node.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use nassun::Package; +use nassun::package::Package; use oro_common::CorgiManifest; use petgraph::stable_graph::{EdgeIndex, NodeIndex}; use unicase::UniCase; diff --git a/crates/node-maintainer/src/wasm.rs b/crates/node-maintainer/src/wasm.rs index 1683d71c..aa2f7a2c 100644 --- a/crates/node-maintainer/src/wasm.rs +++ b/crates/node-maintainer/src/wasm.rs @@ -1,104 +1,177 @@ use std::{collections::HashMap, path::Path}; +use miette::Diagnostic; use nassun::Package; use serde::Deserialize; -use thiserror::Error; -use url::Url; +use tsify::Tsify; use wasm_bindgen::prelude::*; -use crate::{NodeMaintainer, NodeMaintainerError, NodeMaintainerOptions}; +use crate::error::NodeMaintainerError; -type Result = std::result::Result; +type Result = std::result::Result; -#[derive(Error, Debug)] -#[error("{0}")] -#[wasm_bindgen(js_name = NodeMaintainerError)] -pub struct JsNodeMaintainerError(#[from] NodeMaintainerError); +#[wasm_bindgen(typescript_custom_section)] +const TS_APPEND_CONTENT: &'static str = r#" +export interface NodeMaintainerError { + message: string; + code?: string; +} + +export interface PackageJson { + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + optionalDependencies?: Record; + bundledDependencies?: string[]; +} +"#; -#[derive(Debug, Deserialize)] -#[wasm_bindgen(js_name = NodeMaintainerOptions)] -pub struct JsNodeMaintainerOptions { - registry: Option, - scoped_registries: Option>, +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "PackageJson")] + pub type PackageJson; +} + +impl From for JsValue { + fn from(e: NodeMaintainerError) -> Self { + let obj = js_sys::Object::new(); + let msg = format!("{e}"); + js_sys::Reflect::set(&obj, &"message".into(), &JsValue::from_str(&msg)) + .expect(&format!("failed to set error message: {e}")); + if let Some(code) = e.code() { + let code = format!("{code}"); + js_sys::Reflect::set(&obj, &"code".into(), &JsValue::from_str(&code)) + .expect(&format!("failed to set error code: {e:#?}")); + } + obj.into() + } } -#[wasm_bindgen(js_name = NodeMaintainer)] -pub struct JsNodeMaintainer(NodeMaintainer); +/// Options for configuration for various `NodeMaintainer` operations. +#[derive(Tsify, Debug, Deserialize)] +#[allow(non_snake_case)] +#[wasm_bindgen] +pub struct NodeMaintainerOptions { + #[tsify(optional)] + registry: Option, + #[tsify(optional)] + scopedRegistries: Option>, + #[tsify(optional)] + concurrency: Option, + #[tsify(optional)] + kdlLock: Option, + #[tsify(optional)] + npmLock: Option, + #[tsify(optional)] + defaultTag: Option, +} + +/// An NPM-compatible dependency resolver. NodeMaintainer builds trees of +/// package nodes that can be used to generate lockfiles or fetch package +/// tarballs, or even extract them to where they would live in `node_modules`. +#[derive(Tsify)] +#[wasm_bindgen] +pub struct NodeMaintainer { + #[wasm_bindgen(skip)] + pub inner: crate::maintainer::NodeMaintainer, +} + +impl NodeMaintainer { + fn new(inner: crate::maintainer::NodeMaintainer) -> Self { + Self { inner } + } +} -#[wasm_bindgen(js_class = NodeMaintainer)] -impl JsNodeMaintainer { - fn opts_from_js_value(opts: JsValue) -> Result { +#[wasm_bindgen] +impl NodeMaintainer { + fn opts_from_js_value(opts: JsValue) -> Result { console_error_panic_hook::set_once(); - let mut opts_builder = NodeMaintainerOptions::new(); - let mut opts: Vec = serde_wasm_bindgen::from_value(opts) - .map_err(|e| JsNodeMaintainerError(NodeMaintainerError::MiscError(format!("{e}"))))?; - if let Some(opts) = opts.pop() { + let mut opts_builder = crate::maintainer::NodeMaintainer::builder(); + let opts: Option = serde_wasm_bindgen::from_value(opts)?; + if let Some(opts) = opts { if let Some(registry) = opts.registry { - opts_builder = opts_builder.registry(registry); + opts_builder = opts_builder.registry( + registry + .parse() + .map_err(|e| NodeMaintainerError::UrlParseError(registry, e))?, + ); } - if let Some(scopes) = opts.scoped_registries { + if let Some(scopes) = opts.scopedRegistries { for (scope, registry) in scopes { - opts_builder = opts_builder.scope_registry(scope, registry); + opts_builder = opts_builder.scope_registry( + scope, + registry + .parse() + .map_err(|e| NodeMaintainerError::UrlParseError(registry, e))?, + ); } } + if let Some(concurrency) = opts.concurrency { + opts_builder = opts_builder.concurrency(concurrency); + } + if let Some(kdl_lock) = opts.kdlLock { + opts_builder = opts_builder.kdl_lock(kdl_lock)?; + } + if let Some(npm_lock) = opts.npmLock { + opts_builder = opts_builder.npm_lock(npm_lock)?; + } + if let Some(default_tag) = opts.defaultTag { + opts_builder = opts_builder.default_tag(default_tag); + } } Ok(opts_builder) } - #[wasm_bindgen(variadic)] - pub async fn resolve(spec: &str, opts: JsValue) -> Result { + /// Resolves a dependency tree using `spec` as the root package. + #[wasm_bindgen(js_name = "resolveSpec")] + pub async fn resolve_spec(spec: &str, opts: JsValue) -> Result { + console_error_panic_hook::set_once(); let opts_builder = Self::opts_from_js_value(opts)?; opts_builder - .resolve(spec) + .resolve_spec(spec) .await - .map(JsNodeMaintainer) - .map_err(JsNodeMaintainerError) + .map(NodeMaintainer::new) } - #[wasm_bindgen(variadic)] - pub async fn from_manifest(manifest: JsValue, opts: JsValue) -> Result { - let manifest = serde_wasm_bindgen::from_value(manifest) - .map_err(|e| JsNodeMaintainerError(NodeMaintainerError::MiscError(format!("{e}"))))?; + /// Returns a dependency tree using a `package.json` manifest as the root + /// package. + #[wasm_bindgen(js_name = "resolveManifest")] + pub async fn resolve_manifest(manifest: PackageJson, opts: JsValue) -> Result { + console_error_panic_hook::set_once(); + let manifest = serde_wasm_bindgen::from_value(manifest.into())?; let opts_builder = Self::opts_from_js_value(opts)?; opts_builder - .from_manifest(manifest) + .resolve_manifest(manifest) .await - .map(JsNodeMaintainer) - .map_err(JsNodeMaintainerError) + .map(NodeMaintainer::new) } + /// Returns the contents of a package-lock.kdl lockfile for this resolved tree. + #[wasm_bindgen(js_name = "toKdl")] pub fn to_kdl(&self) -> Result { - Ok(self.0.to_kdl()?.to_string()) + Ok(self.inner.to_kdl()?.to_string()) } + /// Given a path within node_modules, returns the package that the + /// referenced file/directory belongs to. + #[wasm_bindgen(js_name = "packageAtPath")] pub fn package_at_path(&self, path: &str) -> Result> { Ok(self - .0 - .package_at_path(Path::new(path)) - .map_err(JsNodeMaintainerError)? + .inner + .package_at_path(Path::new(path))? .map(Package::from_core_package)) } } -#[wasm_bindgen(variadic)] -pub async fn resolve(spec: &str, opts: JsValue) -> Result { - console_error_panic_hook::set_once(); - let mut opts_builder = NodeMaintainerOptions::new(); - if opts.is_object() { - let opts: JsNodeMaintainerOptions = serde_wasm_bindgen::from_value(opts) - .map_err(|e| JsNodeMaintainerError(NodeMaintainerError::MiscError(format!("{e}"))))?; - if let Some(registry) = opts.registry { - opts_builder = opts_builder.registry(registry); - } - if let Some(scopes) = opts.scoped_registries { - for (scope, registry) in scopes { - opts_builder = opts_builder.scope_registry(scope, registry); - } - } - } - opts_builder - .resolve(spec) - .await - .map(JsNodeMaintainer) - .map_err(JsNodeMaintainerError) +/// Resolves a dependency tree using `spec` as the root package. +#[wasm_bindgen(js_name = "resolveSpec")] +pub async fn resolve_spec(spec: &str, opts: JsValue) -> Result { + NodeMaintainer::resolve_spec(spec, opts).await +} + +/// Returns a dependency tree using a `package.json` manifest as the root +/// package. +#[wasm_bindgen(js_name = "resolveManifest")] +pub async fn resolve_manifest(manifest: PackageJson, opts: JsValue) -> Result { + NodeMaintainer::resolve_manifest(manifest, opts).await } diff --git a/crates/node-maintainer/tests/resolver_basic.rs b/crates/node-maintainer/tests/resolver_basic.rs index 82bf1bc7..24f2da06 100644 --- a/crates/node-maintainer/tests/resolver_basic.rs +++ b/crates/node-maintainer/tests/resolver_basic.rs @@ -40,7 +40,7 @@ async fn basic_flatten() -> Result<()> { "#; mocks_from_kdl(&mock_server, mock_data.parse()?).await; let nm = NodeMaintainer::builder() - .parallelism(1) + .concurrency(1) .registry(mock_server.uri().parse().into_diagnostic()?) .resolve_spec("a@^1") .await?; @@ -116,7 +116,7 @@ async fn nesting_simple_conflict() -> Result<()> { "#; mocks_from_kdl(&mock_server, mock_data.parse()?).await; let nm = NodeMaintainer::builder() - .parallelism(1) + .concurrency(1) .registry(mock_server.uri().parse().into_diagnostic()?) .resolve_spec("a@^1") .await?; @@ -198,7 +198,7 @@ async fn nesting_sibling_conflict() -> Result<()> { "#; mocks_from_kdl(&mock_server, mock_data.parse()?).await; let nm = NodeMaintainer::builder() - .parallelism(1) + .concurrency(1) .registry(mock_server.uri().parse().into_diagnostic()?) .resolve_spec("a@^1") .await?; diff --git a/crates/oro-client/src/api/packument.rs b/crates/oro-client/src/api/packument.rs index 84c16c49..ce22a006 100644 --- a/crates/oro-client/src/api/packument.rs +++ b/crates/oro-client/src/api/packument.rs @@ -64,7 +64,7 @@ impl OroClient { } } -#[cfg(test)] +#[cfg(all(test, not(target_arch = "wasm32")))] mod test { use maplit::{btreemap, hashmap}; use miette::{IntoDiagnostic, Result};