@ferrflow/win32-arm64 5.19.0 → 5.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +373 -373
  2. package/README.md +556 -556
  3. package/bin/ferrflow.exe +0 -0
  4. package/package.json +11 -4
package/README.md CHANGED
@@ -1,556 +1,556 @@
1
- # FerrFlow
2
-
3
- [![Latest release](https://img.shields.io/github/v/release/FerrLabs/FerrFlow)](https://github.com/FerrLabs/FerrFlow/releases/latest)
4
- [![Quality Gate](https://sonar.ferrlabs.com/api/project_badges/measure?project=ferrflow&metric=alert_status&token=sqb_53f0d93466bd01a6c6a94a15125d5aa8390c67fa)](https://sonar.ferrlabs.com/dashboard?id=ferrflow)
5
- [![Coverage](https://sonar.ferrlabs.com/api/project_badges/measure?project=ferrflow&metric=coverage&token=sqb_53f0d93466bd01a6c6a94a15125d5aa8390c67fa)](https://sonar.ferrlabs.com/dashboard?id=ferrflow)
6
- [![License](https://img.shields.io/github/license/FerrLabs/FerrFlow)](LICENSE)
7
- [![Socket Badge](https://badge.socket.dev/npm/package/ferrflow/latest)](https://badge.socket.dev/npm/package/ferrflow/latest)
8
- [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/FerrLabs/FerrFlow/badge)](https://scorecard.dev/viewer/?uri=github.com/FerrLabs/FerrFlow)
9
-
10
- Universal semantic versioning for monorepos and classic repos.
11
-
12
- 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.
13
-
14
- ## Why FerrFlow?
15
-
16
- A single compiled binary with no runtime dependencies. Native monorepo support, multi-language versioning, and works with any repo layout.
17
-
18
- ## Supported version files
19
-
20
- | Format | File | Ecosystem | Selector |
21
- |--------|------|-----------|----------|
22
- | `toml` | `Cargo.toml` | Rust | `package.version` |
23
- | `toml` | `pyproject.toml` | Python | `project.version` or `tool.poetry.version` |
24
- | `json` | `package.json` | Node.js | `version` |
25
- | `json` | `composer.json` | PHP | `version` |
26
- | `xml` | `pom.xml` | Java / Maven | first `<version>` that's a direct child of the root element (skips `<parent>` and dependencies) |
27
- | `csproj` | `*.csproj` | .NET (C#, F#) | `<Version>` in `<PropertyGroup>` |
28
- | `gradle` | `build.gradle`, `build.gradle.kts` | Java / Kotlin | `version = "…"` |
29
- | `helm` / `chartyaml` | `Chart.yaml` | Kubernetes / Helm | top-level `version:` |
30
- | `pubspecyaml` | `pubspec.yaml` | Dart / Flutter | top-level `version:` |
31
- | `mixexs` | `mix.exs` | Elixir | `version: "…"` in `def project` |
32
- | `gemspec` | `*.gemspec` | Ruby | `s.version = "…"` |
33
- | `packageswift` | `Package.swift` | Swift | top-level `let <name>Version = "…"` |
34
- | `gomod` | `go.mod` | Go | git tag only — no file write |
35
- | `txt` | `VERSION`, `VERSION.txt` | Any | entire file content |
36
-
37
- ## Installation
38
-
39
- **Cargo**
40
-
41
- ```bash
42
- cargo install ferrflow
43
- ```
44
-
45
- **npm**
46
-
47
- ```bash
48
- npm install -D ferrflow
49
- ```
50
-
51
- (`@ferrlabs/ferrflow` is kept as a published alias for the same package.)
52
-
53
- **Docker**
54
-
55
- ```bash
56
- docker run ghcr.io/ferrlabs/ferrflow:latest check
57
- ```
58
-
59
- **Pre-built binaries**
60
-
61
- Download from [Releases](https://github.com/FerrLabs/FerrFlow/releases).
62
-
63
- ## Usage
64
-
65
- ```bash
66
- # Preview what would be bumped
67
- ferrflow check
68
-
69
- # Run a release
70
- ferrflow release
71
-
72
- # Dry run
73
- ferrflow release --dry-run
74
-
75
- # Force a specific version (skips commit analysis)
76
- ferrflow release --force-version 2.0.0 # single repo
77
- ferrflow release --force-version api@3.0.0 # monorepo
78
-
79
- # Pre-release
80
- ferrflow release --channel beta
81
-
82
- # Scaffold a config file
83
- ferrflow init
84
-
85
- # Scaffold a config file in a specific format
86
- ferrflow init --format json5
87
-
88
- # Use a specific config file
89
- ferrflow check --config path/to/ferrflow.toml
90
-
91
- # Or set via environment variable
92
- FERRFLOW_CONFIG=path/to/ferrflow.toml ferrflow check
93
-
94
- # Print current version
95
- ferrflow version # single repo
96
- ferrflow version api # monorepo, specific package
97
-
98
- # Print last release tag
99
- ferrflow tag
100
- ferrflow tag api
101
-
102
- # JSON output (for scripting)
103
- ferrflow version --json
104
- ferrflow tag --json
105
-
106
- # Shell completions
107
- ferrflow completions bash >> ~/.bash_completion
108
- ferrflow completions zsh > ~/.zfunc/_ferrflow
109
- ferrflow completions fish > ~/.config/fish/completions/ferrflow.fish
110
- ```
111
-
112
- Pre-generated completion scripts are also available as `ferrflow-completions.tar.gz` in each [GitHub release](https://github.com/FerrLabs/FerrFlow/releases).
113
-
114
- ## Configuration
115
-
116
- FerrFlow looks for a config file at the root of your repository, in this order:
117
-
118
- 1. `ferrflow.json`
119
- 2. `ferrflow.json5`
120
- 3. `ferrflow.toml`
121
- 4. `.ferrflow` (dotfile, JSON format)
122
-
123
- 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.
124
-
125
- Run `ferrflow init` to scaffold a config file interactively. Use `--format` to skip the format prompt:
126
-
127
- ```bash
128
- ferrflow init # asks which format (default: json)
129
- ferrflow init --format json5
130
- ferrflow init --format toml
131
- ferrflow init --format dotfile # generates .ferrflow
132
- ```
133
-
134
- ### JSON Schema
135
-
136
- Add `$schema` to get autocompletion and validation in VS Code, WebStorm, and any JSON-aware editor:
137
-
138
- ```json
139
- {
140
- "$schema": "https://ferrflow.com/schema/ferrflow.json"
141
- }
142
- ```
143
-
144
- ### JSON (default)
145
-
146
- ```json
147
- {
148
- "$schema": "https://ferrflow.com/schema/ferrflow.json",
149
- "workspace": {
150
- "remote": "origin",
151
- "branch": "main"
152
- },
153
- "package": [
154
- {
155
- "name": "my-app",
156
- "path": ".",
157
- "changelog": "CHANGELOG.md",
158
- "versionedFiles": [
159
- { "path": "package.json", "format": "json" }
160
- ]
161
- }
162
- ]
163
- }
164
- ```
165
-
166
- ### JSON5
167
-
168
- ```json5
169
- {
170
- workspace: {
171
- remote: "origin",
172
- branch: "main",
173
- },
174
- package: [
175
- {
176
- name: "my-app",
177
- path: ".",
178
- changelog: "CHANGELOG.md",
179
- versionedFiles: [
180
- { path: "package.json", format: "json" },
181
- ],
182
- },
183
- ],
184
- }
185
- ```
186
-
187
- ### TOML
188
-
189
- ```toml
190
- [workspace]
191
- remote = "origin"
192
- branch = "main"
193
-
194
- [[package]]
195
- name = "my-app"
196
- path = "."
197
- changelog = "CHANGELOG.md"
198
-
199
- [[package.versioned_files]]
200
- path = "Cargo.toml"
201
- format = "toml"
202
- ```
203
-
204
- ### Monorepo
205
-
206
- <details>
207
- <summary>JSON</summary>
208
-
209
- ```json
210
- {
211
- "package": [
212
- {
213
- "name": "api",
214
- "path": "services/api",
215
- "changelog": "services/api/CHANGELOG.md",
216
- "sharedPaths": ["services/shared/"],
217
- "versionedFiles": [
218
- { "path": "services/api/Cargo.toml", "format": "toml" }
219
- ]
220
- },
221
- {
222
- "name": "frontend",
223
- "path": "frontend",
224
- "changelog": "frontend/CHANGELOG.md",
225
- "versionedFiles": [
226
- { "path": "frontend/package.json", "format": "json" }
227
- ]
228
- }
229
- ]
230
- }
231
- ```
232
-
233
- </details>
234
-
235
- <details>
236
- <summary>TOML</summary>
237
-
238
- ```toml
239
- [[package]]
240
- name = "api"
241
- path = "services/api"
242
- changelog = "services/api/CHANGELOG.md"
243
- shared_paths = ["services/shared/"]
244
-
245
- [[package.versioned_files]]
246
- path = "services/api/Cargo.toml"
247
- format = "toml"
248
-
249
- [[package]]
250
- name = "frontend"
251
- path = "frontend"
252
- changelog = "frontend/CHANGELOG.md"
253
-
254
- [[package.versioned_files]]
255
- path = "frontend/package.json"
256
- format = "json"
257
- ```
258
-
259
- </details>
260
-
261
- ## Versioning Strategies
262
-
263
- Each package can use its own versioning strategy. Set a default at the workspace level and override per package:
264
-
265
- ```toml
266
- [workspace]
267
- versioning = "semver" # default for all packages
268
-
269
- [[package]]
270
- name = "api"
271
- path = "packages/api"
272
- # inherits semver from workspace
273
-
274
- [[package]]
275
- name = "site"
276
- path = "packages/site"
277
- versioning = "calver" # override: date-based
278
- ```
279
-
280
- | Strategy | Format | Example | Description |
281
- |----------|--------|---------|-------------|
282
- | `semver` | `MAJOR.MINOR.PATCH` | `1.4.2` | Default, driven by conventional commits |
283
- | `calver` | `YYYY.M.D` | `2025.3.28` | Date-based, ignores commit types |
284
- | `calver-short` | `YY.M.D` | `25.3.28` | Compact date-based |
285
- | `calver-seq` | `YYYY.M.SEQ` | `2025.3.3` | Date + daily sequence counter |
286
- | `sequential` | `N` | `42` | Simple incrementing build number |
287
- | `zerover` | `0.MINOR.PATCH` | `0.15.2` | Permanently unstable, never hits 1.0 |
288
-
289
- ## Tag Template
290
-
291
- 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.
292
-
293
- ```toml
294
- [workspace]
295
- tag_template = "v{version}" # all packages: v1.2.3
296
-
297
- [[package]]
298
- name = "api"
299
- path = "packages/api"
300
- tag_template = "{name}/v{version}" # override: api/v1.2.3
301
- ```
302
-
303
- | Layout | Default template | Example tag |
304
- |--------|-----------------|-------------|
305
- | Single repo | `v{version}` | `v1.2.3` |
306
- | Monorepo | `{name}@v{version}` | `api@v1.2.3` |
307
- | Custom | `release-{version}` | `release-1.2.3` |
308
-
309
- ## Pre-release Channels
310
-
311
- Publish pre-release versions (alpha, beta, rc, dev) using the `--channel` flag or branch-based configuration. Pre-release versions follow the format `MAJOR.MINOR.PATCH-CHANNEL.IDENTIFIER`.
312
-
313
- ### CLI flag
314
-
315
- ```bash
316
- ferrflow release --channel beta # 2.0.0-beta.1
317
- ferrflow check --channel rc # preview pre-release version
318
- ```
319
-
320
- ### Branch-based configuration
321
-
322
- Map branches to channels automatically:
323
-
324
- ```json
325
- {
326
- "workspace": {
327
- "branches": [
328
- { "name": "main", "channel": false },
329
- { "name": "develop", "channel": "dev", "prereleaseIdentifier": "timestamp" },
330
- { "name": "release/*", "channel": "rc" }
331
- ]
332
- }
333
- }
334
- ```
335
-
336
- Branch names support glob patterns. The first match wins. Wildcards match across
337
- `/` separators, so `*` matches branches like `fix/global` and `feature/*` matches
338
- `feature/auth/oauth`.
339
-
340
- ### Identifier strategies
341
-
342
- | Strategy | Example | Description |
343
- |----------|---------|-------------|
344
- | `increment` | `-beta.3` | Auto-incrementing counter (default) |
345
- | `timestamp` | `-dev.20250402T1430` | UTC timestamp |
346
- | `short-hash` | `-dev.a1b2c3d` | Git short hash |
347
- | `timestamp-hash` | `-dev.20250402T1430-a1b2c3d` | Timestamp + hash |
348
-
349
- ### Behavior
350
-
351
- - Floating tags (e.g. `v1`, `v1.2`) are never moved by pre-release versions
352
- - GitHub Releases are marked as pre-release
353
- - Stable releases include all commits since the last stable tag (skipping pre-release tags)
354
- - Hook environment includes `FERRFLOW_CHANNEL` and `FERRFLOW_IS_PRERELEASE`
355
-
356
- ## Release Commit Mode
357
-
358
- Controls how FerrFlow commits version bumps and changelog updates after a release.
359
-
360
- ```toml
361
- [workspace]
362
- release_commit_mode = "commit" # default
363
- ```
364
-
365
- | Mode | Description |
366
- |------|-------------|
367
- | `commit` | Push a release commit directly to the branch |
368
- | `pr` | Create a pull request with the release changes |
369
- | `none` | Skip committing entirely (useful when another tool handles it) |
370
-
371
- When using `pr` mode, `auto_merge_releases` controls whether the PR is automatically merged:
372
-
373
- ```toml
374
- [workspace]
375
- release_commit_mode = "pr"
376
- auto_merge_releases = true # default
377
- ```
378
-
379
- ### Release Commit Scope
380
-
381
- In monorepo mode, controls whether all package bumps go into a single commit or one commit per package:
382
-
383
- ```toml
384
- [workspace]
385
- release_commit_scope = "grouped" # default
386
- ```
387
-
388
- | Scope | Description |
389
- |-------|-------------|
390
- | `grouped` | Single commit for all packages (e.g. `chore(release): api v1.0.0, site v2.1.0`) |
391
- | `per-package` | One commit per package (e.g. `chore(release): api v1.0.0`, then `chore(release): site v2.1.0`) |
392
-
393
- Per-package commits make it easier to revert a single package bump without affecting others. This works with both `commit` and `pr` release modes.
394
-
395
- ### Skip CI
396
-
397
- By default, release commits in `commit` mode include `[skip ci]` in the message to avoid triggering a CI loop. Override with `skip_ci`:
398
-
399
- ```toml
400
- [workspace]
401
- skip_ci = false # force CI to run on release commits
402
- ```
403
-
404
- In `pr` mode, `skip_ci` defaults to `false` since the PR merge triggers CI naturally.
405
-
406
- ## Floating Tags
407
-
408
- Move abbreviated tags (e.g. `v1`, `v1.2`) to always point at the latest matching release:
409
-
410
- ```toml
411
- [workspace]
412
- floating_tags = ["major"] # creates/moves v1 when releasing v1.2.3
413
- ```
414
-
415
- | Level | Tag | Points to |
416
- |-------|-----|-----------|
417
- | `major` | `v1` | Latest `v1.x.x` |
418
- | `minor` | `v1.2` | Latest `v1.2.x` |
419
-
420
- Floating tags are never moved by pre-release versions. Override per package:
421
-
422
- ```toml
423
- [[package]]
424
- name = "api"
425
- path = "packages/api"
426
- floating_tags = ["major", "minor"]
427
- ```
428
-
429
- ## Orphaned Tag Strategy
430
-
431
- After a rebase + force-push, existing tags may point to commits that no longer exist on the branch. `orphaned_tag_strategy` controls how FerrFlow handles this:
432
-
433
- ```toml
434
- [workspace]
435
- orphaned_tag_strategy = "warn" # default
436
- ```
437
-
438
- | Strategy | Description |
439
- |----------|-------------|
440
- | `warn` | Log a warning and skip the orphaned tag |
441
- | `treeHash` | Attempt recovery by matching the commit's tree hash |
442
- | `message` | Attempt recovery by matching the commit message |
443
-
444
- ## Recover Missed Releases
445
-
446
- In monorepos, a package can miss a release if its files changed but FerrFlow wasn't run. Enable `recover_missed_releases` to compare files against the last tag instead of just the last commit:
447
-
448
- ```toml
449
- [workspace]
450
- recover_missed_releases = true # default: false
451
- ```
452
-
453
- ## Package Dependencies
454
-
455
- In a monorepo, use `depends_on` to automatically patch-bump a package when one of its dependencies is released:
456
-
457
- ```json
458
- {
459
- "package": [
460
- { "name": "core", "path": "packages/core" },
461
- {
462
- "name": "cli",
463
- "path": "packages/cli",
464
- "depends_on": ["core"]
465
- }
466
- ]
467
- }
468
- ```
469
-
470
- When `core` is bumped, `cli` gets a patch bump even if it had no direct commits.
471
-
472
- ## Hooks
473
-
474
- Run shell commands at lifecycle points during a release. Hooks can be set at the workspace level (applies to all packages) or per package:
475
-
476
- ```toml
477
- [workspace.hooks]
478
- pre_bump = "echo 'about to bump'"
479
- post_bump = "cargo check"
480
- pre_commit = "npm run build"
481
- pre_publish = "npm pack --dry-run"
482
- post_publish = "notify-slack.sh"
483
- on_failure = "abort" # or "continue"
484
- ```
485
-
486
- | Hook | When |
487
- |------|------|
488
- | `pre_bump` | After bump calculation, before writing version files |
489
- | `post_bump` | After writing version files, before changelog generation |
490
- | `pre_commit` | After changelog generation, before git commit |
491
- | `pre_publish` | After commit and tag, before push |
492
- | `post_publish` | After push and release creation |
493
-
494
- If a hook exits non-zero and `on_failure` is `abort` (default), the release is cancelled. Set `on_failure` to `continue` to ignore hook failures.
495
-
496
- Hook commands receive environment variables: `FERRFLOW_PACKAGE`, `FERRFLOW_OLD_VERSION`, `FERRFLOW_NEW_VERSION`, `FERRFLOW_BUMP_TYPE`, `FERRFLOW_TAG`, `FERRFLOW_PACKAGE_PATH`, `FERRFLOW_DRY_RUN`, `FERRFLOW_CHANNEL`, `FERRFLOW_IS_PRERELEASE`.
497
-
498
- ## Conventional Commits
499
-
500
- FerrFlow follows the [Conventional Commits](https://www.conventionalcommits.org/) spec.
501
-
502
- | Prefix | Bump |
503
- |--------|------|
504
- | `fix:`, `perf:`, `refactor:` | patch |
505
- | `feat:` | minor |
506
- | `feat!:`, `BREAKING CHANGE` | major |
507
- | `chore:`, `docs:`, `ci:` | none |
508
-
509
- ## CI usage
510
-
511
- **GitLab CI**
512
-
513
- ```yaml
514
- release:
515
- image: ghcr.io/ferrlabs/ferrflow:latest
516
- script:
517
- - ferrflow release
518
- rules:
519
- - if: '$CI_COMMIT_BRANCH == "main"'
520
- ```
521
-
522
- **GitHub Actions**
523
-
524
- ```yaml
525
- - name: Release
526
- run: ferrflow release
527
- env:
528
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
529
- ```
530
-
531
- ### Using the hosted bot (ferrflow[bot])
532
-
533
- Install the [FerrFlow GitHub App](https://github.com/apps/ferrflow) on your repo or org, then opt in with `bot: true`. Release commits, tags, and GitHub Releases are authored by `ferrflow[bot]` and downstream workflows triggered by those events run normally (unlike the default `GITHUB_TOKEN`, which suppresses them).
534
-
535
- ```yaml
536
- permissions:
537
- id-token: write
538
- contents: read
539
-
540
- steps:
541
- - uses: actions/checkout@v6
542
- with:
543
- fetch-depth: 0
544
- - uses: FerrLabs/FerrFlow@v4
545
- with:
546
- bot: true
547
- ```
548
-
549
- That's it — no `setup-node`, no extra dependencies. FerrFlow's Rust binary handles the OIDC exchange directly, so minimal self-hosted runners work out of the box.
550
-
551
- Three auth modes are supported: `bot: true` uses the hosted FerrFlow App (recommended); `token: <PAT>` uses a personal access token or your own GitHub App token (DIY); omitting both falls back to the workflow's `GITHUB_TOKEN` (simplest, but release events won't trigger downstream workflows).
552
-
553
- ## License
554
-
555
- [MPL-2.0](LICENSE)
556
-
1
+ # FerrFlow
2
+
3
+ [![Latest release](https://img.shields.io/github/v/release/FerrLabs/FerrFlow)](https://github.com/FerrLabs/FerrFlow/releases/latest)
4
+ [![Quality Gate](https://sonar.ferrlabs.com/api/project_badges/measure?project=ferrflow&metric=alert_status&token=sqb_53f0d93466bd01a6c6a94a15125d5aa8390c67fa)](https://sonar.ferrlabs.com/dashboard?id=ferrflow)
5
+ [![Coverage](https://sonar.ferrlabs.com/api/project_badges/measure?project=ferrflow&metric=coverage&token=sqb_53f0d93466bd01a6c6a94a15125d5aa8390c67fa)](https://sonar.ferrlabs.com/dashboard?id=ferrflow)
6
+ [![License](https://img.shields.io/github/license/FerrLabs/FerrFlow)](LICENSE)
7
+ [![Socket Badge](https://badge.socket.dev/npm/package/ferrflow/latest)](https://badge.socket.dev/npm/package/ferrflow/latest)
8
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/FerrLabs/FerrFlow/badge)](https://scorecard.dev/viewer/?uri=github.com/FerrLabs/FerrFlow)
9
+
10
+ Universal semantic versioning for monorepos and classic repos.
11
+
12
+ 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.
13
+
14
+ ## Why FerrFlow?
15
+
16
+ A single compiled binary with no runtime dependencies. Native monorepo support, multi-language versioning, and works with any repo layout.
17
+
18
+ ## Supported version files
19
+
20
+ | Format | File | Ecosystem | Selector |
21
+ |--------|------|-----------|----------|
22
+ | `toml` | `Cargo.toml` | Rust | `package.version` |
23
+ | `toml` | `pyproject.toml` | Python | `project.version` or `tool.poetry.version` |
24
+ | `json` | `package.json` | Node.js | `version` |
25
+ | `json` | `composer.json` | PHP | `version` |
26
+ | `xml` | `pom.xml` | Java / Maven | first `<version>` that's a direct child of the root element (skips `<parent>` and dependencies) |
27
+ | `csproj` | `*.csproj` | .NET (C#, F#) | `<Version>` in `<PropertyGroup>` |
28
+ | `gradle` | `build.gradle`, `build.gradle.kts` | Java / Kotlin | `version = "…"` |
29
+ | `helm` / `chartyaml` | `Chart.yaml` | Kubernetes / Helm | top-level `version:` |
30
+ | `pubspecyaml` | `pubspec.yaml` | Dart / Flutter | top-level `version:` |
31
+ | `mixexs` | `mix.exs` | Elixir | `version: "…"` in `def project` |
32
+ | `gemspec` | `*.gemspec` | Ruby | `s.version = "…"` |
33
+ | `packageswift` | `Package.swift` | Swift | top-level `let <name>Version = "…"` |
34
+ | `gomod` | `go.mod` | Go | git tag only — no file write |
35
+ | `txt` | `VERSION`, `VERSION.txt` | Any | entire file content |
36
+
37
+ ## Installation
38
+
39
+ **Cargo**
40
+
41
+ ```bash
42
+ cargo install ferrflow
43
+ ```
44
+
45
+ **npm**
46
+
47
+ ```bash
48
+ npm install -D ferrflow
49
+ ```
50
+
51
+ (`@ferrlabs/ferrflow` is kept as a published alias for the same package.)
52
+
53
+ **Docker**
54
+
55
+ ```bash
56
+ docker run ghcr.io/ferrlabs/ferrflow:latest check
57
+ ```
58
+
59
+ **Pre-built binaries**
60
+
61
+ Download from [Releases](https://github.com/FerrLabs/FerrFlow/releases).
62
+
63
+ ## Usage
64
+
65
+ ```bash
66
+ # Preview what would be bumped
67
+ ferrflow check
68
+
69
+ # Run a release
70
+ ferrflow release
71
+
72
+ # Dry run
73
+ ferrflow release --dry-run
74
+
75
+ # Force a specific version (skips commit analysis)
76
+ ferrflow release --force-version 2.0.0 # single repo
77
+ ferrflow release --force-version api@3.0.0 # monorepo
78
+
79
+ # Pre-release
80
+ ferrflow release --channel beta
81
+
82
+ # Scaffold a config file
83
+ ferrflow init
84
+
85
+ # Scaffold a config file in a specific format
86
+ ferrflow init --format json5
87
+
88
+ # Use a specific config file
89
+ ferrflow check --config path/to/ferrflow.toml
90
+
91
+ # Or set via environment variable
92
+ FERRFLOW_CONFIG=path/to/ferrflow.toml ferrflow check
93
+
94
+ # Print current version
95
+ ferrflow version # single repo
96
+ ferrflow version api # monorepo, specific package
97
+
98
+ # Print last release tag
99
+ ferrflow tag
100
+ ferrflow tag api
101
+
102
+ # JSON output (for scripting)
103
+ ferrflow version --json
104
+ ferrflow tag --json
105
+
106
+ # Shell completions
107
+ ferrflow completions bash >> ~/.bash_completion
108
+ ferrflow completions zsh > ~/.zfunc/_ferrflow
109
+ ferrflow completions fish > ~/.config/fish/completions/ferrflow.fish
110
+ ```
111
+
112
+ Pre-generated completion scripts are also available as `ferrflow-completions.tar.gz` in each [GitHub release](https://github.com/FerrLabs/FerrFlow/releases).
113
+
114
+ ## Configuration
115
+
116
+ FerrFlow looks for a config file at the root of your repository, in this order:
117
+
118
+ 1. `ferrflow.json`
119
+ 2. `ferrflow.json5`
120
+ 3. `ferrflow.toml`
121
+ 4. `.ferrflow` (dotfile, JSON format)
122
+
123
+ 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.
124
+
125
+ Run `ferrflow init` to scaffold a config file interactively. Use `--format` to skip the format prompt:
126
+
127
+ ```bash
128
+ ferrflow init # asks which format (default: json)
129
+ ferrflow init --format json5
130
+ ferrflow init --format toml
131
+ ferrflow init --format dotfile # generates .ferrflow
132
+ ```
133
+
134
+ ### JSON Schema
135
+
136
+ Add `$schema` to get autocompletion and validation in VS Code, WebStorm, and any JSON-aware editor:
137
+
138
+ ```json
139
+ {
140
+ "$schema": "https://ferrflow.com/schema/ferrflow.json"
141
+ }
142
+ ```
143
+
144
+ ### JSON (default)
145
+
146
+ ```json
147
+ {
148
+ "$schema": "https://ferrflow.com/schema/ferrflow.json",
149
+ "workspace": {
150
+ "remote": "origin",
151
+ "branch": "main"
152
+ },
153
+ "package": [
154
+ {
155
+ "name": "my-app",
156
+ "path": ".",
157
+ "changelog": "CHANGELOG.md",
158
+ "versionedFiles": [
159
+ { "path": "package.json", "format": "json" }
160
+ ]
161
+ }
162
+ ]
163
+ }
164
+ ```
165
+
166
+ ### JSON5
167
+
168
+ ```json5
169
+ {
170
+ workspace: {
171
+ remote: "origin",
172
+ branch: "main",
173
+ },
174
+ package: [
175
+ {
176
+ name: "my-app",
177
+ path: ".",
178
+ changelog: "CHANGELOG.md",
179
+ versionedFiles: [
180
+ { path: "package.json", format: "json" },
181
+ ],
182
+ },
183
+ ],
184
+ }
185
+ ```
186
+
187
+ ### TOML
188
+
189
+ ```toml
190
+ [workspace]
191
+ remote = "origin"
192
+ branch = "main"
193
+
194
+ [[package]]
195
+ name = "my-app"
196
+ path = "."
197
+ changelog = "CHANGELOG.md"
198
+
199
+ [[package.versioned_files]]
200
+ path = "Cargo.toml"
201
+ format = "toml"
202
+ ```
203
+
204
+ ### Monorepo
205
+
206
+ <details>
207
+ <summary>JSON</summary>
208
+
209
+ ```json
210
+ {
211
+ "package": [
212
+ {
213
+ "name": "api",
214
+ "path": "services/api",
215
+ "changelog": "services/api/CHANGELOG.md",
216
+ "sharedPaths": ["services/shared/"],
217
+ "versionedFiles": [
218
+ { "path": "services/api/Cargo.toml", "format": "toml" }
219
+ ]
220
+ },
221
+ {
222
+ "name": "frontend",
223
+ "path": "frontend",
224
+ "changelog": "frontend/CHANGELOG.md",
225
+ "versionedFiles": [
226
+ { "path": "frontend/package.json", "format": "json" }
227
+ ]
228
+ }
229
+ ]
230
+ }
231
+ ```
232
+
233
+ </details>
234
+
235
+ <details>
236
+ <summary>TOML</summary>
237
+
238
+ ```toml
239
+ [[package]]
240
+ name = "api"
241
+ path = "services/api"
242
+ changelog = "services/api/CHANGELOG.md"
243
+ shared_paths = ["services/shared/"]
244
+
245
+ [[package.versioned_files]]
246
+ path = "services/api/Cargo.toml"
247
+ format = "toml"
248
+
249
+ [[package]]
250
+ name = "frontend"
251
+ path = "frontend"
252
+ changelog = "frontend/CHANGELOG.md"
253
+
254
+ [[package.versioned_files]]
255
+ path = "frontend/package.json"
256
+ format = "json"
257
+ ```
258
+
259
+ </details>
260
+
261
+ ## Versioning Strategies
262
+
263
+ Each package can use its own versioning strategy. Set a default at the workspace level and override per package:
264
+
265
+ ```toml
266
+ [workspace]
267
+ versioning = "semver" # default for all packages
268
+
269
+ [[package]]
270
+ name = "api"
271
+ path = "packages/api"
272
+ # inherits semver from workspace
273
+
274
+ [[package]]
275
+ name = "site"
276
+ path = "packages/site"
277
+ versioning = "calver" # override: date-based
278
+ ```
279
+
280
+ | Strategy | Format | Example | Description |
281
+ |----------|--------|---------|-------------|
282
+ | `semver` | `MAJOR.MINOR.PATCH` | `1.4.2` | Default, driven by conventional commits |
283
+ | `calver` | `YYYY.M.D` | `2025.3.28` | Date-based, ignores commit types |
284
+ | `calver-short` | `YY.M.D` | `25.3.28` | Compact date-based |
285
+ | `calver-seq` | `YYYY.M.SEQ` | `2025.3.3` | Date + daily sequence counter |
286
+ | `sequential` | `N` | `42` | Simple incrementing build number |
287
+ | `zerover` | `0.MINOR.PATCH` | `0.15.2` | Permanently unstable, never hits 1.0 |
288
+
289
+ ## Tag Template
290
+
291
+ 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.
292
+
293
+ ```toml
294
+ [workspace]
295
+ tag_template = "v{version}" # all packages: v1.2.3
296
+
297
+ [[package]]
298
+ name = "api"
299
+ path = "packages/api"
300
+ tag_template = "{name}/v{version}" # override: api/v1.2.3
301
+ ```
302
+
303
+ | Layout | Default template | Example tag |
304
+ |--------|-----------------|-------------|
305
+ | Single repo | `v{version}` | `v1.2.3` |
306
+ | Monorepo | `{name}@v{version}` | `api@v1.2.3` |
307
+ | Custom | `release-{version}` | `release-1.2.3` |
308
+
309
+ ## Pre-release Channels
310
+
311
+ Publish pre-release versions (alpha, beta, rc, dev) using the `--channel` flag or branch-based configuration. Pre-release versions follow the format `MAJOR.MINOR.PATCH-CHANNEL.IDENTIFIER`.
312
+
313
+ ### CLI flag
314
+
315
+ ```bash
316
+ ferrflow release --channel beta # 2.0.0-beta.1
317
+ ferrflow check --channel rc # preview pre-release version
318
+ ```
319
+
320
+ ### Branch-based configuration
321
+
322
+ Map branches to channels automatically:
323
+
324
+ ```json
325
+ {
326
+ "workspace": {
327
+ "branches": [
328
+ { "name": "main", "channel": false },
329
+ { "name": "develop", "channel": "dev", "prereleaseIdentifier": "timestamp" },
330
+ { "name": "release/*", "channel": "rc" }
331
+ ]
332
+ }
333
+ }
334
+ ```
335
+
336
+ Branch names support glob patterns. The first match wins. Wildcards match across
337
+ `/` separators, so `*` matches branches like `fix/global` and `feature/*` matches
338
+ `feature/auth/oauth`.
339
+
340
+ ### Identifier strategies
341
+
342
+ | Strategy | Example | Description |
343
+ |----------|---------|-------------|
344
+ | `increment` | `-beta.3` | Auto-incrementing counter (default) |
345
+ | `timestamp` | `-dev.20250402T1430` | UTC timestamp |
346
+ | `short-hash` | `-dev.a1b2c3d` | Git short hash |
347
+ | `timestamp-hash` | `-dev.20250402T1430-a1b2c3d` | Timestamp + hash |
348
+
349
+ ### Behavior
350
+
351
+ - Floating tags (e.g. `v1`, `v1.2`) are never moved by pre-release versions
352
+ - GitHub Releases are marked as pre-release
353
+ - Stable releases include all commits since the last stable tag (skipping pre-release tags)
354
+ - Hook environment includes `FERRFLOW_CHANNEL` and `FERRFLOW_IS_PRERELEASE`
355
+
356
+ ## Release Commit Mode
357
+
358
+ Controls how FerrFlow commits version bumps and changelog updates after a release.
359
+
360
+ ```toml
361
+ [workspace]
362
+ release_commit_mode = "commit" # default
363
+ ```
364
+
365
+ | Mode | Description |
366
+ |------|-------------|
367
+ | `commit` | Push a release commit directly to the branch |
368
+ | `pr` | Create a pull request with the release changes |
369
+ | `none` | Skip committing entirely (useful when another tool handles it) |
370
+
371
+ When using `pr` mode, `auto_merge_releases` controls whether the PR is automatically merged:
372
+
373
+ ```toml
374
+ [workspace]
375
+ release_commit_mode = "pr"
376
+ auto_merge_releases = true # default
377
+ ```
378
+
379
+ ### Release Commit Scope
380
+
381
+ In monorepo mode, controls whether all package bumps go into a single commit or one commit per package:
382
+
383
+ ```toml
384
+ [workspace]
385
+ release_commit_scope = "grouped" # default
386
+ ```
387
+
388
+ | Scope | Description |
389
+ |-------|-------------|
390
+ | `grouped` | Single commit for all packages (e.g. `chore(release): api v1.0.0, site v2.1.0`) |
391
+ | `per-package` | One commit per package (e.g. `chore(release): api v1.0.0`, then `chore(release): site v2.1.0`) |
392
+
393
+ Per-package commits make it easier to revert a single package bump without affecting others. This works with both `commit` and `pr` release modes.
394
+
395
+ ### Skip CI
396
+
397
+ By default, release commits in `commit` mode include `[skip ci]` in the message to avoid triggering a CI loop. Override with `skip_ci`:
398
+
399
+ ```toml
400
+ [workspace]
401
+ skip_ci = false # force CI to run on release commits
402
+ ```
403
+
404
+ In `pr` mode, `skip_ci` defaults to `false` since the PR merge triggers CI naturally.
405
+
406
+ ## Floating Tags
407
+
408
+ Move abbreviated tags (e.g. `v1`, `v1.2`) to always point at the latest matching release:
409
+
410
+ ```toml
411
+ [workspace]
412
+ floating_tags = ["major"] # creates/moves v1 when releasing v1.2.3
413
+ ```
414
+
415
+ | Level | Tag | Points to |
416
+ |-------|-----|-----------|
417
+ | `major` | `v1` | Latest `v1.x.x` |
418
+ | `minor` | `v1.2` | Latest `v1.2.x` |
419
+
420
+ Floating tags are never moved by pre-release versions. Override per package:
421
+
422
+ ```toml
423
+ [[package]]
424
+ name = "api"
425
+ path = "packages/api"
426
+ floating_tags = ["major", "minor"]
427
+ ```
428
+
429
+ ## Orphaned Tag Strategy
430
+
431
+ After a rebase + force-push, existing tags may point to commits that no longer exist on the branch. `orphaned_tag_strategy` controls how FerrFlow handles this:
432
+
433
+ ```toml
434
+ [workspace]
435
+ orphaned_tag_strategy = "warn" # default
436
+ ```
437
+
438
+ | Strategy | Description |
439
+ |----------|-------------|
440
+ | `warn` | Log a warning and skip the orphaned tag |
441
+ | `treeHash` | Attempt recovery by matching the commit's tree hash |
442
+ | `message` | Attempt recovery by matching the commit message |
443
+
444
+ ## Recover Missed Releases
445
+
446
+ In monorepos, a package can miss a release if its files changed but FerrFlow wasn't run. Enable `recover_missed_releases` to compare files against the last tag instead of just the last commit:
447
+
448
+ ```toml
449
+ [workspace]
450
+ recover_missed_releases = true # default: false
451
+ ```
452
+
453
+ ## Package Dependencies
454
+
455
+ In a monorepo, use `depends_on` to automatically patch-bump a package when one of its dependencies is released:
456
+
457
+ ```json
458
+ {
459
+ "package": [
460
+ { "name": "core", "path": "packages/core" },
461
+ {
462
+ "name": "cli",
463
+ "path": "packages/cli",
464
+ "depends_on": ["core"]
465
+ }
466
+ ]
467
+ }
468
+ ```
469
+
470
+ When `core` is bumped, `cli` gets a patch bump even if it had no direct commits.
471
+
472
+ ## Hooks
473
+
474
+ Run shell commands at lifecycle points during a release. Hooks can be set at the workspace level (applies to all packages) or per package:
475
+
476
+ ```toml
477
+ [workspace.hooks]
478
+ pre_bump = "echo 'about to bump'"
479
+ post_bump = "cargo check"
480
+ pre_commit = "npm run build"
481
+ pre_publish = "npm pack --dry-run"
482
+ post_publish = "notify-slack.sh"
483
+ on_failure = "abort" # or "continue"
484
+ ```
485
+
486
+ | Hook | When |
487
+ |------|------|
488
+ | `pre_bump` | After bump calculation, before writing version files |
489
+ | `post_bump` | After writing version files, before changelog generation |
490
+ | `pre_commit` | After changelog generation, before git commit |
491
+ | `pre_publish` | After commit and tag, before push |
492
+ | `post_publish` | After push and release creation |
493
+
494
+ If a hook exits non-zero and `on_failure` is `abort` (default), the release is cancelled. Set `on_failure` to `continue` to ignore hook failures.
495
+
496
+ Hook commands receive environment variables: `FERRFLOW_PACKAGE`, `FERRFLOW_OLD_VERSION`, `FERRFLOW_NEW_VERSION`, `FERRFLOW_BUMP_TYPE`, `FERRFLOW_TAG`, `FERRFLOW_PACKAGE_PATH`, `FERRFLOW_DRY_RUN`, `FERRFLOW_CHANNEL`, `FERRFLOW_IS_PRERELEASE`.
497
+
498
+ ## Conventional Commits
499
+
500
+ FerrFlow follows the [Conventional Commits](https://www.conventionalcommits.org/) spec.
501
+
502
+ | Prefix | Bump |
503
+ |--------|------|
504
+ | `fix:`, `perf:`, `refactor:` | patch |
505
+ | `feat:` | minor |
506
+ | `feat!:`, `BREAKING CHANGE` | major |
507
+ | `chore:`, `docs:`, `ci:` | none |
508
+
509
+ ## CI usage
510
+
511
+ **GitLab CI**
512
+
513
+ ```yaml
514
+ release:
515
+ image: ghcr.io/ferrlabs/ferrflow:latest
516
+ script:
517
+ - ferrflow release
518
+ rules:
519
+ - if: '$CI_COMMIT_BRANCH == "main"'
520
+ ```
521
+
522
+ **GitHub Actions**
523
+
524
+ ```yaml
525
+ - name: Release
526
+ run: ferrflow release
527
+ env:
528
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
529
+ ```
530
+
531
+ ### Using the hosted bot (ferrflow[bot])
532
+
533
+ Install the [FerrFlow GitHub App](https://github.com/apps/ferrflow) on your repo or org, then opt in with `bot: true`. Release commits, tags, and GitHub Releases are authored by `ferrflow[bot]` and downstream workflows triggered by those events run normally (unlike the default `GITHUB_TOKEN`, which suppresses them).
534
+
535
+ ```yaml
536
+ permissions:
537
+ id-token: write
538
+ contents: read
539
+
540
+ steps:
541
+ - uses: actions/checkout@v6
542
+ with:
543
+ fetch-depth: 0
544
+ - uses: FerrLabs/FerrFlow@v4
545
+ with:
546
+ bot: true
547
+ ```
548
+
549
+ That's it — no `setup-node`, no extra dependencies. FerrFlow's Rust binary handles the OIDC exchange directly, so minimal self-hosted runners work out of the box.
550
+
551
+ Three auth modes are supported: `bot: true` uses the hosted FerrFlow App (recommended); `token: <PAT>` uses a personal access token or your own GitHub App token (DIY); omitting both falls back to the workflow's `GITHUB_TOKEN` (simplest, but release events won't trigger downstream workflows).
552
+
553
+ ## License
554
+
555
+ [MPL-2.0](LICENSE)
556
+