@ferrflow/doc 7.17.0
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.
- package/docs-en/ci/github-actions.md +120 -0
- package/docs-en/ci/gitlab-ci.md +90 -0
- package/docs-en/ci/hosted-bot.md +82 -0
- package/docs-en/ci/pipeline-triggers.md +287 -0
- package/docs-en/configuration/config-file.md +1259 -0
- package/docs-en/configuration/formats.md +220 -0
- package/docs-en/configuration/monorepo.md +390 -0
- package/docs-en/installation.md +56 -0
- package/docs-en/introduction.md +56 -0
- package/docs-en/quickstart.md +66 -0
- package/docs-en/reference/api.md +106 -0
- package/docs-en/reference/cli.md +483 -0
- package/docs-en/reference/conventional-commits.md +103 -0
- package/docs-en/reference/errors.md +508 -0
- package/docs-en/verifying-releases.md +97 -0
- package/docs-fr/ci/github-actions.md +109 -0
- package/docs-fr/ci/gitlab-ci.md +77 -0
- package/docs-fr/ci/hosted-bot.md +82 -0
- package/docs-fr/ci/pipeline-triggers.md +238 -0
- package/docs-fr/configuration/config-file.md +839 -0
- package/docs-fr/configuration/formats.md +163 -0
- package/docs-fr/configuration/monorepo.md +357 -0
- package/docs-fr/installation.md +56 -0
- package/docs-fr/introduction.md +54 -0
- package/docs-fr/quickstart.md +63 -0
- package/docs-fr/reference/api.md +106 -0
- package/docs-fr/reference/cli.md +407 -0
- package/docs-fr/reference/conventional-commits.md +103 -0
- package/docs-fr/reference/errors.md +378 -0
- package/docs-fr/verifying-releases.md +97 -0
- package/docs-fr-v4/ci/github-actions.md +106 -0
- package/docs-fr-v4/ci/gitlab-ci.md +77 -0
- package/docs-fr-v4/ci/pipeline-triggers.md +214 -0
- package/docs-fr-v4/configuration/config-file.md +769 -0
- package/docs-fr-v4/configuration/formats.md +128 -0
- package/docs-fr-v4/configuration/monorepo.md +324 -0
- package/docs-fr-v4/installation.md +48 -0
- package/docs-fr-v4/introduction.md +54 -0
- package/docs-fr-v4/legal/telemetry.md +65 -0
- package/docs-fr-v4/quickstart.md +63 -0
- package/docs-fr-v4/reference/cli.md +130 -0
- package/docs-fr-v4/reference/conventional-commits.md +67 -0
- package/docs-fr-v4/reference/errors.md +372 -0
- package/docs-fr-v5/ci/github-actions.md +109 -0
- package/docs-fr-v5/ci/gitlab-ci.md +77 -0
- package/docs-fr-v5/ci/hosted-bot.md +82 -0
- package/docs-fr-v5/ci/pipeline-triggers.md +238 -0
- package/docs-fr-v5/configuration/config-file.md +812 -0
- package/docs-fr-v5/configuration/formats.md +150 -0
- package/docs-fr-v5/configuration/monorepo.md +357 -0
- package/docs-fr-v5/installation.md +56 -0
- package/docs-fr-v5/introduction.md +54 -0
- package/docs-fr-v5/legal/telemetry.md +26 -0
- package/docs-fr-v5/quickstart.md +63 -0
- package/docs-fr-v5/reference/api.md +106 -0
- package/docs-fr-v5/reference/cli.md +356 -0
- package/docs-fr-v5/reference/conventional-commits.md +88 -0
- package/docs-fr-v5/reference/errors.md +378 -0
- package/docs-fr-v5/verifying-releases.md +97 -0
- package/docs-fr-v6/ci/github-actions.md +109 -0
- package/docs-fr-v6/ci/gitlab-ci.md +77 -0
- package/docs-fr-v6/ci/hosted-bot.md +82 -0
- package/docs-fr-v6/ci/pipeline-triggers.md +238 -0
- package/docs-fr-v6/configuration/config-file.md +813 -0
- package/docs-fr-v6/configuration/formats.md +150 -0
- package/docs-fr-v6/configuration/monorepo.md +357 -0
- package/docs-fr-v6/installation.md +56 -0
- package/docs-fr-v6/introduction.md +54 -0
- package/docs-fr-v6/quickstart.md +63 -0
- package/docs-fr-v6/reference/api.md +106 -0
- package/docs-fr-v6/reference/cli.md +356 -0
- package/docs-fr-v6/reference/conventional-commits.md +88 -0
- package/docs-fr-v6/reference/errors.md +378 -0
- package/docs-fr-v6/verifying-releases.md +97 -0
- package/docs-v0/ci/github-actions.md +77 -0
- package/docs-v0/ci/gitlab-ci.md +59 -0
- package/docs-v0/configuration/config-file.md +97 -0
- package/docs-v0/configuration/formats.md +86 -0
- package/docs-v0/configuration/monorepo.md +59 -0
- package/docs-v0/installation.md +48 -0
- package/docs-v0/introduction.md +34 -0
- package/docs-v0/legal/telemetry.md +63 -0
- package/docs-v0/quickstart.md +58 -0
- package/docs-v0/reference/cli.md +95 -0
- package/docs-v0/reference/conventional-commits.md +68 -0
- package/docs-v1/ci/github-actions.md +76 -0
- package/docs-v1/ci/gitlab-ci.md +58 -0
- package/docs-v1/configuration/config-file.md +515 -0
- package/docs-v1/configuration/formats.md +115 -0
- package/docs-v1/configuration/monorepo.md +246 -0
- package/docs-v1/installation.md +48 -0
- package/docs-v1/introduction.md +39 -0
- package/docs-v1/legal/telemetry.md +63 -0
- package/docs-v1/quickstart.md +62 -0
- package/docs-v1/reference/cli.md +128 -0
- package/docs-v1/reference/conventional-commits.md +67 -0
- package/docs-v2/ci/github-actions.md +117 -0
- package/docs-v2/ci/gitlab-ci.md +90 -0
- package/docs-v2/ci/pipeline-triggers.md +263 -0
- package/docs-v2/configuration/config-file.md +806 -0
- package/docs-v2/configuration/formats.md +98 -0
- package/docs-v2/configuration/monorepo.md +324 -0
- package/docs-v2/installation.md +48 -0
- package/docs-v2/introduction.md +40 -0
- package/docs-v2/legal/telemetry.md +66 -0
- package/docs-v2/quickstart.md +63 -0
- package/docs-v2/reference/cli.md +130 -0
- package/docs-v2/reference/conventional-commits.md +67 -0
- package/docs-v2/reference/errors.md +500 -0
- package/docs-v2/self-hosting.md +101 -0
- package/docs-v3/ci/github-actions.md +117 -0
- package/docs-v3/ci/gitlab-ci.md +90 -0
- package/docs-v3/ci/pipeline-triggers.md +263 -0
- package/docs-v3/configuration/config-file.md +806 -0
- package/docs-v3/configuration/formats.md +99 -0
- package/docs-v3/configuration/monorepo.md +324 -0
- package/docs-v3/installation.md +48 -0
- package/docs-v3/introduction.md +40 -0
- package/docs-v3/legal/telemetry.md +66 -0
- package/docs-v3/quickstart.md +66 -0
- package/docs-v3/reference/cli.md +161 -0
- package/docs-v3/reference/conventional-commits.md +67 -0
- package/docs-v3/reference/errors.md +502 -0
- package/docs-v3/self-hosting.md +137 -0
- package/docs-v4/ci/github-actions.md +117 -0
- package/docs-v4/ci/gitlab-ci.md +90 -0
- package/docs-v4/ci/pipeline-triggers.md +263 -0
- package/docs-v4/configuration/config-file.md +850 -0
- package/docs-v4/configuration/formats.md +182 -0
- package/docs-v4/configuration/monorepo.md +324 -0
- package/docs-v4/installation.md +48 -0
- package/docs-v4/introduction.md +56 -0
- package/docs-v4/legal/telemetry.md +65 -0
- package/docs-v4/quickstart.md +66 -0
- package/docs-v4/reference/cli.md +161 -0
- package/docs-v4/reference/conventional-commits.md +67 -0
- package/docs-v4/reference/errors.md +502 -0
- package/docs-v4/self-hosting.md +137 -0
- package/docs-v5/ci/github-actions.md +120 -0
- package/docs-v5/ci/gitlab-ci.md +90 -0
- package/docs-v5/ci/hosted-bot.md +82 -0
- package/docs-v5/ci/pipeline-triggers.md +287 -0
- package/docs-v5/configuration/config-file.md +1133 -0
- package/docs-v5/configuration/formats.md +206 -0
- package/docs-v5/configuration/monorepo.md +390 -0
- package/docs-v5/installation.md +56 -0
- package/docs-v5/introduction.md +56 -0
- package/docs-v5/legal/telemetry.md +26 -0
- package/docs-v5/quickstart.md +66 -0
- package/docs-v5/reference/api.md +106 -0
- package/docs-v5/reference/cli.md +431 -0
- package/docs-v5/reference/conventional-commits.md +88 -0
- package/docs-v5/reference/errors.md +508 -0
- package/docs-v5/verifying-releases.md +97 -0
- package/docs-v6/ci/github-actions.md +120 -0
- package/docs-v6/ci/gitlab-ci.md +90 -0
- package/docs-v6/ci/hosted-bot.md +82 -0
- package/docs-v6/ci/pipeline-triggers.md +287 -0
- package/docs-v6/configuration/config-file.md +1134 -0
- package/docs-v6/configuration/formats.md +206 -0
- package/docs-v6/configuration/monorepo.md +390 -0
- package/docs-v6/installation.md +56 -0
- package/docs-v6/introduction.md +56 -0
- package/docs-v6/quickstart.md +66 -0
- package/docs-v6/reference/api.md +106 -0
- package/docs-v6/reference/cli.md +431 -0
- package/docs-v6/reference/conventional-commits.md +88 -0
- package/docs-v6/reference/errors.md +508 -0
- package/docs-v6/verifying-releases.md +97 -0
- package/package.json +17 -0
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Configuration
|
|
3
|
+
description: Complete reference for the FerrFlow configuration file.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
FerrFlow supports six config file formats, searched in this order:
|
|
7
|
+
|
|
8
|
+
1. `ferrflow.json`
|
|
9
|
+
2. `ferrflow.json5`
|
|
10
|
+
3. `ferrflow.toml`
|
|
11
|
+
4. `ferrflow.ts` (requires `tsx`)
|
|
12
|
+
5. `ferrflow.js` (requires `node`)
|
|
13
|
+
6. `.ferrflow` (JSON)
|
|
14
|
+
|
|
15
|
+
If no config file is found, FerrFlow auto-detects common version files in the current directory.
|
|
16
|
+
|
|
17
|
+
<aside class="ferr-aside ferr-aside--tip"><div class="ferr-aside__body"><p>Add <code>"$schema": "https://ferrflow.com/schema/ferrflow.json"</code> to your JSON config for editor autocompletion and validation.</p>
|
|
18
|
+
</div></aside>
|
|
19
|
+
|
|
20
|
+
## Config formats
|
|
21
|
+
|
|
22
|
+
<div class="ferr-tabs">
|
|
23
|
+
<div class="ferr-tab" data-label="TypeScript"><p class="ferr-tab__label">TypeScript</p><div class="ferr-tab__body"><pre><code class="language-ts">export default {
|
|
24
|
+
workspace: {
|
|
25
|
+
tagTemplate: "v{version}",
|
|
26
|
+
},
|
|
27
|
+
package: [
|
|
28
|
+
{
|
|
29
|
+
name: "my-app",
|
|
30
|
+
path: ".",
|
|
31
|
+
changelog: "CHANGELOG.md",
|
|
32
|
+
versionedFiles: [
|
|
33
|
+
{ path: "Cargo.toml", format: "toml" },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
</code></pre>
|
|
39
|
+
</div></div>
|
|
40
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
41
|
+
"$schema": "https://ferrflow.com/schema/ferrflow.json",
|
|
42
|
+
"workspace": {
|
|
43
|
+
"tagTemplate": "v{version}"
|
|
44
|
+
},
|
|
45
|
+
"package": [
|
|
46
|
+
{
|
|
47
|
+
"name": "my-app",
|
|
48
|
+
"path": ".",
|
|
49
|
+
"changelog": "CHANGELOG.md",
|
|
50
|
+
"versionedFiles": [
|
|
51
|
+
{ "path": "Cargo.toml", "format": "toml" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
</code></pre>
|
|
57
|
+
</div></div>
|
|
58
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
59
|
+
tag_template = "v{version}"
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "my-app"
|
|
63
|
+
path = "."
|
|
64
|
+
changelog = "CHANGELOG.md"
|
|
65
|
+
|
|
66
|
+
[[package.versioned_files]]
|
|
67
|
+
path = "Cargo.toml"
|
|
68
|
+
format = "toml"
|
|
69
|
+
</code></pre>
|
|
70
|
+
|
|
71
|
+
</div></div>
|
|
72
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
73
|
+
$schema: "https://ferrflow.com/schema/ferrflow.json",
|
|
74
|
+
workspace: {
|
|
75
|
+
tagTemplate: "v{version}",
|
|
76
|
+
},
|
|
77
|
+
package: [
|
|
78
|
+
{
|
|
79
|
+
name: "my-app",
|
|
80
|
+
path: ".",
|
|
81
|
+
changelog: "CHANGELOG.md",
|
|
82
|
+
versionedFiles: [
|
|
83
|
+
{ path: "Cargo.toml", format: "toml" },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
</code></pre>
|
|
89
|
+
</div></div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<aside class="ferr-aside ferr-aside--note"><div class="ferr-aside__body"><p>JSON, JSON5, and TypeScript/JavaScript configs use <strong>camelCase</strong> keys (<code>tagTemplate</code>, <code>versionedFiles</code>).
|
|
93
|
+
TOML configs use <strong>snake_case</strong> keys (<code>tag_template</code>, <code>versioned_files</code>).
|
|
94
|
+
All forms are equivalent.</p>
|
|
95
|
+
</div></aside>
|
|
96
|
+
|
|
97
|
+
### TypeScript and JavaScript configs
|
|
98
|
+
|
|
99
|
+
TypeScript (`.ts`) and JavaScript (`.js`) config files use a default ESM export. The export can be a plain object or an async function.
|
|
100
|
+
|
|
101
|
+
<aside class="ferr-aside ferr-aside--warning"><div class="ferr-aside__body"><p>TypeScript configs require <code>tsx</code> (<code>npm install -g tsx</code>). JavaScript configs require <code>node</code> (v18+).</p>
|
|
102
|
+
</div></aside>
|
|
103
|
+
|
|
104
|
+
The main advantage of TS/JS configs is **function hooks**. Instead of shell command strings, you can write hooks as native functions with full access to the hook context:
|
|
105
|
+
|
|
106
|
+
```ts title="ferrflow.ts"
|
|
107
|
+
export default {
|
|
108
|
+
workspace: {
|
|
109
|
+
tagTemplate: 'v{version}',
|
|
110
|
+
hooks: {
|
|
111
|
+
postPublish: async (ctx) => {
|
|
112
|
+
await fetch('https://hooks.slack.com/services/...', {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
text: `Released ${ctx.package}@${ctx.newVersion}`,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
package: [
|
|
122
|
+
{
|
|
123
|
+
name: 'my-app',
|
|
124
|
+
path: '.',
|
|
125
|
+
versionedFiles: [{ path: 'package.json', format: 'json' }],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Hook context object
|
|
132
|
+
|
|
133
|
+
Function hooks receive a context object with these fields:
|
|
134
|
+
|
|
135
|
+
| Field | Type | Description |
|
|
136
|
+
| -------------- | -------------- | ----------------------------------------------------------------------------- |
|
|
137
|
+
| `package` | string | Package name |
|
|
138
|
+
| `oldVersion` | string | Version before bump (empty on first release) |
|
|
139
|
+
| `newVersion` | string | Version after bump |
|
|
140
|
+
| `bumpType` | string | `major`, `minor`, `patch`, or `none` |
|
|
141
|
+
| `tag` | string | Full git tag name |
|
|
142
|
+
| `dryRun` | boolean | Whether `--dry-run` is set |
|
|
143
|
+
| `packagePath` | string | Absolute path to package root |
|
|
144
|
+
| `channel` | string or null | Pre-release channel name |
|
|
145
|
+
| `isPrerelease` | boolean | Whether this is a pre-release |
|
|
146
|
+
| `monorepo` | boolean | Whether this is a monorepo release |
|
|
147
|
+
| `changelog` | string | Rendered changelog section for this bump (markdown) |
|
|
148
|
+
| `commits` | array | `{ hash, message, type?, scope?, breaking }` per commit in the bump |
|
|
149
|
+
| `bumpedFiles` | array | `{ path, format }` for each file the release modified |
|
|
150
|
+
| `allPackages` | array | `{ name, version, bump }` for every package released in this batch |
|
|
151
|
+
| `releaseUrl` | string or null | URL of the created forge release — `postPublish` hooks only, `null` otherwise |
|
|
152
|
+
|
|
153
|
+
`commits`, `bumpedFiles` and `allPackages` arrive as real arrays (parsed from JSON), so you can iterate them directly:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
export default {
|
|
157
|
+
workspace: {
|
|
158
|
+
hooks: {
|
|
159
|
+
postBump(ctx) {
|
|
160
|
+
for (const c of ctx.commits) {
|
|
161
|
+
if (c.breaking) console.log(`breaking: ${c.message}`);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Shell string hooks and function hooks can be mixed in the same config. Shell strings still work in TS/JS configs.
|
|
170
|
+
|
|
171
|
+
## `workspace`
|
|
172
|
+
|
|
173
|
+
Global settings that apply to all packages.
|
|
174
|
+
|
|
175
|
+
| Field | Type | Default | Description |
|
|
176
|
+
| ----------------------- | ------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `remote` | string | `"origin"` | Git remote to push to |
|
|
178
|
+
| `branch` | string | auto-detected | Branch to push to (detected from remote HEAD) |
|
|
179
|
+
| `tagTemplate` | string | `"v{version}"` or `"{name}@v{version}"` | Tag naming pattern. Uses `{version}` and `{name}` placeholders. Defaults to `v{version}` for single-package repos and `{name}@v{version}` for monorepos. |
|
|
180
|
+
| `versioning` | string | `"semver"` | Default versioning strategy for all packages |
|
|
181
|
+
| `releaseCommitMode` | string | `"commit"` | How to handle the release commit: `"commit"`, `"pr"`, or `"none"` |
|
|
182
|
+
| `releaseCommitScope` | string | `"grouped"` | In a monorepo where several packages are bumped at once, whether to create a single `"grouped"` commit or one commit `"per-package"`. Only matters when multiple packages bump. |
|
|
183
|
+
| `forge` | string | `"auto"` | Git forge override: `"auto"` detects from the remote URL, and for an unrecognised host it probes the API over HTTPS to auto-detect a self-hosted **GitLab**, **GitHub Enterprise**, or **Gitea / Forgejo** instance (cached, ~2s, best-effort). Set `"github"`, `"gitlab"`, `"gitea"` (Gitea / Forgejo / Codeberg), or `"bitbucket"` (Bitbucket Cloud) to force a forge — needed only when the host isn't reachable over HTTPS or you want to skip probing. Gitea auth uses `GITEA_TOKEN` / `FORGEJO_TOKEN`; Bitbucket uses `BITBUCKET_TOKEN`. All cover release creation — on Bitbucket, which has no release object, the release is the annotated tag FerrFlow pushes. PR mode is GitHub/GitLab only. |
|
|
184
|
+
| `skipCi` | boolean | depends on mode | Add `[skip ci]` to release commits. Defaults to `true` when mode is `"commit"`, `false` otherwise. |
|
|
185
|
+
| `commitSkipMarkers` | array | `["[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"]` | Markers that cause FerrFlow to skip a commit when computing the next version. Matched case-insensitively, subject line only. |
|
|
186
|
+
| `autoMergeReleases` | boolean | `true` | Enable auto-merge on release PRs (only applies when mode is `"pr"`) |
|
|
187
|
+
| `recoverMissedReleases` | boolean | `false` | Compare versioned files against the last tag instead of just the last commit, recovering releases missed earlier in a monorepo. |
|
|
188
|
+
| `updateLockfiles` | boolean | `false` | After a bump, refresh the sibling lockfile (`Cargo.lock`, `package-lock.json` / `pnpm-lock.yaml` / `yarn.lock`, `poetry.lock` / `uv.lock`, `Gemfile.lock`, `mix.lock`) via the package manager's offline / lockfile-only mode and stage it in the same release commit. A missing package manager or an unresolvable offline update is warned about, never fatal. Set per-package `updateLockfiles: false` to opt a single package out. |
|
|
189
|
+
| `updateDependents` | boolean | `false` | After a bump, rewrite the version constraint every dependent declares for the bumped package and stage the manifest in the same release commit. Only `json` (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`) and `toml` (`dependencies`, `dev-dependencies`, `build-dependencies`) manifests are rewritten, and only plain operator + version constraints — the operator is preserved (`^1.2.3` → `^2.0.0`). A `workspace:*`, `file:`/`git:` spec, `1.x` or a multi-part range is left untouched. |
|
|
190
|
+
| `floatingTags` | array | `[]` | Floating tag levels to create on each release: `"major"`, `"minor"`. For example, `["major"]` creates a `v1` tag that always points to the latest `v1.x.y` release. |
|
|
191
|
+
| `orphanedTagStrategy` | string | `"warn"` | How to handle tags pointing to orphaned commits after rebase + force-push: `"warn"`, `"treeHash"`, or `"message"` |
|
|
192
|
+
| `branches` | array | `[]` | Map branches to pre-release channels (see [Pre-release channels](#pre-release-channels)). |
|
|
193
|
+
| `linked` | array | `[]` | Groups of packages that share a version line when co-released. When any member has a releasable commit, all members bump to the same (highest) version (see [Linked and fixed version groups](/docs/configuration/monorepo#linked-and-fixed-version-groups)). |
|
|
194
|
+
| `fixed` | array | `[]` | Groups of packages locked to an identical version forever. Behaves like `linked`; `ferrflow validate` warns when a fixed group's versions have drifted apart. |
|
|
195
|
+
| `anonymous_telemetry` | boolean | `true` | Deprecated and ignored — telemetry was removed in v5.33 ([details](/v5/docs/legal/telemetry)). The key (and its `telemetry` alias) stays accepted so existing configs remain valid. |
|
|
196
|
+
|
|
197
|
+
### Tag template
|
|
198
|
+
|
|
199
|
+
The `tagTemplate` field controls how git tags are named. Available placeholders:
|
|
200
|
+
|
|
201
|
+
| Placeholder | Description |
|
|
202
|
+
| ----------- | --------------------------------- |
|
|
203
|
+
| `{version}` | The version number (e.g. `1.2.3`) |
|
|
204
|
+
| `{name}` | The package name |
|
|
205
|
+
|
|
206
|
+
<div class="ferr-tabs">
|
|
207
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
208
|
+
"workspace": {
|
|
209
|
+
"tagTemplate": "v{version}"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
</code></pre>
|
|
213
|
+
</div></div>
|
|
214
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
215
|
+
tag_template = "v{version}"
|
|
216
|
+
</code></pre>
|
|
217
|
+
</div></div>
|
|
218
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
219
|
+
workspace: {
|
|
220
|
+
tagTemplate: "v{version}",
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
</code></pre>
|
|
224
|
+
</div></div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
For monorepos, use `{name}` to namespace tags per package:
|
|
228
|
+
|
|
229
|
+
<div class="ferr-tabs">
|
|
230
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
231
|
+
"workspace": {
|
|
232
|
+
"tagTemplate": "{name}@v{version}"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
</code></pre>
|
|
236
|
+
</div></div>
|
|
237
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
238
|
+
tag_template = "{name}@v{version}"
|
|
239
|
+
</code></pre>
|
|
240
|
+
</div></div>
|
|
241
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
242
|
+
workspace: {
|
|
243
|
+
tagTemplate: "{name}@v{version}",
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
</code></pre>
|
|
247
|
+
</div></div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
### Floating tags
|
|
251
|
+
|
|
252
|
+
Floating tags are version aliases that always point to the latest release matching a given level. This is useful for GitHub Actions or Docker images where users reference `v1` instead of `v1.2.3`.
|
|
253
|
+
|
|
254
|
+
<div class="ferr-tabs">
|
|
255
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
256
|
+
"workspace": {
|
|
257
|
+
"floatingTags": ["major"]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
</code></pre>
|
|
261
|
+
</div></div>
|
|
262
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
263
|
+
floating_tags = ["major"]
|
|
264
|
+
</code></pre>
|
|
265
|
+
</div></div>
|
|
266
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
267
|
+
workspace: {
|
|
268
|
+
floatingTags: ["major"],
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
</code></pre>
|
|
272
|
+
</div></div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
When releasing `v1.2.3`, FerrFlow creates or moves a `v1` tag pointing to the same commit. With `["major", "minor"]`, both `v1` and `v1.2` tags are maintained.
|
|
276
|
+
|
|
277
|
+
If a floating tag would move backward (e.g. releasing a `v1.1.0` hotfix when `v1.2.0` already exists), FerrFlow blocks the release. Use `--force` to override this check.
|
|
278
|
+
|
|
279
|
+
### Orphaned tag strategy
|
|
280
|
+
|
|
281
|
+
When a branch is rebased and force-pushed, existing tags may point to commits that are no longer part of the branch history. By default, FerrFlow warns about these orphaned tags and skips them. You can configure automatic recovery instead.
|
|
282
|
+
|
|
283
|
+
| Strategy | Behavior |
|
|
284
|
+
| ------------ | --------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `"warn"` | Log a warning identifying the orphaned tag and skip it (default) |
|
|
286
|
+
| `"treeHash"` | Attempt to find a commit on the current branch with the same file tree as the orphaned tag's commit |
|
|
287
|
+
| `"message"` | Attempt to find a commit on the current branch with the same commit message |
|
|
288
|
+
|
|
289
|
+
<div class="ferr-tabs">
|
|
290
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
291
|
+
"workspace": {
|
|
292
|
+
"orphanedTagStrategy": "treeHash"
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
</code></pre>
|
|
296
|
+
</div></div>
|
|
297
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
298
|
+
orphaned_tag_strategy = "treeHash"
|
|
299
|
+
</code></pre>
|
|
300
|
+
</div></div>
|
|
301
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
302
|
+
workspace: {
|
|
303
|
+
orphanedTagStrategy: "treeHash",
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
</code></pre>
|
|
307
|
+
</div></div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
`"treeHash"` is the safest recovery option — it matches commits that have identical file contents, which is typical after a rebase that doesn't modify files. Use `"message"` when rebases also change the tree (e.g. squashing commits) but preserve the original message.
|
|
311
|
+
|
|
312
|
+
If recovery fails (no matching commit found within the last 1000 commits), FerrFlow falls back to warning and skipping the tag. In that case, re-tag manually:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
git tag -f api@v1.2.0 <correct-commit>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Release commit mode
|
|
319
|
+
|
|
320
|
+
Controls how FerrFlow handles the commit that updates version files and changelogs.
|
|
321
|
+
|
|
322
|
+
| Mode | Behavior |
|
|
323
|
+
| ---------- | ------------------------------------------------------------------------- |
|
|
324
|
+
| `"commit"` | Commits directly to the current branch and pushes (default) |
|
|
325
|
+
| `"pr"` | Opens a persistent release pull request and updates it on each new commit |
|
|
326
|
+
| `"none"` | Only creates tags and releases, does not commit file changes |
|
|
327
|
+
|
|
328
|
+
<div class="ferr-tabs">
|
|
329
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
330
|
+
"workspace": {
|
|
331
|
+
"releaseCommitMode": "pr",
|
|
332
|
+
"autoMergeReleases": true
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
</code></pre>
|
|
336
|
+
</div></div>
|
|
337
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
338
|
+
release_commit_mode = "pr"
|
|
339
|
+
auto_merge_releases = true
|
|
340
|
+
</code></pre>
|
|
341
|
+
</div></div>
|
|
342
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
343
|
+
workspace: {
|
|
344
|
+
releaseCommitMode: "pr",
|
|
345
|
+
autoMergeReleases: true,
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
</code></pre>
|
|
349
|
+
</div></div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
In `"pr"` mode FerrFlow keeps **one long-lived release PR per target branch**. It maintains a single release branch — `ferrflow/release-<target-branch>` — and on every new commit it recomputes the version and changelog and force-pushes that same branch, so the open PR updates in place instead of a new PR opening per version.
|
|
353
|
+
|
|
354
|
+
`autoMergeReleases` (default `true`) enables auto-merge on that PR; it re-applies on each update and is a no-op when disabled (the PR just waits for a human). PR mode is supported on GitHub and GitLab.
|
|
355
|
+
|
|
356
|
+
FerrFlow won't clobber work you push onto the release branch: if the branch carries a commit it didn't author — anything that isn't a `chore(release):` commit, such as a review fix you pushed — it warns and leaves the branch and PR untouched for that run.
|
|
357
|
+
|
|
358
|
+
### Versioning strategies
|
|
359
|
+
|
|
360
|
+
FerrFlow supports multiple versioning strategies, configurable at workspace or package level.
|
|
361
|
+
|
|
362
|
+
| Strategy | Format | Example progression |
|
|
363
|
+
| -------------- | ------------------- | --------------------------------------- |
|
|
364
|
+
| `semver` | `MAJOR.MINOR.PATCH` | `1.2.3` → `1.3.0` → `2.0.0` |
|
|
365
|
+
| `calver` | `YYYY.MM.PATCH` | `2026.03.0` → `2026.03.1` → `2026.04.0` |
|
|
366
|
+
| `calver-short` | `YY.MM.PATCH` | `26.03.0` → `26.03.1` |
|
|
367
|
+
| `calver-seq` | `YYYY.MM.SEQ` | `2026.03.1` → `2026.03.2` |
|
|
368
|
+
| `sequential` | `N` | `1` → `2` → `3` |
|
|
369
|
+
| `zerover` | `0.MINOR.PATCH` | `0.1.0` → `0.2.0` (never reaches 1.0) |
|
|
370
|
+
|
|
371
|
+
<div class="ferr-tabs">
|
|
372
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
373
|
+
"workspace": {
|
|
374
|
+
"versioning": "calver"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
</code></pre>
|
|
378
|
+
</div></div>
|
|
379
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
380
|
+
versioning = "calver"
|
|
381
|
+
</code></pre>
|
|
382
|
+
</div></div>
|
|
383
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
384
|
+
workspace: {
|
|
385
|
+
versioning: "calver",
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
</code></pre>
|
|
389
|
+
</div></div>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
### Pre-release channels
|
|
393
|
+
|
|
394
|
+
The `branches` array maps branch names (or glob patterns) to pre-release channels. When FerrFlow runs on a branch matching an entry, it releases on that channel — e.g. `1.4.0-beta.1` instead of `1.4.0`. The same mapping is what `--channel` on `ferrflow check` and `ferrflow release` overrides ad-hoc.
|
|
395
|
+
|
|
396
|
+
Each entry has:
|
|
397
|
+
|
|
398
|
+
| Field | Type | Description |
|
|
399
|
+
| ---------------------- | ----------------- | ------------------------------------------------------------------- |
|
|
400
|
+
| `name` | string | Branch name or glob pattern (e.g. `"main"`, `"release/*"`) |
|
|
401
|
+
| `channel` | string or `false` | Channel name (`"beta"`, `"rc"`, …), or `false` for a stable release |
|
|
402
|
+
| `prereleaseIdentifier` | string | Strategy for the identifier appended after the channel name |
|
|
403
|
+
|
|
404
|
+
<div class="ferr-tabs">
|
|
405
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
406
|
+
"workspace": {
|
|
407
|
+
"branches": [
|
|
408
|
+
{ "name": "main", "channel": false },
|
|
409
|
+
{ "name": "next", "channel": "beta" }
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
</code></pre>
|
|
414
|
+
</div></div>
|
|
415
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[[workspace.branches]]
|
|
416
|
+
name = "main"
|
|
417
|
+
channel = false
|
|
418
|
+
|
|
419
|
+
[[workspace.branches]]
|
|
420
|
+
name = "next"
|
|
421
|
+
channel = "beta"
|
|
422
|
+
</code></pre>
|
|
423
|
+
|
|
424
|
+
</div></div>
|
|
425
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
426
|
+
workspace: {
|
|
427
|
+
branches: [
|
|
428
|
+
{ name: "main", channel: false },
|
|
429
|
+
{ name: "next", channel: "beta" },
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
</code></pre>
|
|
434
|
+
</div></div>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
## `package`
|
|
438
|
+
|
|
439
|
+
Defines a package to version. You can have one or many.
|
|
440
|
+
|
|
441
|
+
| Field | Required | Default | Description |
|
|
442
|
+
| -------------- | -------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
443
|
+
| `name` | yes | — | Package identifier, used in git tag prefix |
|
|
444
|
+
| `path` | yes | — | Relative path to the package directory |
|
|
445
|
+
| `changelog` | no | `{path}/CHANGELOG.md` | Path to the changelog file |
|
|
446
|
+
| `sharedPaths` | no | `[]` | Paths that trigger this package when changed |
|
|
447
|
+
| `dependsOn` | no | `[]` | Packages this package depends on. When a dependency is bumped, this package is bumped too — with the same bump type by default. Each entry is a package name, or `{ "name": "core", "propagate": "patch" }` to choose the policy (`same`, `major-on-major`, `patch`, `none`). |
|
|
448
|
+
| `versioning` | no | inherited from workspace | Override versioning strategy for this package |
|
|
449
|
+
| `tagTemplate` | no | inherited from workspace | Override tag template for this package |
|
|
450
|
+
| `floatingTags` | no | inherited from workspace | Override floating tags for this package |
|
|
451
|
+
|
|
452
|
+
### `versionedFiles`
|
|
453
|
+
|
|
454
|
+
Files where the version number should be updated.
|
|
455
|
+
|
|
456
|
+
<div class="ferr-tabs">
|
|
457
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
458
|
+
"package": [
|
|
459
|
+
{
|
|
460
|
+
"name": "my-app",
|
|
461
|
+
"path": ".",
|
|
462
|
+
"versionedFiles": [
|
|
463
|
+
{ "path": "Cargo.toml", "format": "toml" },
|
|
464
|
+
{ "path": "npm/package.json", "format": "json" }
|
|
465
|
+
]
|
|
466
|
+
}
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
</code></pre>
|
|
470
|
+
</div></div>
|
|
471
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[[package]]
|
|
472
|
+
name = "my-app"
|
|
473
|
+
path = "."
|
|
474
|
+
|
|
475
|
+
[[package.versioned_files]]
|
|
476
|
+
path = "Cargo.toml"
|
|
477
|
+
format = "toml"
|
|
478
|
+
|
|
479
|
+
[[package.versioned_files]]
|
|
480
|
+
path = "npm/package.json"
|
|
481
|
+
format = "json"
|
|
482
|
+
</code></pre>
|
|
483
|
+
|
|
484
|
+
</div></div>
|
|
485
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
486
|
+
package: [
|
|
487
|
+
{
|
|
488
|
+
name: "my-app",
|
|
489
|
+
path: ".",
|
|
490
|
+
versionedFiles: [
|
|
491
|
+
{ path: "Cargo.toml", format: "toml" },
|
|
492
|
+
{ path: "npm/package.json", format: "json" },
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
}
|
|
497
|
+
</code></pre>
|
|
498
|
+
</div></div>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
| `format` | File | Field updated |
|
|
502
|
+
| -------- | ---------------------------------- | ------------------------------------------------- |
|
|
503
|
+
| `toml` | `Cargo.toml`, `pyproject.toml` | `[package].version` or `[project].version` |
|
|
504
|
+
| `json` | `package.json` | `version` |
|
|
505
|
+
| `xml` | `pom.xml` | First `<version>` element |
|
|
506
|
+
| `gradle` | `build.gradle`, `build.gradle.kts` | `version = "..."` |
|
|
507
|
+
| `helm` | `Chart.yaml` | `version` and `appVersion` (when present) |
|
|
508
|
+
| `gomod` | `go.mod` | No file update — version comes from git tags only |
|
|
509
|
+
| `txt` | `VERSION`, `VERSION.txt` | Entire file content replaced |
|
|
510
|
+
|
|
511
|
+
### Tag-only packages
|
|
512
|
+
|
|
513
|
+
`versionedFiles` is optional. Omit it (or set it to `[]`) for packages whose version is communicated entirely through git tags and GitHub Releases — Go modules, Docker images, GitHub Actions, infrastructure repos.
|
|
514
|
+
|
|
515
|
+
<div class="ferr-tabs">
|
|
516
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
517
|
+
"package": [
|
|
518
|
+
{
|
|
519
|
+
"name": "my-action",
|
|
520
|
+
"path": ".",
|
|
521
|
+
"versionedFiles": []
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
</code></pre>
|
|
526
|
+
</div></div>
|
|
527
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[[package]]
|
|
528
|
+
name = "my-action"
|
|
529
|
+
path = "."
|
|
530
|
+
versioned_files = []
|
|
531
|
+
</code></pre>
|
|
532
|
+
</div></div>
|
|
533
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
534
|
+
package: [
|
|
535
|
+
{
|
|
536
|
+
name: "my-action",
|
|
537
|
+
path: ".",
|
|
538
|
+
versionedFiles: [],
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
}
|
|
542
|
+
</code></pre>
|
|
543
|
+
</div></div>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
FerrFlow reads the current version from the latest matching git tag, computes the next bump from conventional commits, then creates the tag, the GitHub Release, the changelog and any floating tags — without touching any source file. Hooks still run normally, so you can `docker build`, `docker push`, or `gh release upload` from `postPublish` against `FERRFLOW_NEW_VERSION`.
|
|
547
|
+
|
|
548
|
+
<aside class="ferr-aside ferr-aside--note"><div class="ferr-aside__body"><p>Before v5.1, packages without <code>versionedFiles</code> were silently skipped. If you depended on that behavior to exclude a package from a release, remove it from the config instead.</p>
|
|
549
|
+
</div></aside>
|
|
550
|
+
|
|
551
|
+
## `hooks`
|
|
552
|
+
|
|
553
|
+
Run shell commands at key points in the release lifecycle. Hooks can be defined at workspace level (defaults for all packages) or per package (overrides workspace hooks for that package).
|
|
554
|
+
|
|
555
|
+
### Lifecycle
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
calculate bump
|
|
559
|
+
↓
|
|
560
|
+
pre_bump ← validate state, check prerequisites
|
|
561
|
+
↓
|
|
562
|
+
write version files
|
|
563
|
+
↓
|
|
564
|
+
post_bump ← modify additional files based on new version
|
|
565
|
+
↓
|
|
566
|
+
generate changelog
|
|
567
|
+
↓
|
|
568
|
+
pre_commit ← review staged changes, run linters
|
|
569
|
+
↓
|
|
570
|
+
git commit
|
|
571
|
+
↓
|
|
572
|
+
post_commit ← react to the release commit
|
|
573
|
+
↓
|
|
574
|
+
pre_tag ← smoke-test the bumped tree before the tag lands
|
|
575
|
+
↓
|
|
576
|
+
git tag
|
|
577
|
+
↓
|
|
578
|
+
post_tag ← cargo publish before push (recoverable if it fails)
|
|
579
|
+
↓
|
|
580
|
+
pre_publish ← run tests against tagged commit, build artifacts
|
|
581
|
+
↓
|
|
582
|
+
git push + create release
|
|
583
|
+
↓
|
|
584
|
+
post_publish ← push Docker images, notify Slack, publish packages
|
|
585
|
+
|
|
586
|
+
pre_release ← (PR mode) after the release PR opens, before merge
|
|
587
|
+
on_success ← once, after the whole release completes cleanly
|
|
588
|
+
on_error ← once, when the release fails ($FERRFLOW_ERROR_CODE)
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Configuration
|
|
592
|
+
|
|
593
|
+
<div class="ferr-tabs">
|
|
594
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
595
|
+
"workspace": {
|
|
596
|
+
"hooks": {
|
|
597
|
+
"preBump": "cargo test",
|
|
598
|
+
"postBump": "node scripts/sync-deps.js",
|
|
599
|
+
"preCommit": "cargo fmt --check",
|
|
600
|
+
"prePublish": "cargo build --release",
|
|
601
|
+
"postPublish": "make docker-push && ./scripts/notify.sh",
|
|
602
|
+
"onFailure": "abort"
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
</code></pre>
|
|
607
|
+
</div></div>
|
|
608
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[hooks]
|
|
609
|
+
pre_bump = "cargo test"
|
|
610
|
+
post_bump = "node scripts/sync-deps.js"
|
|
611
|
+
pre_commit = "cargo fmt --check"
|
|
612
|
+
pre_publish = "cargo build --release"
|
|
613
|
+
post_publish = "make docker-push && ./scripts/notify.sh"
|
|
614
|
+
on_failure = "abort"
|
|
615
|
+
</code></pre>
|
|
616
|
+
</div></div>
|
|
617
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
618
|
+
workspace: {
|
|
619
|
+
hooks: {
|
|
620
|
+
preBump: "cargo test",
|
|
621
|
+
postBump: "node scripts/sync-deps.js",
|
|
622
|
+
preCommit: "cargo fmt --check",
|
|
623
|
+
prePublish: "cargo build --release",
|
|
624
|
+
postPublish: "make docker-push && ./scripts/notify.sh",
|
|
625
|
+
onFailure: "abort",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
}
|
|
629
|
+
</code></pre>
|
|
630
|
+
</div></div>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
| Field | Type | Default | Description |
|
|
634
|
+
| ------------- | ------ | --------- | --------------------------------------------------------------------------------------- |
|
|
635
|
+
| `preBump` | string | — | Run after bump calculation, before writing version files |
|
|
636
|
+
| `postBump` | string | — | Run after version files are written |
|
|
637
|
+
| `preCommit` | string | — | Run after changelog, before git commit |
|
|
638
|
+
| `postCommit` | string | — | Run after the release commit, before tagging |
|
|
639
|
+
| `preTag` | string | — | Run after the commit, immediately before `git tag` |
|
|
640
|
+
| `postTag` | string | — | Run after tags are created, before push |
|
|
641
|
+
| `prePublish` | string | — | Run after commit+tag, before push |
|
|
642
|
+
| `postPublish` | string | — | Run after push and release creation |
|
|
643
|
+
| `preRelease` | string | — | PR mode only: after the release PR opens, before merge (once per run) |
|
|
644
|
+
| `onSuccess` | string | — | Run once after the whole release completes cleanly |
|
|
645
|
+
| `onError` | string | — | Run once when the release fails; sets `FERRFLOW_ERROR_CODE` (once per run) |
|
|
646
|
+
| `onFailure` | string | `"abort"` | Strategy — `"abort"` cancels the release on hook failure, `"continue"` prints a warning |
|
|
647
|
+
|
|
648
|
+
### Environment variables
|
|
649
|
+
|
|
650
|
+
Every hook receives these variables:
|
|
651
|
+
|
|
652
|
+
| Variable | Description | Example |
|
|
653
|
+
| ---------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
654
|
+
| `FERRFLOW_PACKAGE` | Package name | `api` |
|
|
655
|
+
| `FERRFLOW_OLD_VERSION` | Version before bump (empty on first release) | `1.2.3` |
|
|
656
|
+
| `FERRFLOW_NEW_VERSION` | Version after bump | `1.3.0` |
|
|
657
|
+
| `FERRFLOW_BUMP_TYPE` | `major`, `minor`, `patch`, or `none` | `minor` |
|
|
658
|
+
| `FERRFLOW_TAG` | Full git tag name | `api@v1.3.0` |
|
|
659
|
+
| `FERRFLOW_DRY_RUN` | `true` if `--dry-run` is set | `false` |
|
|
660
|
+
| `FERRFLOW_PACKAGE_PATH` | Absolute path to package root | `/home/user/repo/packages/api` |
|
|
661
|
+
| `FERRFLOW_IS_PRERELEASE` | `true` on a pre-release channel | `false` |
|
|
662
|
+
| `FERRFLOW_MONOREPO` | `true` on a monorepo release | `false` |
|
|
663
|
+
| `FERRFLOW_CHANGELOG` | Rendered changelog section for this bump | `### Features\n- ...` |
|
|
664
|
+
| `FERRFLOW_COMMITS_JSON` | JSON array of `{ hash, message, type?, scope?, breaking }` | `[{"hash":"a1b2","message":"feat: x","type":"feat","breaking":false}]` |
|
|
665
|
+
| `FERRFLOW_BUMPED_FILES_JSON` | JSON array of `{ path, format }` the release modified | `[{"path":"package.json","format":"json"}]` |
|
|
666
|
+
| `FERRFLOW_ALL_PACKAGES_JSON` | JSON array of `{ name, version, bump }` released this batch | `[{"name":"api","version":"1.3.0","bump":"minor"}]` |
|
|
667
|
+
| `FERRFLOW_RELEASE_URL` | URL of the created forge release (`postPublish` only) | `https://github.com/acme/api/releases/tag/v1.3.0` |
|
|
668
|
+
| `FERRFLOW_ERROR_CODE` | Error code, set only for `onError` | `E2005` |
|
|
669
|
+
|
|
670
|
+
`FERRFLOW_COMMITS_JSON`, `FERRFLOW_BUMPED_FILES_JSON` and `FERRFLOW_ALL_PACKAGES_JSON` are JSON strings — pipe them through `jq` from shell hooks.
|
|
671
|
+
|
|
672
|
+
For the once-per-run hooks (`preRelease`, `onSuccess`, `onError`) the per-package variables are empty and `FERRFLOW_TAG` holds every released tag joined by commas.
|
|
673
|
+
|
|
674
|
+
`onFailure` is the failure **strategy** (`abort` / `continue`), not a command. The command that runs _when_ a release fails is `onError`, which receives the failing `FERRFLOW_ERROR_CODE`.
|
|
675
|
+
|
|
676
|
+
### Per-package hooks
|
|
677
|
+
|
|
678
|
+
Package-level hooks **replace** workspace-level hooks for that package (they are not merged).
|
|
679
|
+
|
|
680
|
+
<div class="ferr-tabs">
|
|
681
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
682
|
+
"workspace": {
|
|
683
|
+
"hooks": {
|
|
684
|
+
"preBump": "echo releasing $FERRFLOW_PACKAGE",
|
|
685
|
+
"postPublish": "make notify"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
"package": [
|
|
689
|
+
{
|
|
690
|
+
"name": "api",
|
|
691
|
+
"path": "packages/api",
|
|
692
|
+
"hooks": {
|
|
693
|
+
"preBump": "cargo test"
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
</code></pre>
|
|
699
|
+
</div></div>
|
|
700
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[hooks]
|
|
701
|
+
pre_bump = "echo releasing $FERRFLOW_PACKAGE"
|
|
702
|
+
post_publish = "make notify"
|
|
703
|
+
|
|
704
|
+
[[package]]
|
|
705
|
+
name = "api"
|
|
706
|
+
path = "packages/api"
|
|
707
|
+
|
|
708
|
+
[package.hooks]
|
|
709
|
+
pre_bump = "cargo test"
|
|
710
|
+
</code></pre>
|
|
711
|
+
|
|
712
|
+
</div></div>
|
|
713
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
714
|
+
workspace: {
|
|
715
|
+
hooks: {
|
|
716
|
+
preBump: "echo releasing $FERRFLOW_PACKAGE",
|
|
717
|
+
postPublish: "make notify",
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
package: [
|
|
721
|
+
{
|
|
722
|
+
name: "api",
|
|
723
|
+
path: "packages/api",
|
|
724
|
+
hooks: {
|
|
725
|
+
preBump: "cargo test",
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
}
|
|
730
|
+
</code></pre>
|
|
731
|
+
</div></div>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
734
|
+
In this example, the `api` package runs `cargo test` for `preBump` (overriding the workspace echo) but inherits the workspace `postPublish` hook.
|
|
735
|
+
|
|
736
|
+
### Behavior
|
|
737
|
+
|
|
738
|
+
- **`--dry-run`**: hooks are printed but not executed.
|
|
739
|
+
- **`--verbose`**: hook stdout/stderr is streamed live. Otherwise output is only shown on failure.
|
|
740
|
+
- Files modified by `postBump` or `preCommit` hooks are automatically included in the release commit.
|
|
741
|
+
|
|
742
|
+
## `publishers`
|
|
743
|
+
|
|
744
|
+
Declarative replacement for the shell-script-in-`postPublish`-hook pattern. Each entry says "after the GitHub Release is created, push this package to that target." Available since v5.4.
|
|
745
|
+
|
|
746
|
+
Six built-in kinds cover the common publishing targets:
|
|
747
|
+
|
|
748
|
+
| `kind` | What it does | Idempotent on |
|
|
749
|
+
| ---------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
|
|
750
|
+
| `cargo` | `cargo publish` to crates.io or a custom registry | "already uploaded" registry response |
|
|
751
|
+
| `npm` | `npm publish` to npmjs.org, GitHub Packages, or a custom registry | "cannot publish over the previously published versions" |
|
|
752
|
+
| `docker` | `docker buildx build --push` with multi-arch + optional Sigstore | `docker manifest inspect` on each requested tag |
|
|
753
|
+
| `helm` | `helm package` + `helm push` to an OCI registry | `helm show chart` on the new version |
|
|
754
|
+
| `github-release-asset` | `gh release upload --clobber` of a sidecar file | always re-uploads (clobber semantics) |
|
|
755
|
+
| `webhook` | Generic `POST` notifier with `{name}` / `{version}` / `{tag}` / `{env:NAME}` interpolation | none — webhooks are fire-and-forget |
|
|
756
|
+
|
|
757
|
+
All kinds honor `--dry-run` (print the plan, do nothing) and the crash-resume checkpoint (a re-run after a partial failure picks up where it left off).
|
|
758
|
+
|
|
759
|
+
### Registries
|
|
760
|
+
|
|
761
|
+
Token credentials are declared once at the workspace level. Each publisher references a registry by name; FerrFlow validates the token env var is exported _before_ invoking the underlying tool.
|
|
762
|
+
|
|
763
|
+
<div class="ferr-tabs">
|
|
764
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
765
|
+
"workspace": {
|
|
766
|
+
"registries": {
|
|
767
|
+
"kellnr": {
|
|
768
|
+
"url": "https://kellnr.example.com",
|
|
769
|
+
"tokenEnv": "CARGO_REGISTRIES_KELLNR_TOKEN"
|
|
770
|
+
},
|
|
771
|
+
"gh-packages": {
|
|
772
|
+
"url": "https://npm.pkg.github.com",
|
|
773
|
+
"tokenEnv": "NODE_AUTH_TOKEN"
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
</code></pre>
|
|
779
|
+
</div></div>
|
|
780
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace.registries.kellnr]
|
|
781
|
+
url = "https://kellnr.example.com"
|
|
782
|
+
token_env = "CARGO_REGISTRIES_KELLNR_TOKEN"
|
|
783
|
+
|
|
784
|
+
[workspace.registries.gh-packages]
|
|
785
|
+
url = "https://npm.pkg.github.com"
|
|
786
|
+
token_env = "NODE_AUTH_TOKEN"
|
|
787
|
+
</code></pre>
|
|
788
|
+
|
|
789
|
+
</div></div>
|
|
790
|
+
</div>
|
|
791
|
+
|
|
792
|
+
The token value itself never lives in the config file — only the env-var name does. This keeps `ferrflow.json` checked-in safely.
|
|
793
|
+
|
|
794
|
+
### Multiple registries per package
|
|
795
|
+
|
|
796
|
+
`publishers` is a list and every entry runs on its own, so to publish one package to several registries you add one entry per target — each with its own `registry` (and therefore its own credentials), each idempotency-checked against that registry.
|
|
797
|
+
|
|
798
|
+
```json title="ferrflow.json"
|
|
799
|
+
{
|
|
800
|
+
"package": [
|
|
801
|
+
{
|
|
802
|
+
"name": "mylib",
|
|
803
|
+
"path": "crates/mylib",
|
|
804
|
+
"versionedFiles": [{ "path": "Cargo.toml", "format": "toml" }],
|
|
805
|
+
"publishers": [{ "kind": "cargo" }, { "kind": "cargo", "registry": "kellnr" }]
|
|
806
|
+
}
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
This publishes `mylib` to crates.io (public, default token) **and** the private `kellnr` registry (its own `tokenEnv`). The same fan-out works for every kind — `npm` to npmjs + GitHub Packages, `helm` to two OCI registries, `docker` to two `image` targets (one `docker login` per host).
|
|
812
|
+
|
|
813
|
+
### Cargo publisher
|
|
814
|
+
|
|
815
|
+
```json title="ferrflow.json"
|
|
816
|
+
{
|
|
817
|
+
"package": [
|
|
818
|
+
{
|
|
819
|
+
"name": "ferrlabs-auth",
|
|
820
|
+
"path": "crates/auth",
|
|
821
|
+
"versionedFiles": [{ "path": "Cargo.toml", "format": "toml" }],
|
|
822
|
+
"publishers": [{ "kind": "cargo", "registry": "kellnr" }]
|
|
823
|
+
}
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
Omit `registry` to publish to crates.io. `allowDirty: true` adds `--allow-dirty` (mirrors `cargo publish`'s own flag). `noVerify: true` adds `--no-verify` — set it for multi-crate batch releases so inter-dependent crates don't fail on registry-index propagation timing.
|
|
829
|
+
|
|
830
|
+
### npm publisher
|
|
831
|
+
|
|
832
|
+
```json title="ferrflow.json"
|
|
833
|
+
{
|
|
834
|
+
"package": [
|
|
835
|
+
{
|
|
836
|
+
"name": "@ferrlabs/ui-react",
|
|
837
|
+
"path": "packages/react",
|
|
838
|
+
"publishers": [
|
|
839
|
+
{ "kind": "npm", "registry": "gh-packages", "tag": "next", "access": "public" }
|
|
840
|
+
]
|
|
841
|
+
}
|
|
842
|
+
]
|
|
843
|
+
}
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
Omit `registry` to publish to npmjs.org. `tag` defaults to `"latest"`. A scoped `.npmrc` is written into the package dir for the duration of the publish and removed on exit — your project's `.npmrc` is never modified.
|
|
847
|
+
|
|
848
|
+
### Docker publisher
|
|
849
|
+
|
|
850
|
+
```json title="ferrflow.json"
|
|
851
|
+
{
|
|
852
|
+
"package": [
|
|
853
|
+
{
|
|
854
|
+
"name": "ferrlabs-auth-api",
|
|
855
|
+
"path": "crates/auth-api",
|
|
856
|
+
"publishers": [
|
|
857
|
+
{
|
|
858
|
+
"kind": "docker",
|
|
859
|
+
"image": "ghcr.io/ferrlabs/auth-api",
|
|
860
|
+
"tags": ["{version}", "{major}", "{minor}", "latest"],
|
|
861
|
+
"platforms": ["linux/amd64", "linux/arm64"],
|
|
862
|
+
"sign": "sigstore"
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
`{version}`, `{major}`, `{minor}` expand from the release version. `sign: "sigstore"` runs `cosign sign --yes <image>@<digest>` against the produced manifest after push. Auth assumes you've already run `docker login` (FerrFlow doesn't manage docker credentials so it doesn't conflict with the rest of the CI).
|
|
871
|
+
|
|
872
|
+
### Helm publisher
|
|
873
|
+
|
|
874
|
+
```json title="ferrflow.json"
|
|
875
|
+
{
|
|
876
|
+
"package": [
|
|
877
|
+
{
|
|
878
|
+
"name": "ferrvault-operator",
|
|
879
|
+
"path": "operator",
|
|
880
|
+
"publishers": [
|
|
881
|
+
{
|
|
882
|
+
"kind": "helm",
|
|
883
|
+
"chart": "chart",
|
|
884
|
+
"registry": "oci://ghcr.io/ferrlabs/charts"
|
|
885
|
+
}
|
|
886
|
+
]
|
|
887
|
+
}
|
|
888
|
+
]
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
`chart` is the directory containing `Chart.yaml`, relative to the package path. Auth assumes `helm registry login` was done in CI.
|
|
893
|
+
|
|
894
|
+
### GitHub release asset
|
|
895
|
+
|
|
896
|
+
```json title="ferrflow.json"
|
|
897
|
+
{
|
|
898
|
+
"publishers": [
|
|
899
|
+
{ "kind": "github-release-asset", "path": "sbom.cdx.json" },
|
|
900
|
+
{
|
|
901
|
+
"kind": "github-release-asset",
|
|
902
|
+
"path": "ferrflow-linux-x64.tar.gz.bundle",
|
|
903
|
+
"displayName": "linux-x64.bundle"
|
|
904
|
+
}
|
|
905
|
+
]
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
Re-uploads use `--clobber` so a retry replaces the previous file rather than failing. Picks up `GITHUB_TOKEN` from the environment, same as `gh release upload`.
|
|
910
|
+
|
|
911
|
+
### Webhook
|
|
912
|
+
|
|
913
|
+
```json title="ferrflow.json"
|
|
914
|
+
{
|
|
915
|
+
"publishers": [
|
|
916
|
+
{
|
|
917
|
+
"kind": "webhook",
|
|
918
|
+
"url": "https://hooks.slack.com/services/...",
|
|
919
|
+
"body": { "text": "Released {name}@{version} :rocket:" },
|
|
920
|
+
"headers": { "Authorization": "Bearer {env:SLACK_TOKEN}" }
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
`{name}`, `{version}`, `{tag}` and `{env:NAME}` are interpolated in the URL, body, and header values. Missing `{env:NAME}` is an error (a webhook with an unset bearer token must NOT send anonymously).
|
|
927
|
+
|
|
928
|
+
### Extra flags (`args`)
|
|
929
|
+
|
|
930
|
+
Every command-based publisher — `cargo`, `npm`, `docker`, `helm`, `github-release-asset` — accepts an `args` array. The strings are appended verbatim to the underlying tool invocation, so you can pass options FerrFlow doesn't model natively without waiting for a new field.
|
|
931
|
+
|
|
932
|
+
```json title="ferrflow.json"
|
|
933
|
+
{
|
|
934
|
+
"publishers": [
|
|
935
|
+
{ "kind": "cargo", "registry": "kellnr", "args": ["--locked"] },
|
|
936
|
+
{ "kind": "npm", "args": ["--provenance"] },
|
|
937
|
+
{
|
|
938
|
+
"kind": "docker",
|
|
939
|
+
"image": "ghcr.io/ferrlabs/api",
|
|
940
|
+
"args": ["--build-arg", "PROFILE=release"]
|
|
941
|
+
}
|
|
942
|
+
]
|
|
943
|
+
}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
For the docker publisher, `args` are inserted before the build-context positional (buildx rejects flags that follow the context argument). The `webhook` publisher has no `args` — it isn't a shelled-out command.
|
|
947
|
+
|
|
948
|
+
### Deferring publishing to a separate job (`deferPublish`)
|
|
949
|
+
|
|
950
|
+
By default publishers run inline at the end of `ferrflow release`. That's fine when the release job already has what the publishers need — but `docker`, `helm`, and `npm` publishers need a build toolchain (buildx, helm, a built `dist/`) that a minimal release job often doesn't carry.
|
|
951
|
+
|
|
952
|
+
Set `workspace.deferPublish: true` and `ferrflow release` will **skip** the publishers; a separate [`ferrflow publish`](/docs/reference/cli/#ferrflow-publish) run executes them. You keep **one** config file:
|
|
953
|
+
|
|
954
|
+
```json title="ferrflow.json"
|
|
955
|
+
{
|
|
956
|
+
"workspace": { "deferPublish": true },
|
|
957
|
+
"package": [
|
|
958
|
+
{
|
|
959
|
+
"name": "my-operator",
|
|
960
|
+
"path": ".",
|
|
961
|
+
"versionedFiles": [{ "path": "go.mod", "format": "gomod" }],
|
|
962
|
+
"publishers": [
|
|
963
|
+
{ "kind": "docker", "image": "ghcr.io/acme/my-operator", "tags": ["{version}", "latest"] },
|
|
964
|
+
{ "kind": "helm", "chart": "charts/my-operator", "registry": "oci://ghcr.io/acme/charts" }
|
|
965
|
+
]
|
|
966
|
+
}
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
`release` versions + tags as usual; a tag-triggered job that sets up the toolchain runs `ferrflow publish` (which always runs the publishers, ignoring `deferPublish`). See the [`ferrflow publish`](/docs/reference/cli/#ferrflow-publish) reference for the matching workflow.
|
|
972
|
+
|
|
973
|
+
### Migrating from postPublish hooks
|
|
974
|
+
|
|
975
|
+
The publishers block is additive — your existing `postPublish` hooks keep working. Recommended order:
|
|
976
|
+
|
|
977
|
+
1. Add a `publishers` block alongside your hook with `--dry-run` enabled in CI to preview what would be published.
|
|
978
|
+
2. Once the dry-run output matches what your hook does, switch CI to a real `ferrflow release` and verify the live publish.
|
|
979
|
+
3. Delete the `postPublish` hook.
|
|
980
|
+
|
|
981
|
+
The crash-resume checkpoint (see [pipeline triggers](/docs/ci/pipeline-triggers#crash-resume)) means a partially-failed publish is safely re-runnable — the publishers that already succeeded skip themselves on the second pass.
|
|
982
|
+
|
|
983
|
+
## Complete examples
|
|
984
|
+
|
|
985
|
+
### Single repo
|
|
986
|
+
|
|
987
|
+
<div class="ferr-tabs">
|
|
988
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
989
|
+
"$schema": "https://ferrflow.com/schema/ferrflow.json",
|
|
990
|
+
"workspace": {
|
|
991
|
+
"tagTemplate": "v{version}"
|
|
992
|
+
},
|
|
993
|
+
"package": [
|
|
994
|
+
{
|
|
995
|
+
"name": "ferrflow",
|
|
996
|
+
"path": ".",
|
|
997
|
+
"changelog": "CHANGELOG.md",
|
|
998
|
+
"versionedFiles": [
|
|
999
|
+
{ "path": "Cargo.toml", "format": "toml" },
|
|
1000
|
+
{ "path": "npm/package.json", "format": "json" }
|
|
1001
|
+
]
|
|
1002
|
+
}
|
|
1003
|
+
]
|
|
1004
|
+
}
|
|
1005
|
+
</code></pre>
|
|
1006
|
+
</div></div>
|
|
1007
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
1008
|
+
tag_template = "v{version}"
|
|
1009
|
+
|
|
1010
|
+
[[package]]
|
|
1011
|
+
name = "ferrflow"
|
|
1012
|
+
path = "."
|
|
1013
|
+
changelog = "CHANGELOG.md"
|
|
1014
|
+
|
|
1015
|
+
[[package.versioned_files]]
|
|
1016
|
+
path = "Cargo.toml"
|
|
1017
|
+
format = "toml"
|
|
1018
|
+
|
|
1019
|
+
[[package.versioned_files]]
|
|
1020
|
+
path = "npm/package.json"
|
|
1021
|
+
format = "json"
|
|
1022
|
+
</code></pre>
|
|
1023
|
+
|
|
1024
|
+
</div></div>
|
|
1025
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
1026
|
+
$schema: "https://ferrflow.com/schema/ferrflow.json",
|
|
1027
|
+
workspace: {
|
|
1028
|
+
tagTemplate: "v{version}",
|
|
1029
|
+
},
|
|
1030
|
+
package: [
|
|
1031
|
+
{
|
|
1032
|
+
name: "ferrflow",
|
|
1033
|
+
path: ".",
|
|
1034
|
+
changelog: "CHANGELOG.md",
|
|
1035
|
+
versionedFiles: [
|
|
1036
|
+
{ path: "Cargo.toml", format: "toml" },
|
|
1037
|
+
{ path: "npm/package.json", format: "json" },
|
|
1038
|
+
],
|
|
1039
|
+
},
|
|
1040
|
+
],
|
|
1041
|
+
}
|
|
1042
|
+
</code></pre>
|
|
1043
|
+
</div></div>
|
|
1044
|
+
</div>
|
|
1045
|
+
|
|
1046
|
+
### Monorepo
|
|
1047
|
+
|
|
1048
|
+
<div class="ferr-tabs">
|
|
1049
|
+
<div class="ferr-tab" data-label="JSON"><p class="ferr-tab__label">JSON</p><div class="ferr-tab__body"><pre><code class="language-json">{
|
|
1050
|
+
"$schema": "https://ferrflow.com/schema/ferrflow.json",
|
|
1051
|
+
"workspace": {
|
|
1052
|
+
"tagTemplate": "{name}@v{version}"
|
|
1053
|
+
},
|
|
1054
|
+
"package": [
|
|
1055
|
+
{
|
|
1056
|
+
"name": "api",
|
|
1057
|
+
"path": "packages/api",
|
|
1058
|
+
"changelog": "packages/api/CHANGELOG.md",
|
|
1059
|
+
"sharedPaths": ["packages/shared/"],
|
|
1060
|
+
"versionedFiles": [
|
|
1061
|
+
{ "path": "packages/api/Cargo.toml", "format": "toml" }
|
|
1062
|
+
]
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
"name": "site",
|
|
1066
|
+
"path": "packages/site",
|
|
1067
|
+
"changelog": "packages/site/CHANGELOG.md",
|
|
1068
|
+
"sharedPaths": ["packages/shared/"],
|
|
1069
|
+
"versionedFiles": [
|
|
1070
|
+
{ "path": "packages/site/package.json", "format": "json" }
|
|
1071
|
+
]
|
|
1072
|
+
}
|
|
1073
|
+
]
|
|
1074
|
+
}
|
|
1075
|
+
</code></pre>
|
|
1076
|
+
</div></div>
|
|
1077
|
+
<div class="ferr-tab" data-label="TOML"><p class="ferr-tab__label">TOML</p><div class="ferr-tab__body"><pre><code class="language-toml">[workspace]
|
|
1078
|
+
tag_template = "{name}@v{version}"
|
|
1079
|
+
|
|
1080
|
+
[[package]]
|
|
1081
|
+
name = "api"
|
|
1082
|
+
path = "packages/api"
|
|
1083
|
+
changelog = "packages/api/CHANGELOG.md"
|
|
1084
|
+
shared_paths = ["packages/shared/"]
|
|
1085
|
+
|
|
1086
|
+
[[package.versioned_files]]
|
|
1087
|
+
path = "packages/api/Cargo.toml"
|
|
1088
|
+
format = "toml"
|
|
1089
|
+
|
|
1090
|
+
[[package]]
|
|
1091
|
+
name = "site"
|
|
1092
|
+
path = "packages/site"
|
|
1093
|
+
changelog = "packages/site/CHANGELOG.md"
|
|
1094
|
+
shared_paths = ["packages/shared/"]
|
|
1095
|
+
|
|
1096
|
+
[[package.versioned_files]]
|
|
1097
|
+
path = "packages/site/package.json"
|
|
1098
|
+
format = "json"
|
|
1099
|
+
</code></pre>
|
|
1100
|
+
|
|
1101
|
+
</div></div>
|
|
1102
|
+
<div class="ferr-tab" data-label="JSON5"><p class="ferr-tab__label">JSON5</p><div class="ferr-tab__body"><pre><code class="language-json5">{
|
|
1103
|
+
$schema: "https://ferrflow.com/schema/ferrflow.json",
|
|
1104
|
+
workspace: {
|
|
1105
|
+
tagTemplate: "{name}@v{version}",
|
|
1106
|
+
},
|
|
1107
|
+
package: [
|
|
1108
|
+
{
|
|
1109
|
+
name: "api",
|
|
1110
|
+
path: "packages/api",
|
|
1111
|
+
changelog: "packages/api/CHANGELOG.md",
|
|
1112
|
+
sharedPaths: ["packages/shared/"],
|
|
1113
|
+
versionedFiles: [
|
|
1114
|
+
{ path: "packages/api/Cargo.toml", format: "toml" },
|
|
1115
|
+
],
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
name: "site",
|
|
1119
|
+
path: "packages/site",
|
|
1120
|
+
changelog: "packages/site/CHANGELOG.md",
|
|
1121
|
+
sharedPaths: ["packages/shared/"],
|
|
1122
|
+
versionedFiles: [
|
|
1123
|
+
{ path: "packages/site/package.json", format: "json" },
|
|
1124
|
+
],
|
|
1125
|
+
},
|
|
1126
|
+
],
|
|
1127
|
+
}
|
|
1128
|
+
</code></pre>
|
|
1129
|
+
</div></div>
|
|
1130
|
+
</div>
|
|
1131
|
+
|
|
1132
|
+
<aside class="ferr-aside ferr-aside--tip"><div class="ferr-aside__body"><p>Run <code>ferrflow init</code> to generate a config file automatically based on what FerrFlow detects in your repo.</p>
|
|
1133
|
+
</div></aside>
|