# `release --json` output
`ferrflow release --json` emits a single JSON object on stdout describing
the release. All other human-readable stdout is suppressed so the object
is the only thing written (diagnostics and warnings still go to stderr).
This is the machine-readable counterpart to `check --json` and is meant
for CI steps that react to what a release just produced.
Combine it with `--dry-run` to get the computed plan without mutating the
repository.
## Schema
```json
{
"released": [
{
"package": "api",
"previous_version": "1.2.3",
"new_version": "1.3.0",
"bump_type": "minor",
"tag": "api@v1.3.0",
"commit_count": 7,
"prerelease": false,
"forge_release_url": "https://github.com/owner/repo/releases/tag/api@v1.3.0",
"forge_release_id": 123456789
}
],
"skipped": [
{ "package": "web", "reason": "no releasable commits" }
],
"git": {
"commit": "abc1234",
"tags_pushed": ["api@v1.3.0"],
"branch": "main"
},
"dry_run": false
}
```
### Fields
| `released[]` | array | One entry per package that was (or would be) released this run, including dependency-cascade bumps. |
| `released[].package` | string | Package name from the config. |
| `released[].previous_version` | string | Version before the bump. |
| `released[].new_version` | string | Version after the bump. |
| `released[].bump_type` | string | `major`, `minor`, `patch`, `forced`, or a strategy name (`calver`, `sequential`, …). |
| `released[].tag` | string | The tag that was (or would be) created. |
| `released[].commit_count` | number | Number of commits considered for this package's changelog. |
| `released[].prerelease` | boolean | Whether the new version is a pre-release. |
| `released[].forge_release_url` | string \| null | URL of the created forge release. `null` on dry-run, when no forge is configured, or when the release was not created. |
| `released[].forge_release_id` | number \| null | Numeric id of the created forge release (GitHub). `null` on dry-run, on GitLab, or when not created. |
| `skipped[]` | array | Packages that were considered but not released. |
| `skipped[].package` | string | Package name. |
| `skipped[].reason` | string | Why it was skipped (`not touched`, `no new commits`, `no releasable commits`, `version unchanged`). |
| `git.commit` | string | Short HEAD SHA after the run. |
| `git.tags_pushed` | array | Tags pushed to the remote. Empty on dry-run. |
| `git.branch` | string | Branch the release targeted. |
| `dry_run` | boolean | `true` when `--dry-run` was passed. |
## Dry-run behaviour
With `--dry-run --json`:
- `dry_run` is `true`.
- `released[]` and `skipped[]` are populated from the computed plan.
- `git.tags_pushed` is `[]` and the `forge_release_*` fields are `null`
(nothing is created or pushed).
## Interaction with `--dry-run --verbose`
`--dry-run --verbose` (without `--json`) prints a unified diff of every
file a release would change, including the changelog. When both `--json`
and `--dry-run --verbose` are passed, `--json` wins: only the JSON object
is emitted and the diffs are suppressed.
## Stability
The field names and structure above are a stable contract. New optional
fields may be added over time; existing fields will not be removed or
renamed without a major version bump. Consumers should ignore unknown
fields rather than failing on them.