verspec
v0.2.0
Published
Tiny, zero-dependency SemVer toolkit: parse, compare, sort, diff, increment, and test versions against ranges (^, ~, x-ranges, hyphen, ||) with correct prerelease precedence — plus a CLI. Node, Deno, Bun & browser.
Maintainers
Readme
verspec
Semantic Versioning done right — parse, compare, sort and range-match versions in ~2 KB, with zero dependencies.
Does 1.2.3-beta.11 come before or after 1.2.3-beta.2? Does 1.2.3-rc.1
satisfy ^1.0.0? These are spec questions with exact answers — and exactly the
kind of fiddly rule that's easy to get wrong by hand. verspec implements
SemVer 2.0.0 precisely, including prerelease precedence
and the prerelease-in-range rule.
import { compare, satisfies, sort } from "verspec";
compare("1.2.3-beta.2", "1.2.3-beta.11"); // -1 (numeric identifiers compare numerically)
satisfies("1.2.3", "^1.0.0"); // true
satisfies("1.2.3-rc.1", "^1.0.0"); // false (prereleases don't leak into plain ranges)
sort(["1.10.0", "1.2.0", "1.0.0-rc.1"]); // ["1.0.0-rc.1", "1.2.0", "1.10.0"]Why verspec?
- 📏 Spec-accurate. Passes the canonical semver.org precedence chain (
alpha < alpha.1 < alpha.beta < beta < beta.2 < beta.11 < rc.1 < release) and the prerelease-in-range rule that trips up naive comparisons. - 🎯 Full range syntax.
^,~,x-ranges, partials, hyphen ranges (1 - 2), comparator sets (>=1.2 <2), and||unions. - 🪶 ~2 KB gzipped, zero dependencies. A fraction of the size of the classic
semverpackage. - 🧩 Complete toolkit.
parse,valid,compare,eq/gt/lt/…,sort/rsort,satisfies,maxSatisfying/minSatisfying,inc. - 🌍 Runs everywhere. Node 18+, Deno, Bun, Workers and the browser.
- 🛡️ Type-safe. Written in TypeScript, ships full declarations.
Install
npm install verspec
# or: pnpm add verspec / yarn add verspec / bun add verspecShips ESM and CommonJS:
import { satisfies } from "verspec"; // ESM / TypeScript
const { satisfies } = require("verspec"); // CommonJSCLI
A handy SemVer toolkit for scripts and CI — comparison commands set the exit code:
npx verspec satisfies 1.2.3 "^1.0.0" # exit 0 if it satisfies, else 1
npx verspec sort 1.10.0 1.2.0 1.9.0 # 1.2.0 / 1.9.0 / 1.10.0
npx verspec inc 1.2.3 minor # 1.3.0
npx verspec diff 1.2.3 2.0.0 # major
npx verspec max "^1.2.0" 1.2.0 1.3.1 2.0.0 # 1.3.1Commands: valid validRange satisfies compare eq|lt|gt|lte|gte sort
rsort inc diff max min.
Usage
Parse & validate
parse("1.2.3-beta.1+build.5");
// { major: 1, minor: 2, patch: 3, prerelease: ["beta", 1], build: ["build", "5"] }
valid("v1.2.3"); // "1.2.3"
valid("1.2"); // nullCompare & sort
compare("2.0.0", "1.9.9"); // 1
gt("2.0.0", "1.0.0"); // true
lte("1.0.0-alpha", "1.0.0"); // true
sort(["1.10.0", "1.2.0", "1.1.0"]); // ["1.1.0", "1.2.0", "1.10.0"]
rsort(versions); // descendingRanges
satisfies("1.2.9", "~1.2.3"); // true
satisfies("0.3.0", "^0.2.3"); // false
satisfies("1.2.7", "1.2.x"); // true
satisfies("2.3.4", "1.2.3 - 2.3.4"); // true
satisfies("3.1.0", "^1 || ^3"); // true
maxSatisfying(["1.2.0", "1.2.5", "1.3.0"], "~1.2.0"); // "1.2.5"
validRange(">=1.0.0 <2.0.0 || 3.x"); // truePrerelease handling
By default, a prerelease only satisfies a range that explicitly targets the same
major.minor.patch with a prerelease — matching npm's behavior. Opt in with
includePrerelease:
satisfies("1.2.3-beta", "^1.0.0"); // false
satisfies("1.2.3-beta", "^1.2.3-alpha"); // true
satisfies("1.2.3-beta", "^1.0.0", { includePrerelease: true }); // trueIncrement
inc("1.2.3", "minor"); // "1.3.0"
inc("1.2.3", "prerelease"); // "1.2.4-0"
inc("1.2.3-beta.1", "prerelease"); // "1.2.3-beta.2"
inc("1.2.3", "preminor", "beta"); // "1.3.0-beta.0"API
| Function | Description |
| -------- | ----------- |
| parse(v) / valid(v) / format(s) | Parse, validate, render. |
| compare(a, b) | -1 \| 0 \| 1 by SemVer precedence (build ignored). |
| eq neq gt gte lt lte | Boolean comparisons. |
| sort(list) / rsort(list) | Ascending / descending sort. |
| satisfies(v, range, opts?) | Range test. |
| validRange(range) | Is the range parseable? |
| maxSatisfying / minSatisfying(list, range, opts?) | Best match. |
| inc(v, release, identifier?) | Bump major/minor/patch/pre*. |
| diff(a, b) | The change type between two versions (major/minor/patch/…), or null if equal. |
| Range | The parsed-range class, if you want to reuse it. |
Comparison
| | verspec | hand-rolled compare | the classic semver pkg |
| ---------------------------- | :-------: | :-----------------: | :----------------------: |
| Prerelease precedence | ✅ | ⚠️ | ✅ |
| Full range syntax | ✅ | ❌ | ✅ |
| Prerelease-in-range rule | ✅ | ❌ | ✅ |
| Zero dependencies | ✅ | ✅ | ⚠️ |
| ~2 KB gzipped | ✅ | ✅ | ❌ |
Contributing
Contributions are very welcome! Please read CONTRIBUTING.md and our Code of Conduct.
git clone https://github.com/didrod205/verspec.git
cd verspec
npm install
npm test💖 Sponsor
verspec is free and MIT-licensed, built and maintained in spare time. If it
saved you from a versioning headache, please consider supporting it — every bit
helps keep the project healthy.
- ⭐ Star this repo — the simplest, free way to help others discover it.
- 🍋 Sponsor via Lemon Squeezy — one-time or recurring support.
Sponsoring? Open an issue and we'll add your name/logo here. Thank you! 🙏
License
MIT © verspec contributors
