Skip to main content

npm_utils/
lib.rs

1//! Pure-Rust utilities for the npm registry and web assets.
2//!
3//! Building blocks for fetching browser/JS dependencies at build time without
4//! Node or npm:
5//!
6//! - [`registry`] — talk to an npm registry: build tarball URLs, fetch a
7//!   package's metadata, and resolve the newest version matching a semver range.
8//! - [`download`] — fetch bytes over HTTP (with a retry) and build GitHub
9//!   archive URLs.
10//! - [`extract`] — unpack `.tar.gz` and `.zip` archives into a destination
11//!   directory, selecting all files, an explicit file map, or a predicate, with
12//!   path-traversal protection.
13//! - [`path_safety`] — the path-traversal hardening shared by `extract` and
14//!   `install`: reject `..`/absolute paths and refuse symlink-redirected writes.
15//! - [`cache`] — content-hash markers, a cross-process build lock, and directory
16//!   helpers for skip-if-unchanged download caches.
17//! - [`package_json`] — read pinned dependency versions from a `package.json`, and
18//!   resolve its `exports`/`module`/`browser`/`main` to browser entry points (for
19//!   generating an ES-module import map).
20//! - [`install`] — produce a real `node_modules/` directory, pure Rust, with every tarball
21//!   sha512-verified: resolve a `package.json`'s transitive `dependencies` against the registry
22//!   ([`install::node_modules`]), or install the exact tree a `package-lock.json` pins —
23//!   devDependencies included, `.bin` shims and all — an `npm ci` in Rust
24//!   ([`install::from_lockfile`]).
25//! - [`integrity`] — verify a downloaded tarball's `sha512` Subresource-Integrity (both
26//!   install paths check it before trusting bytes).
27//! - [`sbom`] — render the packages a `package-lock.json` pins as a license summary, a CycloneDX
28//!   1.6 document, or an SPDX 2.3 document — compliance artifacts, pure Rust, no Node.
29//!
30//! ```no_run
31//! use npm_utils::{download, extract, registry::Registry};
32//!
33//! # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
34//! let reg = Registry::npm();
35//! let lit = reg.resolve("lit", &"^3".parse()?)?;
36//! let tgz = download::fetch(&lit.tarball_url)?;
37//! extract::tar_gz(&tgz, "dist/lit".as_ref(), Some("package/"), extract::Select::All)?;
38//! # Ok(()) }
39//! ```
40
41#![forbid(unsafe_code)]
42
43/// The crate's boxed, thread-safe error type. A single alias so the whole crate shares one error
44/// spelling, errors cross thread boundaries, and a future switch to a structured enum is one edit.
45pub type Error = Box<dyn std::error::Error + Send + Sync>;
46/// The crate's result type, defaulting the error to [`Error`].
47pub type Result<T, E = Error> = std::result::Result<T, E>;
48
49pub mod cache;
50// The command-line tool (`npm-utils` / `cargo npm-utils`), behind the `cli` feature so a default
51// library build pulls no `clap`. Drives the primitives below — `registry`, `install`, and the
52// `package_json` manifest/lock writers — for `install`/`ci`/`add`/`init`/`upgrade`.
53#[cfg(feature = "cli")]
54pub mod cli;
55pub mod download;
56pub mod extract;
57pub mod install;
58pub mod integrity;
59// The npm `package.json` / `package-lock.json` schemas — a pure-parsing module (no IO),
60// modeled on the npm specs, with strict spec-conformance tests living beside it.
61pub mod package_json;
62pub mod path_safety;
63pub mod registry;
64// License/SBOM output (license summary · CycloneDX · SPDX) for a parsed `package-lock.json`.
65pub mod sbom;