Semantic Versioning
Semantic versioning (SemVer) conventions, version ranges, and strategies for managing breaking changes
You are an expert in semantic versioning (SemVer), including version range syntax, pre-release conventions, and strategies for managing breaking changes in published packages. ## Key Points - **MAJOR** (e.g., 1.x.x to 2.0.0): Incompatible API changes. Consumers may need to update their code. - **MINOR** (e.g., 1.1.x to 1.2.0): New functionality added in a backward-compatible manner. - **PATCH** (e.g., 1.1.1 to 1.1.2): Backward-compatible bug fixes. - `^0.2.3` resolves to `>=0.2.3 <0.3.0` (MINOR is treated as breaking). - `^0.0.3` resolves to `>=0.0.3 <0.0.4` (effectively exact). 1. Document all breaking changes in the changelog. 2. Provide a migration guide. 3. Consider a codemod for automated upgrades. 4. Publish release candidates first: `2.0.0-rc.1`, `2.0.0-rc.2`. 5. Maintain the previous major with security patches for a defined period. - **Fixed/uniform**: All packages share the same version (Lerna fixed mode, Changesets fixed groups). - **Independent**: Each package has its own version, bumped independently. ## Quick Example ``` 2.0.0-alpha.1 2.0.0-beta.3 2.0.0-rc.1 ``` ``` 1.0.0+20240101 1.0.0+build.42 ```
skilldb get package-management-skills/Semantic VersioningFull skill: 211 linesSemantic Versioning — Package Management
You are an expert in semantic versioning (SemVer), including version range syntax, pre-release conventions, and strategies for managing breaking changes in published packages.
Core Philosophy
Overview
Semantic Versioning (SemVer) is the universal versioning contract for npm packages. A version string MAJOR.MINOR.PATCH communicates the nature of changes to consumers: breaking changes bump MAJOR, new features bump MINOR, and bug fixes bump PATCH. Understanding SemVer deeply — including range syntax, pre-release identifiers, and real-world edge cases — is essential for both publishers and consumers.
Core Concepts
The SemVer Contract
Given a version MAJOR.MINOR.PATCH:
- MAJOR (e.g., 1.x.x to 2.0.0): Incompatible API changes. Consumers may need to update their code.
- MINOR (e.g., 1.1.x to 1.2.0): New functionality added in a backward-compatible manner.
- PATCH (e.g., 1.1.1 to 1.1.2): Backward-compatible bug fixes.
Pre-release Versions
Pre-release identifiers are appended with a hyphen:
2.0.0-alpha.1
2.0.0-beta.3
2.0.0-rc.1
Pre-release versions have lower precedence than the release version:
1.0.0-alpha.1 < 1.0.0-beta.1 < 1.0.0-rc.1 < 1.0.0
Build Metadata
Build metadata is appended with + and is ignored during version comparison:
1.0.0+20240101
1.0.0+build.42
Version Ranges in npm
| Syntax | Meaning | Example range | Matches |
|---|---|---|---|
^1.2.3 | Compatible with version (same MAJOR) | >=1.2.3 <2.0.0 | 1.2.3, 1.9.9 |
~1.2.3 | Approximately equivalent (same MAJOR.MINOR) | >=1.2.3 <1.3.0 | 1.2.3, 1.2.9 |
1.2.3 | Exact version | 1.2.3 | 1.2.3 only |
* | Any version | >=0.0.0 | Everything |
>=1.2.3 | Greater than or equal | >=1.2.3 | 1.2.3, 2.0.0, etc. |
1.2.x | Any patch version | >=1.2.0 <1.3.0 | 1.2.0 through 1.2.x |
1.x | Any minor/patch | >=1.0.0 <2.0.0 | Same as ^1.0.0 |
1.2.3 - 2.0.0 | Inclusive range | >=1.2.3 <=2.0.0 | Between the two |
>=1.0.0 <1.5.0 | Combined range | Intersection | Both conditions met |
^1.2.3 || ^2.0.0 | Union of ranges | Either range | Matches if either is true |
The 0.x Exception
For versions below 1.0.0, SemVer treats the API as unstable:
^0.2.3resolves to>=0.2.3 <0.3.0(MINOR is treated as breaking).^0.0.3resolves to>=0.0.3 <0.0.4(effectively exact).
This is a frequent source of confusion. Packages at 0.x signal that any minor bump may break.
Implementation Patterns
Bumping Versions with npm
# Patch: bug fix
npm version patch # 1.2.3 -> 1.2.4
# Minor: new feature
npm version minor # 1.2.3 -> 1.3.0
# Major: breaking change
npm version major # 1.2.3 -> 2.0.0
# Pre-release
npm version premajor --preid=beta # 1.2.3 -> 2.0.0-beta.0
npm version prerelease --preid=beta # 2.0.0-beta.0 -> 2.0.0-beta.1
# Skip git tag creation
npm version patch --no-git-tag-version
Automating Version Decisions with Conventional Commits
Conventional Commits map commit types to SemVer bumps:
fix: correct null check in parser -> PATCH
feat: add streaming API -> MINOR
feat!: redesign configuration format -> MAJOR
BREAKING CHANGE: removed legacy API -> MAJOR
Tools like semantic-release or changesets automate the version bump based on commit history:
# semantic-release (fully automated)
npx semantic-release
# changesets (manual changeset files, automated version + publish)
npx changeset # developer adds a changeset
npx changeset version # CI bumps versions
npx changeset publish # CI publishes
Changeset File Example
---
"@myorg/parser": minor
"@myorg/cli": patch
---
Added streaming support to the parser. Updated CLI to use the new streaming API.
Communicating Breaking Changes
When publishing a major version:
- Document all breaking changes in the changelog.
- Provide a migration guide.
- Consider a codemod for automated upgrades.
- Publish release candidates first:
2.0.0-rc.1,2.0.0-rc.2. - Maintain the previous major with security patches for a defined period.
# Publish RC
npm version 2.0.0-rc.1
npm publish --tag next
# When stable
npm version 2.0.0
npm publish
Version Resolution in Monorepos
In a monorepo, workspace packages reference each other. Version strategies:
- Fixed/uniform: All packages share the same version (Lerna fixed mode, Changesets fixed groups).
- Independent: Each package has its own version, bumped independently.
// .changeset/config.json
{
"fixed": [["@myorg/core", "@myorg/cli"]],
"linked": [["@myorg/plugin-*"]]
}
Peer Dependency Ranges
Peer dependencies use ranges to declare compatibility:
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
}
}
This tells consumers: "I work with React 17 or 18. You provide it."
Best Practices
- Follow SemVer strictly once you hit 1.0.0. Consumers depend on the contract.
- Use
^(caret) ranges for dependencies in libraries. This gives consumers maximum flexibility for deduplication. - Use exact versions or
~(tilde) ranges for application dependencies where you want tighter control. - Stay at 0.x only during genuine early development. Reach 1.0.0 as soon as the API stabilizes.
- Use Conventional Commits to make version bumps deterministic and auditable.
- Publish pre-release versions (
-alpha,-beta,-rc) to a non-latestdist tag. - Write changelogs that describe the impact on consumers, not the implementation details.
- When deprecating an API, mark it as deprecated in one minor version before removing it in the next major.
- In monorepos, use
linkedorfixedversion groups for packages that must stay in sync. - Run
npm outdatedornpx npm-check-updatesregularly to review available updates.
Common Pitfalls
- Breaking changes in a PATCH or MINOR: The most damaging mistake. Consumers on
^ranges auto-install it, and their builds break. - 0.x confusion:
^0.2.3does not behave like^1.2.3. Minor bumps at 0.x are treated as potentially breaking, and the range is much narrower. - Pre-release range matching:
^1.0.0does NOT match1.0.1-beta.0. Pre-release versions only match ranges that explicitly include a pre-release on the sameMAJOR.MINOR.PATCH. - Forgetting
--tagfor pre-releases: Publishing2.0.0-beta.1without--tag betamakes itlatest, and allnpm installusers get the pre-release. - Lockfile masking range issues: A lockfile pins exact versions, so range bugs only surface when consumers install fresh. Always test your ranges in a clean environment.
- Over-broad peer dependency ranges: Declaring
"react": "*"as a peer dependency promises compatibility with every version, which is almost never true. - Skipping the changelog: Users cannot adopt new major versions without understanding what changed. Always document breaking changes.
- Using
>=without an upper bound:">=1.0.0"will match version 47.0.0. Always bound your ranges.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add package-management-skills
Related Skills
Bundling Libraries
Bundling JavaScript/TypeScript libraries for distribution using tsup, unbuild, and Rollup
Dependency Audit
Security auditing npm dependencies for vulnerabilities, license compliance, and supply chain risks
Lockfile Management
Lock file strategies for deterministic installs across npm, pnpm, and Yarn
Npm Publishing
Publishing packages to the npm registry with proper configuration, access control, and release automation
Pnpm
pnpm workspace management for monorepos with content-addressable storage and strict dependency isolation
Private Registries
Setting up and using private npm registries with Verdaccio, GitHub Packages, and GitLab Package Registry