ferrflow release
Run the full release pipeline: bump versions, update changelogs, commit, tag, push, and create a release.
ferrflow release [OPTIONS]
| Flag | Description |
|---|---|
--force |
Allow floating tags to move backward to a lower version |
--force-version |
Force a specific version, skipping commit analysis. Format: VERSION (single repo) or NAME@VERSION (monorepo) |
--channel |
Pre-release channel override (e.g. beta, rc, dev) |
--draft |
Create releases as drafts (GitHub only). A later ferrflow release without --draft detects and publishes existing drafts automatically |
--force-unlock |
Break an existing .git/ferrflow.lock before acquiring it. Use only when no other ferrflow release is running — e.g. after a crash left the lockfile behind |
What it does:
- Scans commits since the last tag for each package
- Determines the version bump from Conventional Commits
- Updates all
versionedFileswith the new version - Appends the new section to
CHANGELOG.md - Creates a git commit, opens a PR, or skips (depending on
releaseCommitMode) - Creates and pushes the git tag
- Creates a GitHub/GitLab release with the changelog as notes
Which version is bumped from
Starting with FerrFlow v3, the baseline for every bump is the highest semver-valid tag for the package (e.g. my-pkg@v2.4.1 or v2.4.1), not the value in the versioned file.
The versioned file stays the canonical write target so downstream consumers (cargo publish, Docker builds, etc.) always see a coherent version, but it is no longer the source of truth for the bump computation. This prevents two classes of silent failure:
- Parallel release workflows: two pull requests merging back-to-back used to spawn two release jobs that both read the pre-release version from the file. Both computed the same next version — the second push either collided or was silently skipped. Today the second workflow sees the first workflow's freshly-pushed tag and computes the correct next version on top of it.
- File/tag drift: a revert, a merge from an old branch, or a manual edit could leave the file behind the tags. Bumping from a stale file produced tags that collided with history and the release got silently skipped with
tag X already exists, skipping. The tag now wins; the file only wins when it is genuinely ahead (human pre-bump).
Resolution order, per package:
| Tag | File | Baseline used |
|---|---|---|
| present | present | max(tag, file) by semver |
| present | absent | tag |
| absent | present | file |
| absent | absent | strategy bootstrap (see below) |
First release on a brand-new repo
When no tag exists yet and the format has no version to read (notably go.mod, which stores the version in tags alone), FerrFlow bootstraps from the versioning strategy's zero value:
| Strategy | Bootstrap baseline |
|---|---|
semver, zerover |
0.0.0 |
sequential |
0 |
calver-seq |
0.0 |
calver, calver-short |
ignored — bump derives from today's date |
From there the first feat: commit bumps to 0.1.0 / 1 / today's date / … and the release flow creates the tag itself — no git tag foo@v0.0.0 ceremony required before the first run.
ferrflow check
Preview what ferrflow release would do without making any changes.
ferrflow check [OPTIONS]
| Flag | Description |
|---|---|
--json |
Output as JSON |
--channel |
Pre-release channel override (e.g. beta, rc, dev) |
--comment |
Post a preview comment on the current PR/MR |
ferrflow publish
Run the configured publishers for the currently-released version of each package — without bumping, committing, or tagging. ferrflow release already runs your publishers at the end of a release; ferrflow publish is for when you'd rather run them in a separate CI job that has the build toolchain and registry auth the publishers need (docker buildx, helm, a built dist/, …) which your release job may not.
ferrflow publish [PACKAGE]
| Argument | Description |
|---|---|
[PACKAGE] |
Publish a single package by name. Omit to run publishers for every package that declares them. |
It reads each package's current version from its versionedFiles (or the latest matching tag for tag-only packages), so run it after ferrflow release has cut the version — typically from a workflow keyed off the release tag. Publishers are idempotent: anything already on the registry is skipped, so a re-run is safe. Use the global --dry-run to preview without publishing.
The GitHub Action exposes this as mode: publish — it installs the binary and runs ferrflow publish for you, so a tag-triggered job only has to set up the toolchain its publishers need:
on:
push:
tags: ["v*"]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: FerrLabs/FerrFlow@v5
with:
mode: publish
ferrflow changelog
Generate or update CHANGELOG.md only, without bumping versions or creating tags.
ferrflow changelog
Takes no command-specific flags. Use the global --dry-run to print the entry without writing it.
ferrflow init
Scaffold a config file for the current repository. Detects existing version files (Cargo.toml, package.json, etc.) and generates the appropriate config.
ferrflow init [OPTIONS]
| Flag | Description |
|---|---|
--format |
Config file format: json, json5, or toml |
ferrflow status
Show the current version of each package and whether a release would be triggered.
ferrflow status [OPTIONS]
| Flag | Description |
|---|---|
--output |
Output format: text (default) or json |
Example output:
api 1.2.3 minor bump pending (1 feat commit)
site 0.4.1 no release (only chore commits)
ferrflow version
Print the current version of one or all packages. Useful in CI scripts.
ferrflow version [PACKAGE] [OPTIONS]
| Flag | Description |
|---|---|
--json |
Output as JSON |
Returns the version from the latest git tag matching the package's tag template.
ferrflow tag
Print the latest tag for one or all packages.
ferrflow tag [PACKAGE] [OPTIONS]
| Flag | Description |
|---|---|
--json |
Output as JSON |
ferrflow validate
Validate the config and the versioned files it points at, without bumping anything. Pass --repo to validate a remote repository instead of the working tree.
ferrflow validate [OPTIONS]
| Flag | Description |
|---|---|
--json |
Output as JSON |
--repo |
Remote repository to validate (e.g. owner/repo for GitHub, or gitlab:group/project) |
--ref |
Git ref for remote validation (branch, tag, or commit) |
ferrflow completions
Generate a shell completion script and print it to stdout.
ferrflow completions <SHELL>
is one of bash, elvish, fish, powershell, or zsh.
Global flags
These flags work with all commands:
| Flag | Description |
|---|---|
--dry-run |
Show what would happen without making any changes |
--verbose, -v |
Verbose output, including commit hashes and file diffs |
--config |
Path to a custom config file (default: auto-detected). Also accepts the FERRFLOW_CONFIG env variable. |
--version |
Print the FerrFlow version and exit |
--help, -h |
Print help |