myopensoft / php-version-bumper
Bump your project's semantic version from Conventional Commits and generate a Keep-a-Changelog entry. Framework-free, zero dependencies.
Requires
- php: ^8.3
Requires (Dev)
- laravel/pint: ^1.14
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- vimeo/psalm: ^6.0
README
Bump your project's semantic version from Conventional Commits
and generate a Keep a Changelog entry, using the commits since your
last git tag. Framework-free, zero runtime dependencies — a single git binary on PATH is all it needs.
Installation
composer require --dev myopensoft/php-version-bumper
CLI usage
vendor/bin/version-bump [options]
| Option | Effect |
|---|---|
--dry-run | Preview the next version and changelog without writing |
--commit | Stage and commit the version + changelog changes |
--tag | Create a git tag for the new version |
--push | Push the branch and tag to the configured remote |
--help, -h | Show usage |
--version, -V | Show the version-bump version |
The bump level follows Conventional Commits: feat → minor, fix/perf → patch, and any commit
marked breaking (feat!: or a BREAKING CHANGE: footer) → major. The baseline is your most recent
git tag; if no commit since that tag warrants a release, the command prints a notice and exits 0.
Committing, tagging, and pushing are each opt-in — a bare vendor/bin/version-bump only writes the
version file and the changelog.
Worked example
Given a repo tagged 1.0.0 with these commits since the tag:
feat(api): add export endpoint
fix(cli): correct exit code on failure
$ vendor/bin/version-bump --dry-run
Bumping 1.0.0 → 1.1.0 (Minor).
## [1.1.0] — 2026-08-02
### Added
- api: add export endpoint
### Fixed
- cli: correct exit code on failure
Dry run — nothing written, committed, or pushed.
Dropping --dry-run writes VERSION and prepends the entry to CHANGELOG.md. Adding --commit
--tag then creates the release commit and tag:
$ vendor/bin/version-bump --commit --tag
Bumping 1.0.0 → 1.1.0 (Minor).
## [1.1.0] — 2026-08-02
...
Released 1.1.0.
$ git log --oneline -1 && git tag --list '1.1.0'
4c7bc98 chore(release): 1.1.0
1.1.0
Configuration: version-bumper.php
Drop a version-bumper.php in your project root and the CLI will pick it up automatically; it is
entirely optional — with no file present, the defaults below already give you a working setup
(a VERSION file plus a CHANGELOG.md, both in the current working directory).
<?php
return [
// Where the current version lives: 'file', 'config_file', or 'git_tag'.
'source' => 'file',
// Path to the plain-text version file, used when source is 'file'.
'version_file' => 'VERSION',
'config_file' => [
// Path to the file to regex-patch, used when source is 'config_file'.
'path' => '',
// A regex with exactly one capture group around the version literal.
'pattern' => '',
],
// Path to the Keep-a-Changelog file that new entries are prepended to.
'changelog' => 'CHANGELOG.md',
// Format passed to DateTimeImmutable::format() for the changelog entry date.
'date_format' => 'Y-m-d',
// Commit type => changelog section heading. Types left out of this map
// are parsed but never appear in the changelog (e.g. docs, chore, test).
'sections' => [
'feat' => 'Added',
'fix' => 'Fixed',
'perf' => 'Changed',
],
// Commit type => bump level ('major', 'minor', or 'patch'). Types left
// out of this map never trigger a release on their own. A commit marked
// breaking (`feat!:` or a `BREAKING CHANGE:` footer) always bumps major,
// regardless of this map.
'bumps' => [
'feat' => 'minor',
'fix' => 'patch',
'perf' => 'patch',
],
'git' => [
// Remote pushed to when --push is passed.
'remote' => 'origin',
// Branch pushed to when --push is passed. null (the default)
// resolves to the current branch at run time.
'branch' => null,
],
];
Paths are resolved relative to the current working directory unless they are absolute.
Version sources
file(default) — reads and writes a plain-text version string inversion_file. A missing or empty file reads as0.0.0.config_file— regex-patches a version literal inside an arbitrary file, e.g. a'version'entry in some other config file. Bothconfig_file.pathandconfig_file.patternare required: selecting this source while either is left at its empty-string default throws anInvalidArgumentExceptionbefore anything is read or written.git_tag— reads the current version from the latest git tag;write()is a no-op, since the tag created by--tagis itself the new source of truth.
Library usage
Everything the CLI does is reachable directly, if you'd rather drive a release from your own script:
use Myopensoft\VersionBumper\Config;
use Myopensoft\VersionBumper\ReleaseFactory;
use Myopensoft\VersionBumper\ReleaseOptions;
$release = (new ReleaseFactory(Config::fromArray([]), (string) getcwd()))
->releaseRunner()
->run(new ReleaseOptions(dryRun: true));
echo $release->to; // '1.1.0'
echo $release->changelogEntry; // '## [1.1.0] — 2026-08-02 …'
Config::fromArray([]) applies the same built-in defaults as an absent version-bumper.php.
Release also carries from, bump, status, and the buffered messages the CLI prints.
Laravel
myopensoft/laravel-version-bumper wraps
this package as an Artisan command (php artisan version:bump) with a published config file. If
you're on Laravel, install that package instead of wiring this one up by hand.
Testing & static analysis
composer test # Pest
composer analyse # PHPStan level 10
composer psalm # Psalm errorLevel 1
composer format # Pint
License
The MIT License (MIT). Please see the License File for more information.