⚡ Node.jsDevTools / CI/CD
Semver Validation in Node.js
Validate semantic version strings in Node.js — parse major, minor, and patch components, detect pre-release tags and build metadata, and validate version strings in CI/CD pipelines and package registries.
Also available in Python
Contents
1. Semver anatomy
A semantic version follows the format MAJOR.MINOR.PATCH[-pre-release][+build]:
2 . 1 . 0 - beta.1 + build.001 │ │ │ │ └─ build metadata (ignored for ordering) │ │ │ └─ pre-release identifier │ │ └─ patch (bug fixes) │ └─ minor (new features, backwards-compatible) └─ major (breaking changes)
| Component | Rule |
|---|---|
| MAJOR | Non-negative integer. 0 = initial development, 1+ = public API |
| MINOR | Non-negative integer. Reset to 0 when MAJOR increments |
| PATCH | Non-negative integer. Reset to 0 when MINOR increments |
| Pre-release | Dot-separated alphanumeric identifiers (no leading zeros in numeric parts) |
| Build metadata | Dot-separated alphanumeric identifiers — ignored in version precedence |
2. API response structure
Endpoint: GET /v0/semver?value=…
{ "valid": true, "version": "2.1.0-beta.1+build.001", "major": 2, "minor": 1, "patch": 0, "prerelease": "beta.1", "build": "build.001", "formatted": "2.1.0-beta.1+build.001", "isStable": false }
- Check
valid— strict semver 2.0.0 compliance - Use
major/minor/patchfor version comparison logic - Check
isStable—truewhen prerelease is null - Use
formattedas the canonical string for storage
3. Valid and invalid examples
| String | Valid? | Reason |
|---|---|---|
| 1.0.0 | ✓ | Standard release |
| 0.0.1 | ✓ | Initial development |
| 1.0.0-alpha | ✓ | Pre-release |
| 1.0.0-alpha.1 | ✓ | Numbered pre-release |
| 1.0.0+build.123 | ✓ | With build metadata |
| 1.0.0-beta.1+sha.abc123 | ✓ | Pre-release + build |
| v1.0.0 | ✗ | Leading v is not semver |
| 1.0 | ✗ | Missing patch component |
| 1.0.0.4 | ✗ | Extra version component |
| 1.0.0-01 | ✗ | Leading zero in numeric pre-release |
4. Pre-release and build metadata
const result = await iv.semver('1.0.0-rc.2+sha.abc123') console.log(result.isStable) // false console.log(result.prerelease) // "rc.2" console.log(result.build) // "sha.abc123" // Common pre-release naming conventions: // alpha → early testing, breaking changes expected // beta → feature-complete, bug fixes ongoing // rc.N → release candidate, near-final // (none) → stable release // Precedence: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0
ℹ️Build metadata (
+sha.abc123) is ignored when determining version precedence. 1.0.0+build.1 and 1.0.0+build.2 are considered the same version.5. CI/CD and package registry use cases
import { createClient } from '@isvalid-dev/sdk' const iv = createClient({ apiKey: process.env.ISVALID_API_KEY! }) // Validate a single semver string async function validateVersion(version: string) { const result = await iv.semver(version) if (!result.valid) throw new Error(`Invalid semver: ${version}`) return result } // Validate package.json version fields async function validatePackageVersions(pkg: { version: string dependencies?: Record<string, string> }) { const toValidate: [string, string][] = [['version', pkg.version]] // Validate exact dependency versions (skip ranges like ^1.0.0) if (pkg.dependencies) { for (const [name, ver] of Object.entries(pkg.dependencies)) { if (/^\d/.test(ver)) toValidate.push([name, ver]) } } const results = await Promise.allSettled( toValidate.map(([, ver]) => iv.semver(ver)) ) const errors: Record<string, string> = {} toValidate.forEach(([name], i) => { const r = results[i] if (r.status === 'rejected' || (r.status === 'fulfilled' && !r.value.valid)) { errors[name] = `Invalid version: ${toValidate[i][1]}` } }) return { valid: Object.keys(errors).length === 0, errors } } // Check if a release is stable (no pre-release tag) async function isStableRelease(version: string) { const result = await iv.semver(version) if (!result.valid) return false return result.prerelease === null } // Examples const v = await validateVersion('2.1.0-beta.1') console.log(v.major) // 2 console.log(v.minor) // 1 console.log(v.patch) // 0 console.log(v.prerelease) // "beta.1" const stable = await isStableRelease('1.0.0-rc.1') console.log(stable) // false
Validate before publishing to npm
import { readFileSync } from 'fs' const pkg = JSON.parse(readFileSync('package.json', 'utf8')) const result = await iv.semver(pkg.version) if (!result.valid) { console.error(`Invalid version in package.json: ${pkg.version}`) process.exit(1) } if (!result.isStable && process.env.CI_BRANCH === 'main') { console.error('Stable branch requires a stable version (no pre-release tag)') process.exit(1) }
6. Edge cases
Stripping the leading 'v'
💡Git tags often use
v1.0.0 by convention, but this is not valid semver. Strip the leading v before validating.const gitTag = 'v2.1.0' const cleaned = gitTag.replace(/^v/, '') // "2.1.0" const result = await iv.semver(cleaned)
npm range specifiers
⚠️Range specifiers like
^1.0.0, ~2.1.0, or >=1.0.0 <2.0.0 are not semver — they are npm range syntax. The API validates strict semver strings only. Strip or expand ranges before validating.7. Summary checklist
✓Validate semver format in CI before publishing
✓Strip leading "v" from git tags before validation
✓Check isStable for production deployment gates
✓Parse major/minor/patch for compatibility checks
✓Store formatted as canonical version string
✓Do not pass npm range specifiers to the API
✓Validate package.json version on every build
✓Run batch validation with Promise.allSettled