FerrFlow
Universal semantic versioning for monorepos and classic repos.
FerrFlow reads your commit history, determines the right version bump, updates your version files, generates a changelog, and creates a tagged release — for any language, any repo layout.
Why FerrFlow?
Most versioning tools are tied to a specific ecosystem (semantic-release for JS, cargo-release for Rust) or require a Node.js runtime. FerrFlow is a single compiled binary with no runtime dependencies.
| Tool | Monorepo | Multi-language | Runtime |
|---|---|---|---|
| semantic-release | plugins | JS only | Node.js |
| changesets | manual | JS only | Node.js |
| knope | limited | partial | none |
| FerrFlow | native | yes | none |
Supported version files
| Format | File | Ecosystem |
|---|---|---|
| TOML | Cargo.toml |
Rust |
| TOML | pyproject.toml |
Python |
| JSON | package.json |
Node.js |
| XML | pom.xml |
Java / Maven |
Installation
Cargo
npm
Docker
Pre-built binaries
Download from Releases.
Usage
# Preview what would be bumped
# Run a release
# Dry run
# Scaffold a config file
# Scaffold a config file in a specific format
# Use a specific config file
# Or set via environment variable
FERRFLOW_CONFIG=path/to/ferrflow.toml
# Print current version
# Print last release tag
# JSON output (for scripting)
Configuration
FerrFlow looks for a config file at the root of your repository, in this order:
ferrflow.jsonferrflow.json5ferrflow.toml.ferrflow(dotfile, JSON format)
If multiple config files exist, FerrFlow exits with an error listing the conflicting files. Use --config <path> (or FERRFLOW_CONFIG env var) to specify which one to use. If no config file is found, FerrFlow auto-detects common version files in the current directory.
Run ferrflow init to scaffold a config file interactively. Use --format to skip the format prompt:
JSON Schema
Add $schema to get autocompletion and validation in VS Code, WebStorm, and any JSON-aware editor:
JSON (default)
JSON5
{
workspace: {
remote: "origin",
branch: "main",
},
package: [
{
name: "my-app",
path: ".",
changelog: "CHANGELOG.md",
versioned_files: [
{ path: "package.json", format: "json" },
],
},
],
}
TOML
[]
= "origin"
= "main"
[[]]
= "my-app"
= "."
= "CHANGELOG.md"
[[]]
= "Cargo.toml"
= "toml"
Monorepo
[[]]
= "api"
= "services/api"
= "services/api/CHANGELOG.md"
= ["services/shared/"]
[[]]
= "services/api/Cargo.toml"
= "toml"
[[]]
= "frontend"
= "frontend"
= "frontend/CHANGELOG.md"
[[]]
= "frontend/package.json"
= "json"
Versioning Strategies
Each package can use its own versioning strategy. Set a default at the workspace level and override per package:
[]
= "semver" # default for all packages
[[]]
= "api"
= "packages/api"
# inherits semver from workspace
[[]]
= "site"
= "packages/site"
= "calver" # override: date-based
| Strategy | Format | Example | Description |
|---|---|---|---|
semver |
MAJOR.MINOR.PATCH |
1.4.2 |
Default, driven by conventional commits |
calver |
YYYY.M.D |
2025.3.28 |
Date-based, ignores commit types |
calver-short |
YY.M.D |
25.3.28 |
Compact date-based |
calver-seq |
YYYY.M.SEQ |
2025.3.3 |
Date + daily sequence counter |
sequential |
N |
42 |
Simple incrementing build number |
zerover |
0.MINOR.PATCH |
0.15.2 |
Permanently unstable, never hits 1.0 |
Tag Template
By default, FerrFlow tags single-repo releases as v1.2.3 and monorepo releases as api@v1.2.3. Customize with tag_template at the workspace or package level using {name} and {version} placeholders.
[]
= "v{version}" # all packages: v1.2.3
[[]]
= "api"
= "packages/api"
= "{name}/v{version}" # override: api/v1.2.3
| Layout | Default template | Example tag |
|---|---|---|
| Single repo | v{version} |
v1.2.3 |
| Monorepo | {name}@v{version} |
api@v1.2.3 |
| Custom | release-{version} |
release-1.2.3 |
Conventional Commits
FerrFlow follows the Conventional Commits spec.
| Prefix | Bump |
|---|---|
fix:, perf:, refactor: |
patch |
feat: |
minor |
feat!:, BREAKING CHANGE |
major |
chore:, docs:, ci: |
none |
CI usage
GitLab CI
release:
image: ghcr.io/ferrflow/ferrflow:latest
script:
- ferrflow release
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
GitHub Actions
- name: Release
run: ferrflow release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
License
MPL-2.0