Files
semver-generator/README.md
T
lukaszraczylo e90191de4c feat(matching): add an opt-in regex keyword matching mode
Fuzzy matching compares each keyword with every word of the commit
message. A word matches if it contains the keyword's letters in order.
Thus "feat" matches "fix(delegation):" and "major" matches
"deps(majors):", which gives false minor and major versions.

The new top-level config key "matching" accepts "fuzzy" (the default)
or "regex". In regex mode every wording keyword is a Go regular
expression. The tool compiles all of them when it loads the config and
runs them against the full commit message, so "(?m)^" anchors a
pattern to the start of a line. An invalid value, an invalid pattern,
or any other config parse error in regex mode stops the tool with a
non-zero exit code. It never falls back to fuzzy matching.

Fuzzy mode does not change. The blacklist check moves into one helper
that both modes use. A replay of 15 repositories gives the same
versions as before. This change improves version precision for the
repositories that opt in.

Claude-Session: https://claude.ai/code/session_01Qq1gbqQek1Zjtofo4VDRdW
2026-09-25 19:35:48 +01:00

324 lines
14 KiB
Markdown

## Semantic version generator
[![Docker image build.](https://github.com/lukaszraczylo/semver-generator/actions/workflows/release.yaml/badge.svg)](https://github.com/lukaszraczylo/semver-generator/actions/workflows/release.yaml) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/lukaszraczylo/semver-generator) [![codecov](https://codecov.io/gh/lukaszraczylo/semver-generator/branch/main/graph/badge.svg?token=FY9BKETB59)](https://codecov.io/gh/lukaszraczylo/semver-generator)
Project created overnight, to prove that management of semantic versioning is NOT painful and do not require arguments and debates within the team. Simple, clean and only thing the project team should need to agree to are the keywords.
- [Semantic version generator](#semantic-version-generator)
- [How does it work](#how-does-it-work)
- [Additional features](#additional-features)
- [Important changes](#important-changes)
- [Usage](#usage)
- [Authentication](#authentication)
- [As a binary](#as-a-binary)
- [Homebrew (macOS)](#homebrew-macos)
- [Manual Download](#manual-download)
- [Self-Update](#self-update)
- [As a github action](#as-a-github-action)
- [As a docker container](#as-a-docker-container)
- [Verifying Release Signatures](#verifying-release-signatures)
- [Calculations example \[standard\]](#calculations-example-standard)
- [Calculations example \[strict matching\]](#calculations-example-strict-matching)
- [Release candidates](#release-candidates)
- [Tag prefix stripping](#tag-prefix-stripping)
- [Matching modes](#matching-modes)
- [Example configuration](#example-configuration)
- [Good to knows](#good-to-knows)
- [Telemetry](#telemetry)
### How does it work
* Binary clones the github repository
* Iterates through the list of commits looking for the keywords specified in config file for additional bumps of versions
* Returns the semantic version which can be included in the release
### Additional features
* With flag `-e` or config `force.existing: true` the existing tags in versioning will be respected, helping you to avoid the version conflicts.
* With config `force.commit: deadbeef` where `deadbeef` is the commit hash - calculations will start from the specified commit.
* Tag prefix stripping: The `v` prefix is automatically stripped from tags (e.g., `v1.2.3` → `1.2.3`). Additional prefixes can be configured via `tag_prefixes` for monorepo setups (e.g., `app-1.2.3`, `infra-1.2.3`).
### Important changes
* From version `1.4.2+` as pointed out in [issue #12](https://github.com/lukaszraczylo/semver-generator/issues/12) commits from merge will not be included in the calculations and commits themselves will bump the version on first match ( starting checks from `patch` upwards ).
* Added support for blacklisting terms to ignore specific commits, branch names, and merge messages from version calculations.
### Usage
#### Authentication
If you intend to use this project with remote repositories ( regardless of them being private or public ) you need to authenticate with your repository.
To do so you can utilise the following environment variables ( they are NOT github specific, for other providers you can use the password )
```bash
export GITHUB_USERNAME=lukaszraczylo
export GITHUB_TOKEN=yourPersonalApiToken
```
#### As a binary
##### Homebrew (macOS)
```bash
brew install --cask lukaszraczylo/taps/semver-generator
```
##### Manual Download
You can download latest versions of the binaries from the [release page](https://github.com/lukaszraczylo/semver-generator/releases/latest).
**Supported OS and architectures:**
Darwin ARM64/AMD64, Linux ARM64/AMD64, Windows AMD64
```bash
bash$ semver-generator generate -r https://github.com/nextapps-de/winbox
SEMVER 9.0.10
bash$ semver-generator generate -l
SEMVER 5.1.1
```
**Local repository flag `-l` will always take precedence over remote repository URL**
```yaml
Usage:
semver-generator generate [flags]
semver-generator [command]
Available Commands:
generate Generates semantic version
help Help about any command
Flags:
-c, --config string Path to config file (default "semver.yaml")
-d, --debug Enable debug mode
-e, --existing Respect existing tags
-h, --help help for semver-generator
-l, --local Use local repository
-r, --repository string Remote repository URL. (default "https://github.com/lukaszraczylo/simple-gql-client")
-b, --branch string Remote repository URL Branch. (default "main")
-s, --strict Strict matching
-u, --update Update binary with latest (no authentication required)
-v, --version Display version
```
##### Self-Update
The binary can update itself to the latest version:
```bash
semver-generator -u
```
This downloads the latest release for your platform directly from GitHub releases. No authentication is required.
#### As a github action
```yaml
jobs:
prepare:
name: Preparing build context
runs-on: ubuntu-latest
outputs:
RELEASE_VERSION: ${{ steps.semver.outputs.semantic_version }}
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
fetch-depth: '0'
- name: Semver run
id: semver
uses: lukaszraczylo/semver-generator@PLACE_LATEST_TAG_HERE
# you can also use v1 tag which _should_ automatically upgrade to latest
# uses: lukaszraczylo/semver-generator@v1
with:
config_file: semver.yaml
# either...
repository_local: true
# or...
repository_url: https://github.com/lukaszraczylo/simple-gql-client
# when using remote repository, especially with private one:
github_username: lukaszraczylo
github_token: MySupeRSecr3tPa$$w0rd
strict: true
existing: false
- name: Semver check
run: |
echo "Semantic version detected: ${{ steps.semver.outputs.semantic_version }}"
```
#### As a docker container
```bash
docker pull ghcr.io/lukaszraczylo/semver-generator:latest
```
#### Verifying Release Signatures
All release checksums and Docker images are signed with [cosign](https://github.com/sigstore/cosign) using keyless signing. To verify:
```bash
# Verify checksum signature
cosign verify-blob \
--certificate-identity-regexp "https://github.com/lukaszraczylo/semver-generator/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--bundle "<checksums-file>.sigstore.json" \
<checksums-file>
# Verify Docker image
cosign verify \
--certificate-identity-regexp "https://github.com/lukaszraczylo/semver-generator/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/lukaszraczylo/semver-generator:latest
```
**Docker supported architectures:**
Linux/arm64, Linux/amd64
#### Calculations example [standard]
```bash
- 0.0.1 - PATCH - starting commit
- 0.0.2 - PATCH - another commit
- 0.0.4 - PATCH - another commit with word 'Update' => DOUBLE increment PATCH
- 0.1.0 - MINOR - after commit with word 'Change' => increment MINOR, reset PATCH
- 0.1.1 - PATCH - additional commit
- 1.0.1 - MAJOR - commit with word 'BREAKING' = > INCREMENT MAJOR, reset MINOR
- 1.0.2 - PATCH - another commit
```
#### Calculations example [strict matching]
```bash
- 0.0.1 - PATCH - starting commit
- 0.0.1 - PATCH - another commit
- 0.0.1 - PATCH - another commit with word 'Update' => SINGLE increment PATCH
- 0.1.0 - MINOR - after commit with word 'Change' => increment MINOR, reset PATCH
- 0.1.0 - PATCH - additional commit
- 1.0.0 - MAJOR - commit with word 'BREAKING' = > INCREMENT MAJOR, reset MINOR
- 1.0.0 - PATCH - another commit
```
#### Release candidates
The `semver-gen` supports release candidates generation as well. Add following configuration ( and change the trigger keywords to anything what suits you )
to generate the appropriate release in format `1.3.37-rc.1` and counting up until next `minor` trigger will be detected.
```yaml
release:
- release-candidate
- add-rc
```
#### Tag prefix stripping
When using the `-e` (existing tags) flag, the semver-generator needs to parse existing git tags to determine the current version. Tags often include prefixes that need to be stripped before version parsing.
**Automatic `v` prefix stripping:**
The `v` prefix is always stripped automatically from tags. For example:
- `v1.2.3` → parsed as `1.2.3`
- `v0.5.0` → parsed as `0.5.0`
**Custom prefixes for monorepos:**
In monorepo setups where different components have their own versioned tags, you can configure additional prefixes to strip:
```yaml
tag_prefixes:
- "app-"
- "infra-"
- "api-"
- "frontend-"
```
With this configuration:
- `app-1.2.3` → parsed as `1.2.3`
- `infra-0.5.0` → parsed as `0.5.0`
- `api-2.0.0-rc.1` → parsed as `2.0.0-rc.1` (release candidate)
This is particularly useful when:
- You have multiple services/components in a single repository
- Your CI/CD creates tags with component prefixes
- You want to track versions separately for different parts of your codebase
#### Matching modes
Config key `matching` controls how each keyword in `wording` is checked against a commit message. Allowed values are `fuzzy` (the default, also used when the key is absent) and `regex`. Any other value is a config error and `semver-gen` exits non-zero rather than silently falling back to `fuzzy`.
**`fuzzy` (default):** the commit message is split on whitespace into words, and a keyword matches as soon as one word contains the keyword's letters *in order*, case-insensitive — it does not need to be a whole word or an exact substring. This is convenient but has a real false-positive risk: keyword `feat` also matches the word `fix(delegation):` (f-e-a-t appear in order inside "fix(delegation):"), and keyword `major` matches `deps(majors):`. Short, generic keywords are the most likely to over-match; test your wording list against real commit messages before relying on it.
**`regex`:** every keyword in `wording.patch` / `wording.minor` / `wording.major` / `wording.release` is a Go RE2 regular expression (the standard library `regexp` package). Patterns are compiled once when the config is loaded — an invalid pattern makes `semver-gen` exit non-zero immediately, naming the wording list and the keyword. Each pattern is matched with `MatchString` against the **full raw commit message** (subject and body, with the original newlines, not the whitespace-split words `fuzzy` uses), so `(?m)^feat(\([^)]*\))?!?:` anchors to the start of a line. Matching is case-sensitive unless the pattern itself uses `(?i)`. The blacklist works exactly the same way in both modes.
Regex patterns must be quoted in YAML (for example `'(?m)^fix:'`), since unquoted `^`, `:` and other YAML-significant characters can break the file's syntax; a YAML syntax error makes the tool fall back to its defaults, the same as it already does for any unreadable config file.
```yaml
matching: regex
wording:
patch:
- '^fix(\([^)]*\))?:'
minor:
- '(?m)^feat(\([^)]*\))?!?:'
major:
# a trailer line anywhere in the commit body, e.g. "Semver-Major: dropped the v1 API"
- '(?mi)^semver-major:'
```
With this config, a subject line of `fix: correct off-by-one` bumps patch, `feat: add widget` bumps minor, and a body trailer of `Semver-Major: ...` bumps major regardless of case. `fix(delegation): route around dead worker` bumps patch too — the patch pattern's `(\([^)]*\))?` group allows an optional scope — but it does NOT bump minor, unlike `fuzzy` mode, where keyword `feat` matches it (see above).
#### Example configuration
```yaml
version: 1
force:
major: 1
minor: 0
patch: 1
commit: 69fbe2df696f40281b9104ff073d26186cde1024
blacklist:
- "Merge branch"
- "Merge pull request"
- "feature/"
- "feature:"
tag_prefixes:
- "app-"
- "infra-"
- "service-"
wording:
patch:
- update
- initial
minor:
- change
- improve
major:
- breaking
- the # For testing purposes
release:
- release-candidate
- add-rc
```
* `version`: is not respected at the moment, introduced for potential backwards compatibility in future
* `force`: sets the "starting" version, you don't need to specify this section as the default is always `0`
* `force.commit`: allows you to set commit hash from which the calculations should start
* `blacklist`: terms to ignore when processing commits. Any commit containing these terms will be skipped in version calculations. Useful for ignoring merge commits, feature branch names, and other unwanted triggers.
* `tag_prefixes`: prefixes to strip from existing tags before parsing version numbers. Useful for monorepos where tags are prefixed with component names (e.g., `app-1.2.3`, `infra-0.5.0`). The `v` prefix is always stripped automatically.
* `matching`: how `wording` keywords are matched against commit messages — `fuzzy` (default) or `regex`. See [Matching modes](#matching-modes).
* `wording`: words the program should look for in the git commits to increment (patch|minor|major)
### Good to knows
* Word matching uses fuzzy search AND is case INSENSITIVE by default; set `matching: regex` for anchored, case-sensitive regular expression matching instead — see [Matching modes](#matching-modes)
* I do not recommend using common words ( like "the" from the example configuration )
* You can specify env variable `LOG_LEVEL=debug` to see what exactly happens during the calculations
### Telemetry
On startup this binary sends a single anonymous adoption ping — project name,
version, timestamp; no identifiers, no commit content, no repository data.
Fire-and-forget with a 2-second timeout; cannot block startup or panic.
See **[oss-telemetry — Disabling telemetry](https://github.com/lukaszraczylo/oss-telemetry#disabling-telemetry)**
for the exact wire format, source, and full opt-out documentation.
Quick opt-out: set any of `DO_NOT_TRACK=1`, `OSS_TELEMETRY_DISABLED=1`,
or `SEMVER_GENERATOR_DISABLE_TELEMETRY=1`.