Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/release-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Release pipeline

on:
workflow_call:
inputs:
version-command:
description: >
Override for version detection (e.g., `poetry version --short`).
Auto-detects from VERSION/package.json/pyproject.toml if empty.
type: string
default: ""
force_pr:
description: Force pull request creation
type: boolean
default: false

outputs:
created-tag:
description: The tag if one was created (truthy means release shipped)
value: ${{ jobs.detect_release.outputs.created-tag }}
current-version:
description: The current version from detect_release
value: ${{ jobs.detect_release.outputs.current-version }}

jobs:
detect_release:
name: Detect release
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2

- id: version-command
run: |
custom="${{ inputs.version-command }}"
if [ -n "$custom" ]; then
echo "cmd=$custom" >> $GITHUB_OUTPUT
elif [ -f package.json ]; then
echo "cmd=node -p \"require('./package.json').version\"" >> $GITHUB_OUTPUT
elif [ -f pyproject.toml ]; then
echo "cmd=grep -Po '(?<=^version = \")([^\"]+)' pyproject.toml" >> $GITHUB_OUTPUT
elif [ -f VERSION ]; then
echo "cmd=cat VERSION" >> $GITHUB_OUTPUT
else
echo "::error::No version source found. Set version-command input or add VERSION/package.json/pyproject.toml."
exit 1
fi
shell: bash

- id: tag
continue-on-error: true
uses: salsify/action-detect-and-tag-new-version@v2
with:
version-command: ${{ steps.version-command.outputs.cmd }}
tag-annotation-template: |
chore(release): {VERSION}

outputs:
created-tag: ${{ steps.tag.outputs.tag }}
current-version: ${{ steps.tag.outputs.current-version }}

build_changelog:
name: Build changelog
runs-on: ubuntu-latest
needs: detect_release
if: "!needs.detect_release.outputs.created-tag"

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- id: changelog
uses: flowcanon/release-builder/build-changelog@v2

outputs:
has-prs: ${{ steps.changelog.outputs.has-prs }}
previous-version: ${{ steps.changelog.outputs.previous-version }}
next-version: ${{ steps.changelog.outputs.next-version }}
notes: ${{ steps.changelog.outputs.notes }}
release: ${{ steps.changelog.outputs.release }}

create_pr:
if: inputs.force_pr || needs.build_changelog.outputs.has-prs
name: Create pull request
runs-on: ubuntu-latest
needs: build_changelog

steps:
- uses: actions/checkout@v4

- uses: flowcanon/release-builder/package-version@v2
with:
version: ${{ needs.build_changelog.outputs.next-version }}

- uses: flowcanon/release-builder/pull-request@v2
with:
next-version: ${{ needs.build_changelog.outputs.next-version }}
notes: ${{ needs.build_changelog.outputs.notes }}
previous-version: ${{ needs.build_changelog.outputs.previous-version }}
release: ${{ needs.build_changelog.outputs.release }}
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
build_changelog:
name: Build changelog
runs-on: ubuntu-latest
needs: detect_release
if: "!needs.detect_release.outputs.created-tag"

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Each directory contains an `action.yml` (the composite action definition) and su

## Release pipeline

**Downstream repos should use the reusable workflow** (`.github/workflows/release-pipeline.yml`) rather than assembling jobs from composite actions. The reusable workflow enforces correct job ordering (`detect_release → build_changelog → create_pr`) and prevents race conditions where `build_changelog` runs in parallel with `detect_release`. This repo's own dogfood workflow (`.github/workflows/release.yml`) can't use the reusable workflow because it references actions via `./` local paths, so it handles ordering with explicit `needs` and `if` conditions.

The actions are designed to run in this order:

1. **build-changelog** — Finds merged PRs since the last git tag, generates release notes, determines version bump type (major/minor/patch) from PR title keywords, calculates the next semantic version.
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,77 @@ Sends Slack notifications about release status. Can send an initial "pending" me

---

## Reusable Workflow (Recommended)

The easiest way to use release-builder is with the reusable workflow. It encapsulates the full pipeline with correct job ordering, preventing race conditions between tag detection and changelog building.

### Simple repo

```yaml
name: Release

on:
push:
branches: [main]
workflow_dispatch:
inputs:
force_pr:
description: Force pull request
type: boolean

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

jobs:
release:
uses: flowcanon/release-builder/.github/workflows/release-pipeline.yml@v2
with:
force_pr: ${{ inputs.force_pr || false }}
```

### With deploy step

For repos that deploy on release, add a job that depends on the reusable workflow's outputs:

```yaml
jobs:
release:
uses: flowcanon/release-builder/.github/workflows/release-pipeline.yml@v2
with:
force_pr: ${{ inputs.force_pr || false }}

deploy:
if: needs.release.outputs.created-tag
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./script/deploy
```

### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `version-command` | string | `""` | Override for version detection (e.g., `poetry version --short`). Auto-detects from VERSION/package.json/pyproject.toml if empty. |
| `force_pr` | boolean | `false` | Force PR creation even if no PRs are found |

### Outputs

| Output | Description |
|--------|-------------|
| `created-tag` | The tag if one was created (truthy means a release shipped) |
| `current-version` | The current version from detect_release |

### Why use the reusable workflow?

When using composite actions directly, `detect_release` and `build_changelog` can run in parallel. If a PR title was renamed after the release PR was created, `build_changelog` may calculate a different version than what was tagged, opening a bogus release PR. The reusable workflow enforces `detect_release → build_changelog → create_pr` ordering, so `build_changelog` is skipped when a tag is created.

## Composite Actions

If you need more control (e.g., custom deploy steps between detection and changelog), you can use the composite actions directly. See the sections below.

## Example

This repository uses its own actions for releases. See [`.github/workflows/release.yml`](.github/workflows/release.yml) for a working implementation.
Expand Down