Skip to content

Release

Release #217

Workflow file for this run

name: Release
# Automated releases trigger after CI completes on main. Release commits
# contain [skip ci] so GitHub skips CI for them entirely, breaking the loop
# without any extra condition needed.
#
# A single file calls _release.reusable.yml so that npm OIDC trusted
# publishing can be configured against one workflow filename.
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
packages:
description: Packages to release
type: choice
options:
- all
- version
- notes
- publish
- release
default: all
bump:
description: Version bump type
type: choice
options:
- auto
- patch
- minor
- major
- prerelease
- prepatch
- preminor
- premajor
default: auto
npm_auth:
description: NPM auth method
type: choice
options:
- auto
- oidc
- token
default: oidc
sync:
description: Use synchronized versioning across all packages
type: boolean
default: true
dry_run:
description: Dry run (no publish, no push)
type: boolean
default: true
concurrency:
group: release
cancel-in-progress: false
jobs:
check-labels:
name: Check Release Labels
runs-on: ubuntu-latest
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'
outputs:
should_release: ${{ steps.check.outputs.should_release }}
bump: ${{ steps.check.outputs.bump }}
steps:
- name: Check for release labels
id: check
env:
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
# Helper function to safely call gh API and capture output
gh_api() {
local result
result=$(gh api "$1" 2>&1) || true
echo "$result"
}
# Get PRs that were merged with this commit (includes labels)
PRS_OUTPUT=$(gh_api "repos/$REPO/commits/$COMMIT_SHA/pulls")
PRS=$(echo "$PRS_OUTPUT" | jq -r '.[].number' 2>/dev/null | tr '\n' ' ') || PRS=""
if [ -z "$PRS" ]; then
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "No PRs found for commit, skipping release"
[ -n "$PRS_OUTPUT" ] && echo "Debug: $PRS_OUTPUT"
exit 0
fi
# Extract the release label and derive the bump type from it.
# Only bump:* labels are supported (release:* labels are deprecated).
LABELS=$(echo "$PRS_OUTPUT" | jq -r '.[].labels[].name' 2>/dev/null)
# Check for release:stable first (takes precedence over bump:* per ci-setup.md)
if echo "$LABELS" | grep -qE "^release:stable$"; then
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "bump=auto" >> "$GITHUB_OUTPUT"
echo "release:stable label found — promoting prerelease to stable"
exit 0
fi
# Check for release:prerelease + bump:* (creates prerelease)
if echo "$LABELS" | grep -qE "^release:prerelease$"; then
# Map bump label to prerelease bump type
PRERELEASE_BUMP="prerelease"
if echo "$LABELS" | grep -qE "^bump:patch$"; then
PRERELEASE_BUMP="prepatch"
elif echo "$LABELS" | grep -qE "^bump:minor$"; then
PRERELEASE_BUMP="preminor"
elif echo "$LABELS" | grep -qE "^bump:major$"; then
PRERELEASE_BUMP="premajor"
fi
if echo "$LABELS" | grep -qE "^bump:(patch|minor|major)$"; then
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "bump=$PRERELEASE_BUMP" >> "$GITHUB_OUTPUT"
echo "release:prerelease + bump:* label found — creating $PRERELEASE_BUMP prerelease"
exit 0
else
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "release:prerelease requires a bump:* label — skipping release"
exit 0
fi
fi
# Check for bump:* labels
BUMP_LABEL=$(echo "$LABELS" | grep -E "^bump:(patch|minor|major)$" | head -1)
if [ -n "$BUMP_LABEL" ]; then
BUMP_TYPE="${BUMP_LABEL#bump:}"
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "bump=$BUMP_TYPE" >> "$GITHUB_OUTPUT"
echo "Bump label '${BUMP_LABEL}' found — bump: ${BUMP_TYPE}"
exit 0
fi
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "No release label found on merged PRs, skipping release"
release-auto:
name: Auto Release
needs: check-labels
if: needs.check-labels.outputs.should_release == 'true'
uses: ./.github/workflows/_release.reusable.yml
permissions:
id-token: write
contents: write
with:
packages: all
bump: ${{ needs.check-labels.outputs.bump || 'auto' }}
sync: true
dry_run: false
npm_auth: oidc
secrets:
ssh_key: ${{ secrets.DEPLOY_KEY }}
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
release-manual:
name: Manual Release
if: github.event_name == 'workflow_dispatch'
uses: ./.github/workflows/_release.reusable.yml
permissions:
id-token: write
contents: write
with:
packages: ${{ inputs.packages }}
bump: ${{ inputs.bump }}
npm_auth: ${{ inputs.npm_auth }}
sync: ${{ inputs.sync }}
dry_run: ${{ inputs.dry_run }}
secrets:
ssh_key: ${{ secrets.DEPLOY_KEY }}
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
build-action-manual:
name: Build and tag action dist
needs: release-manual
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-manual.outputs.action_tag != '' }}
uses: ./.github/workflows/_action-release.reusable.yml
permissions:
contents: write
with:
tag: ${{ needs.release-manual.outputs.action_tag }}
secrets:
ssh_key: ${{ secrets.DEPLOY_KEY }}
build-action-auto:
name: Build and tag action dist
needs: release-auto
if: ${{ github.event_name == 'workflow_run' && needs.release-auto.outputs.action_tag != '' }}
uses: ./.github/workflows/_action-release.reusable.yml
permissions:
contents: write
with:
tag: ${{ needs.release-auto.outputs.action_tag }}
secrets:
ssh_key: ${{ secrets.DEPLOY_KEY }}