npm.io
0.1.1 • Published yesterday

rs-semver-linux-x64

Licence
MIT
Version
0.1.1
Deps
0
Size
730 kB
Vulns
0
Weekly
0
Stars
1

rs-semver

A drop-in, Rust-powered replacement for the semver npm package — same API you already use (satisfies, inc, sort, SemVer, Range, ...), backed by a Rust engine instead of regex.

npm install rs-semver
const semver = require('rs-semver')

semver.satisfies('1.2.3', '^1.2.0') // true
semver.inc('1.2.3', 'minor')        // '1.3.0'
semver.sort(['1.2.3', '1.0.0', '2.0.0']) // ['1.0.0', '1.2.3', '2.0.0']

If you already use semver in a Node project, this is meant to be a straight swap — same function names, same arguments, same behavior (checked against semver's own test suite — see Project status below).

Why

Most of what a range check does — satisfies, sort, maxSatisfying — is CPU-bound comparison work, and doing it in Rust instead of JavaScript is meaningfully faster once you're checking a lot of versions (think: a package manager resolving a whole dependency tree). Measured against the real semver package on the same machine, same data:

Operation Speedup
satisfies, 10,000 checks 4.2x faster
sort, 1,000 versions 11.9x faster
maxSatisfying, 500 candidates × 50 ranges 2.0x faster

(Full numbers, methodology, and one honest exception in Benchmarks below.)

Also available as

  • A CLI, matching the semver command-line tool's flags exactly — see CLI.
  • A Rust crate (rs-semver-core), if you want the engine without Node at all — see Using it from Rust.

More examples

const semver = require('rs-semver')

// classes, same shape as the original semver package
const v = new semver.SemVer('1.2.3-beta.1')
v.major        // 1
v.prerelease   // ['beta', '1']

const r = new semver.Range('^1.2.0')
r.test('1.5.0') // true

// import just what you need, same subpaths as `semver`
const valid = require('rs-semver/functions/valid')
const SemVer = require('rs-semver/classes/semver')

CLI

cargo build --release -p rs-semver-cli
./target/release/rs-semver -r "^1.2.0" 1.2.3 2.0.0 1.5.0
# 1.2.3
# 1.5.0

Flags match semver's CLI (-r/--range, -i/--increment, --preid, -l/--loose, -p/--include-prerelease, -c/--coerce, --rtl/--ltr, -n <base>) — verified byte-for-byte against the real CLI.

Using it from Rust

use rs_semver_core::{satisfies, Options};

assert!(satisfies("1.2.3", "^1.2.0", Options::default()).unwrap());

rs-semver-core is a standalone, zero-dependency crate — it doesn't need Node.js at all. See crates/core/src/lib.rs for the full function list; it mirrors the npm package's API (valid, clean, inc, diff, the compare family, satisfies and the rest of the range functions, sort/rsort, coerce), plus SemVer, Comparator, and Range types.

Project status

This was built from scratch and checked against node-semver's own test suite (the real fixtures from v7.8.5, not hand-picked cases) — parsing, precedence, every range type, version increments, and more. All pass.

A few parts are intentionally simpler than the original and are called out in the source with a ponytail: comment where they're defined:

  • coerce() (pulling a version out of a messy string like v1.2.3.4.5) covers the common cases but isn't a full port of the original's regex.
  • subset() and simplifyRange() handle typical ranges well but not every exotic multi-range combination the original supports.
  • intersects() won't catch one rare edge case (two ranges that only overlap via a shared prerelease tag).

Not built yet: a browser/WASM build, prebuilt binaries for every OS (see CI/CD), and publishing the Rust crates to crates.io.

Contributor / internals details (repo layout, building from source, benchmarks, CI/CD)

Repo layout

crates/
  core/    rs-semver-core — the actual semver engine, zero dependencies
  cli/     rs-semver-cli  — CLI binary (crates.io deps: none beyond core)
  napi/    rs-semver-napi — NAPI-RS bindings, thin wrapper over core
packages/
  rs-semver/  npm package: classes/, functions/ (per-symbol require paths,
              matching node-semver's layout) + index.js (full API surface)

Building from source

# Rust library + CLI
cargo build --release
cargo test
cargo bench -p rs-semver-core

# Node bindings
cd packages/rs-semver
npm run build   # cargo builds the addon, copies it to rs-semver.node
node -e "console.log(require('.').satisfies('1.2.3', '^1.2.0'))"

CI/CD

  • .github/workflows/ci.yml — runs on every push/PR to main: cargo build/test/clippy across the workspace, plus a Node smoke test that builds the addon in debug mode and exercises it.
  • .github/workflows/release.yml — triggered by pushing a version tag (git tag v0.1.1 && git push origin v0.1.1). Builds the NAPI addon for macOS (arm64), Linux (x64 + arm64, glibc), and Windows (x64), publishes each as its own rs-semver-<platform>-<arch> npm package, then publishes the main rs-semver package with those as optionalDependencies — the same prebuilt-binary pattern used by sharp, esbuild, @swc/core, etc. Needs an NPM_TOKEN (npm "Automation" access token) added as a repo secret before it'll work.
  • macOS x64 (Intel) isn't built — GitHub's hosted Intel Mac runner pool (macos-13) sat queued for 25+ minutes with no runner assigned on the first real release run, so that leg was dropped rather than block every future release on it. Apple hasn't sold an Intel Mac since 2023; add the leg back (see the release.yml history) if it turns out to matter for real users.
  • Only builds glibc Linux binaries; musl (Alpine) isn't covered.
  • crates.io publishing isn't wired up — cargo publish manually if you want the Rust crates available outside this repo.

Benchmarks

Measured on a single Apple Silicon (arm64) dev machine, one run each, cargo build --release. Not a controlled, multi-machine, statistically rigorous benchmark suite — treat these as directional, and reproduce them yourself before relying on them (commands below).

Pure Rust core (cargo bench -p rs-semver-core, criterion, 100 samples each):

Benchmark Median time
Parse 10,000 random versions 1.10 ms
satisfies on 10,000 (version, range) pairs 11.2 ms
Sort 1,000 versions 322 µs
maxSatisfying, 500 candidates × 50 ranges 2.88 ms

Node.js: rs-semver (via NAPI) vs. the real semver package (benchmarks/compare.js, same generated data for both, best-of-5, identical process):

Benchmark rs-semver node-semver speedup
Parse 10,000 versions 6.05 ms 2.95 ms 0.5x (slower)
satisfies, 10,000 pairs 9.37 ms 38.95 ms 4.2x
Sort 1,000 versions 0.42 ms 4.93 ms 11.9x
maxSatisfying, 500 × 50 4.84 ms 9.81 ms 2.0x

The parse result is the honest finding here, and it matches a risk called out in the original design doc ("NAPI-RS binding overhead negates gains"): calling parse 10,000 times each returns a full SemVer class instance across the FFI boundary, and that per-call marshaling cost currently outweighs what the Rust parser saves — the original package's regex-based parser is faster end to end for this specific access pattern. satisfies/sort/maxSatisfying win because they return a primitive (bool/string/array) per call and do more work internally, so the FFI cost is amortized. A real integration (e.g. resolving npm's whole dependency graph, which does exactly this kind of bulk parse-then-compare) would need to batch calls or avoid re-parsing already-parsed versions to see a win across the board — that's the concrete next step if this gets pushed further, not a caching layer for its own sake.

Reproduce:

cargo bench -p rs-semver-core

cd packages/rs-semver && npm run build && cd ../../benchmarks
npm install
node compare.js

Design notes

  • No regex, no backtracking parser. SemVer::parse is a single-pass hand-written parser straight into the SemVer struct.
  • Ranges compile to concrete comparators at parse time. ^, ~, x-ranges, and hyphen ranges all expand into plain >=/</<=/= comparators up front, so matching a version is just a linear scan — no regex rewriting happens at match time.
  • Prerelease exclusion matches node-semver exactly, including the synthetic -0 prerelease tag node-semver attaches to compiled range bounds (verified directly against the reference implementation, not just from memory of the source) so that plain numeric comparison correctly excludes/includes prereleases without needing a same-tuple check on every comparator.

License

MIT