A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits.
npm install package-versioner
A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Supports both single package projects and monorepos with flexible versioning strategies.
- Automatically determines version bumps based on commit history (using conventional commits)
- Supports both single package projects and monorepos with minimal configuration
- Support for both npm (package.json) and Rust (Cargo.toml) projects
- Flexible versioning strategies (e.g., based on commit types, branch patterns)
- Integrates with conventional commits presets
- Customizable through a version.config.json file or CLI options
- Automatically updates package.json or Cargo.toml version
- Creates appropriate Git tags for releases
- Automatically generates and maintains changelogs in Keep a Changelog or Angular format
- Integrates commit messages, breaking changes, and issue references into well-structured changelogs
- CI/CD friendly with JSON output support
package-versioner provides version management for both JavaScript/TypeScript (via package.json) and Rust (via Cargo.toml) projects:
- JavaScript/TypeScript: Automatically detects and updates version in package.json files
- Rust: Detects and updates version in Cargo.toml files using the same versioning strategies
- Mixed Projects: Supports repositories containing both package.json and Cargo.toml files
When run, the tool will automatically discover and update the appropriate manifest file based on the project structure.
package-versioner is designed to be run directly using your preferred package manager's execution command, without needing global installation.
``bashDetermine bump based on conventional commits since last tag
npx package-versioner
Note on Targeting: Using the
-t flag creates package-specific tags (e.g., @scope/package-a@1.2.0) but not a global tag (like v1.2.0). If needed, create the global tag manually in your CI/CD script after this command.$3
By default,
package-versioner intelligently handles Git tag reachability to provide the best user experience:- Default behaviour: Uses reachable tags when available, but falls back to the latest repository tag if needed (common in feature branches)
- Strict mode (
--strict-reachable): Only uses tags reachable from the current commit, following strict Git semanticsThis is particularly useful when working on feature branches that have diverged from the main branch where newer tags exist. The tool will automatically detect the Git context and provide helpful guidance:
`bash
On a feature branch with unreachable tags
npx package-versioner --dry-run
Output: "No tags reachable from current branch 'feature-x'. Using latest repository tag v1.2.3 as version base."
Tip: Consider 'git merge main' or 'git rebase main' to include tag history in your branch.
Force strict Git semantics
npx package-versioner --dry-run --strict-reachable
Output: Uses only reachable tags, may result in "No reachable tags found"
`JSON Output
When using the
--json flag, normal console output is suppressed and the tool outputs a structured JSON object that includes information about the versioning operation.`json
{
"dryRun": true,
"updates": [
{
"packageName": "@scope/package-a",
"newVersion": "1.2.3",
"filePath": "/path/to/package.json"
}
],
"commitMessage": "chore(release): v1.2.3",
"tags": [
"v@scope/package-a@1.2.3"
]
}
`For detailed examples of how to use this in CI/CD pipelines, see CI/CD Integration.
Configuration
Customize behaviour by creating a
version.config.json file in your project root:`json
{
"preset": "angular",
"versionPrefix": "v",
"tagTemplate": "${packageName}@${prefix}${version}",
"packageSpecificTags": true,
"commitMessage": "chore: release ${packageName}@${version} [skip ci]",
"updateChangelog": true,
"changelogFormat": "keep-a-changelog",
"strictReachable": false,
"sync": true,
"skip": [
"docs",
"e2e"
],
"packages": ["@mycompany/*"],
"mainPackage": "primary-package",
"cargo": {
"enabled": true,
"paths": ["src/", "crates/"]
}
}
`$3
#### General Options (All Projects)
-
preset: Conventional commits preset to use for version calculation (default: "angular")
- versionPrefix: Prefix for version numbers in tags (default: "v")
- tagTemplate: Template for Git tags (default: "${prefix}${version}")
- commitMessage: Template for commit messages (default: "chore(release): ${version}")
- updateChangelog: Whether to automatically update changelogs (default: true)
- changelogFormat: Format for changelogs - "keep-a-changelog" or "angular" (default: "keep-a-changelog")
- strictReachable: Only use reachable tags, no fallback to unreachable tags (default: false)
- prereleaseIdentifier: Identifier for prerelease versions (e.g., "alpha", "beta", "next") used in versions like "1.2.0-alpha.3"
- cargo: Options for Rust projects:
- enabled: Whether to handle Cargo.toml files (default: true)
- paths: Directories to search for Cargo.toml files (optional)#### Monorepo-Specific Options
-
sync: Whether all packages should be versioned together (default: true)
- skip: Array of package names or patterns to exclude from versioning. Supports exact names, scope wildcards, path patterns, and global wildcards (e.g., ["@scope/package-a", "@scope/", "packages//"])
- packages: Array of package names or patterns to target for versioning. Supports exact names, scope wildcards, path patterns and global wildcards (e.g., ["@scope/package-a", "@scope/", ""])
- mainPackage: Package name whose commit history should drive version determination
- packageSpecificTags: Whether to enable package-specific tagging behaviour (default: false)
- updateInternalDependencies: How to update internal dependencies ("patch", "minor", "major", or "inherit")For more details on CI/CD integration and advanced usage, see CI/CD Integration.
$3
The
packages configuration option controls which packages are processed for versioning. It supports several pattern types:#### Exact Package Names
`json
{
"packages": ["@mycompany/core", "@mycompany/utils", "standalone-package"]
}
`#### Scope Wildcards
Target all packages within a specific scope:
`json
{
"packages": ["@mycompany/*"]
}
`#### Path Patterns / Globs
Target all packages in a directory or matching a path pattern:
`json
{
"packages": ["packages//*", "examples/"]
}
`
This will match all packages in nested directories under packages/ or examples/.#### Global Wildcard
Target all packages in the workspace:
`json
{
"packages": ["*"]
}
`#### Mixed Patterns
Combine different pattern types:
`json
{
"packages": ["@mycompany/", "@utils/logger", "legacy-package", "packages//"]
}
`Behaviour:
- When
packages is specified, only packages matching those patterns will be processed
- When packages is empty or not specified, all workspace packages will be processed
- The skip option can exclude specific packages from the selected setNote: Your workspace configuration (pnpm-workspace.yaml, package.json workspaces, etc.) determines which packages are available, but the
packages option directly controls which ones get versioned.$3
The
packageSpecificTags option controls whether the tool creates and searches for package-specific Git tags:- When
false (default): Creates global tags like v1.2.3 and searches for the latest global tag
- When true: Creates package-specific tags like @scope/package-a@v1.2.3 and searches for package-specific tagsThis option works in conjunction with
tagTemplate to control tag formatting. The tagTemplate is used for all tag creation, with the packageSpecificTags boolean controlling whether the ${packageName} variable is populated:- When
packageSpecificTags is false: The ${packageName} variable is empty, so templates should use ${prefix}${version}
- When packageSpecificTags is true: The ${packageName} variable contains the package nameExamples:
For single-package repositories or sync monorepos:
`json
{
"packageSpecificTags": true,
"tagTemplate": "${packageName}@${prefix}${version}"
}
`
Creates tags like my-package@v1.2.3For global versioning:
`json
{
"packageSpecificTags": false,
"tagTemplate": "${prefix}${version}"
}
`
Creates tags like v1.2.3Important Notes:
- In sync mode with a single package,
packageSpecificTags: true will use the package name even though all packages are versioned together
- In sync mode with multiple packages, package names are not used regardless of the setting
- In async mode, each package gets its own tag when packageSpecificTags is enabledWith package-specific tagging enabled, the tool will:
1. Look for existing tags matching the configured pattern for each package
2. Create new tags using the same pattern when releasing
3. Fall back to global tag lookup if no package-specific tags are found
How Versioning Works
package-versioner determines the next version based on your configuration (version.config.json). The two main approaches are:1. Conventional Commits: Analyzes commit messages (like
feat:, fix:, BREAKING CHANGE:) since the last tag.
2. Branch Pattern: Determines the bump based on the current or recently merged branch name matching predefined patterns.For a detailed explanation of these concepts and monorepo modes (Sync vs. Async), see Versioning Strategies and Concepts.
Documentation
- Versioning Strategies and Concepts - Detailed explanation of versioning approaches
- CI/CD Integration - Guide for integrating with CI/CD pipelines
- Changelog Generation - How changelogs are automatically generated and maintained
For more details on available CLI options, run:
`bash
npx package-versioner --help
`Acknowledgements
jucian0/turbo-version`. We appreciate the foundational work done by the original authors.MIT