Semantic Versioning driven by Conventional Commits, executed automatically by semantic-release in CI. There is no manual version bumping and no GitVersion/.NET dependency.
- Version numbers follow SemVer 2.0.0:
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]. - Version selection is derived from commit messages. A commit that does not match a release rule triggers no release.
- Tags are created automatically as
vMAJOR.MINOR.PATCH(e.g.v1.2.3) and are the only tags the repository uses — except onfeature/*branches, which are npm-only: the tag is deleted right after publishing and no GitHub Release is created. - Changelog / GitHub releases are generated from the same commits (stable, beta and rc channels only).
| Branch | Version channel | Example | npm dist-tag |
|---|---|---|---|
master |
Stable release | 1.2.3 |
latest |
develop |
Beta prerelease | 1.2.3-beta.1 |
beta |
feature/* |
Feature-name prerelease (npm-only, no git tag) | 1.2.3-my-feature.1 |
<feature-name> |
hotfix/* |
Release-candidate prerelease | 1.2.3-rc.1 |
rc |
The prerelease identifier (and npm dist-tag) for a feature/* branch is the
sanitized branch name after feature/: lowercased, with runs of characters
outside [a-z0-9-] collapsed to a single - and leading/trailing - stripped
(e.g. feature/My_Cool thing → my-cool-thing). Keep feature branch names to
lowercase alphanumerics and dashes — two branches that sanitize to the same
identifier will conflict. If sanitization yields an empty string it falls back to
alpha.
Stable releases happen only on master. Pre-release channels never touch latest.
Work that lands on a pre-release branch is re-analyzed when merged into master, so the
same commits can produce 1.2.3-beta.1 on develop and then 1.2.3 on master.
Applied from the most significant commit since the last release.
| Commit message | Bump | Example result |
|---|---|---|
feat!: / fix!: / BREAKING CHANGE: footer |
MAJOR | 1.2.3 → 2.0.0 |
feat: |
MINOR | 1.2.3 → 1.3.0 |
fix:, perf: |
PATCH | 1.2.3 → 1.2.4 |
build:, chore:, ci:, docs:, refactor:, revert:, style:, test: |
no release | — |
Breaking changes are expressed either with an ! after the type (feat!:)
or with a BREAKING CHANGE: trailer in the commit body.
First release: the repository has no tags yet and the package is at 0.0.1.
The first release will be computed from existing history and land as 1.0.0
(the project already contains feat: commits), which is above the currently
published 0.0.1.
.github/workflows/ci.yml:
build-testruns on every push and pull request:pnpm install,pnpm lint,pnpm test,pnpm build.releaseruns after a successfulbuild-test, on pushes tomaster,develop,feature/*orhotfix/*only. It runssemantic-release, which:- analyzes commits since the last tag,
- computes the next version per the rules above,
- updates
package.jsonin the packed artifact, - publishes to npm with OIDC trusted publishing and provenance
(no
NPM_TOKENsecret needed; the job carriesid-token: write), - pushes the
vX.Y.Ztag — except onfeature/*, where the tag is deleted immediately after publishing so no git tag remains, - opens a GitHub Release with the generated changelog — except on
feature/*, which skips the@semantic-release/githubplugin entirely.
If no release-worthy commit exists, semantic-release exits without releasing.
| Goal | How |
|---|---|
| Cut a patch release | Commit fix: on master (or merge a PR with one) and push. |
| Cut a minor release | Commit feat: on master and push. |
| Cut a major release | Commit with BREAKING CHANGE: / feat!: on master and push. |
| Release a beta | Push to develop. |
| Release a feature preview | Push to a feature/* branch. Publishes x.y.z-<feature-name>.n to npm under the <feature-name> dist-tag; no git tag, no GitHub Release. |
| Publish no version | Use chore:, docs:, refactor:, etc. |
Do not tag releases by hand. v* tags created manually bypass the changelog and
provenance flow and can confuse the next analysis.
The old GitVersion.yml (Invoicify) maps as follows:
| GitVersion concept | semantic-release equivalent |
|---|---|
.NET runtime + gittools/actions/gitversion/* |
Node.js semantic-release + a workflow job |
commit-message-incrementing: Enabled |
Conventional Commits analysis (built-in) |
major-version-bump-message regex |
BREAKING CHANGE: footer / ! suffix |
minor-version-bump-message (feat) |
feat: commits |
patch-version-bump-message (fix/chore/...) |
fix:, perf: commits |
tag-prefix: '[vV]?' |
tagFormat: 'v${version}' |
mode: ContinuousDeployment |
release on every push to a release branch |
branches.main (^master$, release) |
master (stable) |
branches.develop (label beta) |
develop → prerelease: beta |
branches.feature (label alpha) |
feature/* → prerelease:/channel from sanitized branch name (npm-only, tag deleted) |
branches.hotfix (patch) |
hotfix/* → prerelease: rc |
prevent-increment.when-current-commit-tagged |
built-in: tagged commits are skipped |
mathieudutour/github-tag-action |
tag created by semantic-release itself |
| git-cliff changelog | @semantic-release/release-notes-generator + @semantic-release/github |
| File | Purpose |
|---|---|
release.config.mjs |
Branch → channel mapping and plugin list |
.github/workflows/ci.yml |
Build/test gate + release job |
package.json |
semantic-release in devDependencies |