Skip to content

Semantic Versioning ​

Semantic Versioning (SemVer) is a versioning scheme that uses a three-part version number: MAJOR.MINOR.PATCH.

Version Format ​

text
MAJOR.MINOR.PATCH[-prerelease][+build-metadata]

Examples:

  • 1.0.0 - Initial release
  • 1.2.3 - Stable version
  • 2.0.0-alpha.1 - Pre-release version
  • 1.5.0+build.123 - With build metadata

When to Bump Versions ​

MAJOR Version (X.0.0) ​

Bump the major version when you make incompatible API changes.

Examples:

  • Remove or rename a function
  • Change function signatures
  • Restructure data formats
  • Remove dependencies that were part of the public API

MINOR Version (0.Y.0) ​

Bump the minor version when you add new functionality in a backwards-compatible manner.

Examples:

  • Add new features
  • Add new methods to classes
  • Improve existing features
  • Add deprecation warnings

PATCH Version (0.0.Z) ​

Bump the patch version when you make backwards-compatible bug fixes.

Examples:

  • Fix bugs
  • Fix performance issues
  • Update dependencies for security fixes
  • Fix documentation

Pre-release Versions ​

Pre-release versions are indicated by appending a hyphen and a series of dot-separated identifiers:

text
1.0.0-alpha
1.0.0-alpha.1
1.0.0-beta
1.0.0-beta.2
1.0.0-rc.1
2.0.0-preview

Pre-release versions:

  • Indicate unstable versions
  • Take precedence below the associated normal version
  • Are useful for testing before official release

Examples of precedence:

text
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta
< 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11
< 1.0.0-rc.1 < 1.0.0

Build Metadata ​

Build metadata is indicated by appending a plus sign and a series of dot-separated identifiers:

text
1.0.0+20130313144700
1.0.0+build.1
1.0.0+exp.sha.5114f85

Build metadata:

  • Should be ignored when determining version precedence
  • Useful for tracking build information

Versu and Semantic Versioning ​

Versu automatically handles semantic versioning by:

  1. Analyzing commits using Conventional Commits
  2. Determining the highest version bump needed
    • If any breaking change → MAJOR bump
    • Else if any feature → MINOR bump
    • Else if any fix → PATCH bump
  3. Applying that bump to the current version
  4. Optionally creating pre-release versions

Example Workflow ​

Starting with version 1.0.0:

text
Commits:
- feat: add new feature    → Would bump to 1.1.0
- fix: resolve bug         → Would bump to 1.0.1
Result: 1.1.0 (minor takes precedence)

Next cycle with version 1.1.0:
- feat(api)!: redesign API → Would bump to 2.0.0
- feat: add utility        → Would bump to 1.2.0
Result: 2.0.0 (breaking change takes precedence)

Configuration in Versu ​

Configure how Versu handles versioning:

bash
# Enable pre-release versioning
versu run --prerelease-mode --prerelease-id alpha

# Add build metadata with short git hash
versu run --add-build-metadata

# Use timestamps prerelease identifiers
versu run --prerelease-mode --timestamp-versions

# Add SNAPSHOT suffix for development versions
# if supported by your plugins
versu run --append-snapshot

In addition, you can customize versioning rules and their effect on version bumps in your versu.config.js. The example below keeps the defaults for code changes but disables bumps for non-code types (by default, all recognized types trigger at least a patch bump):

javascript
export default {
  versioning: {
    breakingChange: {
      stable: "major",
      prerelease: "premajor",
    },
    unknownCommitType: {
      stable: "patch",
      prerelease: "prepatch",
    },
    commitTypes: {
      feat: {
        stable: "minor",
        prerelease: "preminor",
      },
      fix: {
        stable: "patch",
        prerelease: "prepatch",
      },
      perf: {
        stable: "patch",
        prerelease: "prepatch",
      },
      refactor: {
        stable: "patch",
        prerelease: "prepatch",
      },
      docs: {
        stable: "none",
        prerelease: "none",
      },
      test: {
        stable: "none",
        prerelease: "none",
      },
      chore: {
        stable: "none",
        prerelease: "none",
      },
      style: {
        stable: "none",
        prerelease: "none",
      },
      ci: {
        stable: "none",
        prerelease: "none",
      },
      build: {
        stable: "none",
        prerelease: "none",
      },
    },
    cascadeRules: {
      stable: {
        major: "major",
        minor: "minor",
        patch: "patch",
      },
      prerelease: {
        premajor: "premajor",
        preminor: "preminor",
        prepatch: "prepatch",
        prerelease: "prerelease",
      },
    },
  },
}

Best Practices ​

✅ Do's ​

  • Use semantic versioning for public APIs
  • Increment MAJOR for breaking changes
  • Use pre-releases for testing
  • Document breaking changes clearly
  • Keep versions consistent across related packages

❌ Don'ts ​

  • Skip version numbers
  • Use 0.0.x indefinitely (stabilize to 1.0.0)
  • Break API without incrementing MAJOR
  • Commit breaking changes without pre-release testing

Relationship with Commits ​

Semantic Versioning defines what to increment, while Conventional Commits specifies how to determine what to increment:

  • feat: commits → MINOR version bump
  • fix: commits → PATCH version bump
  • BREAKING CHANGE: commits → MAJOR version bump

Further Reading ​


Ready to understand more? Check out Multi-Module Projects!

Released under the MIT License.