name: Reusable Auto Release
on:
workflow_call:
inputs:
bump:
description: Version bump strategy (auto, major, minor, patch).
required: false
default: auto
type: string
tag-prefix:
description: Prefix for release tags.
required: false
default: v
type: string
update-major-alias:
description: Also move the moving major alias tag to the new release.
required: false
default: false
type: boolean
notes-file:
description: Path where generated release notes are written.
required: false
default: release_notes.md
type: string
dry-run:
description: Analyze and report without creating any commit, tag, or release.
required: false
default: false
type: boolean
create-pr:
description: Create or update an automated release branch and pull request instead of publishing directly.
required: false
default: false
type: boolean
release-branch:
description: Automation-owned release branch (automation/release or automation/release/*).
required: false
default: automation/release
type: string
required-workflows:
description: Comma-separated workflow names that must pass for the target commit.
required: false
default: ""
type: string
dispatch-workflows:
description: Comma-separated workflow files to dispatch after a release.
required: false
default: ""
type: string
dispatch-version-workflow:
description: Optional workflow file that receives the released version as a version input.
required: false
default: ""
type: string
github-app-id:
description: Optional GitHub App installation ID source. Pair with github-app-private-key to authenticate release commits and PRs as the App.
required: false
default: ""
type: string
secrets:
release-token:
description: Optional GitHub App or PAT token; falls back to GITHUB_TOKEN. Required if the created tag must trigger tag pipelines natively.
required: false
github-app-private-key:
description: Private key for the GitHub App identified by github-app-id. Store it as an organization or repository secret.
required: false
outputs:
released:
description: Whether a release was created (true or false).
value: ${{ jobs.release.outputs.released }}
version:
description: The released version without the tag prefix.
value: ${{ jobs.release.outputs.version }}
tag:
description: The created release tag.
value: ${{ jobs.release.outputs.tag }}
release-url:
description: URL of the created GitHub Release.
value: ${{ jobs.release.outputs.release-url }}
release-pr-number:
description: Number of the created or updated automated release pull request.
value: ${{ jobs.release.outputs.release-pr-number }}
release-pr-url:
description: URL of the created or updated automated release pull request.
value: ${{ jobs.release.outputs.release-pr-url }}
release-branch:
description: Branch used for the automated release pull request.
value: ${{ jobs.release.outputs.release-branch }}
permissions:
contents: read
pull-requests: write
jobs:
check:
name: Check release prerequisites
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
outputs:
should-release: ${{ steps.gate.outputs.should-release }}
target-sha: ${{ steps.target.outputs.sha }}
steps:
- name: Checkout target commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
- name: Resolve target commit
id: target
env:
EVENT_NAME: ${{ github.event_name }}
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == workflow_run ]]; then
[[ "$WORKFLOW_RUN_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid workflow-run head SHA" >&2; exit 1; }
echo "sha=$WORKFLOW_RUN_HEAD_SHA" >> "$GITHUB_OUTPUT"
else
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
fi
- name: Verify required workflows
id: gate
env:
GH_TOKEN: ${{ github.token }}
TARGET_SHA: ${{ steps.target.outputs.sha }}
REQUIRED_WORKFLOWS: ${{ inputs.required-workflows }}
EVENT_NAME: ${{ github.event_name }}
TRIGGER_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
TRIGGER_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == workflow_run && ( "$TRIGGER_CONCLUSION" != success || "$TRIGGER_REPOSITORY" != "$GITHUB_REPOSITORY" ) ]]; then
echo "should-release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ -z "$REQUIRED_WORKFLOWS" ]]; then
echo "should-release=true" >> "$GITHUB_OUTPUT"
exit 0
fi
IFS=',' read -ra workflows <<< "$REQUIRED_WORKFLOWS"
for workflow in "${workflows[@]}"; do
workflow="$(xargs <<< "$workflow")"
[[ -n "$workflow" ]] || continue
conclusion="$(gh run list --workflow="$workflow" --commit="$TARGET_SHA" --limit=1 --json conclusion --jq '.[0].conclusion // ""')"
if [[ "$conclusion" != success ]]; then
echo "Required workflow '$workflow' is '$conclusion' for $TARGET_SHA"
echo "should-release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "should-release=true" >> "$GITHUB_OUTPUT"
release:
name: Auto Release
needs: check
if: needs.check.outputs.should-release == 'true'
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
pull-requests: write
outputs:
released: ${{ steps.release.outputs.released }}
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
release-url: ${{ steps.release.outputs.release-url }}
release-pr-number: ${{ steps.release.outputs.release-pr-number }}
release-pr-url: ${{ steps.release.outputs.release-pr-url }}
release-branch: ${{ steps.release.outputs.release-branch }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
ref: ${{ needs.check.outputs.target-sha }}
- name: Checkout the action at the workflow ref
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
repository: ${{ job.workflow_repository || 'ThreatFlux/github_actions' }}
ref: ${{ job.workflow_sha }}
path: .release-action
persist-credentials: false
- name: Create GitHub App installation token
id: github-app-token
if: inputs.github-app-id != '' && secrets.github-app-private-key != ''
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with:
app-id: ${{ inputs.github-app-id }}
private-key: ${{ secrets.github-app-private-key }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
- name: Validate GitHub App configuration
if: (inputs.github-app-id == '') != (secrets.github-app-private-key == '')
run: |
echo "github-app-id and github-app-private-key must be configured together" >&2
exit 1
- name: Release
id: release
uses: ./.release-action
with:
command: release
token: ${{ steps.github-app-token.outputs.token || secrets.release-token || github.token }}
bump: ${{ inputs.bump }}
tag-prefix: ${{ inputs.tag-prefix }}
update-major-alias: ${{ inputs.update-major-alias }}
notes-file: ${{ inputs.notes-file }}
dry-run: ${{ inputs.dry-run }}
create-pr: ${{ inputs.create-pr }}
release-branch: ${{ inputs.release-branch }}
- name: Dispatch downstream release workflows
if: steps.release.outputs.released == 'true' && inputs.dispatch-workflows != ''
env:
GH_TOKEN: ${{ steps.github-app-token.outputs.token || secrets.release-token || github.token }}
TAG: ${{ steps.release.outputs.tag }}
VERSION: ${{ steps.release.outputs.version }}
DISPATCH_WORKFLOWS: ${{ inputs.dispatch-workflows }}
VERSION_WORKFLOW: ${{ inputs.dispatch-version-workflow }}
run: |
set -euo pipefail
IFS=',' read -ra workflows <<< "$DISPATCH_WORKFLOWS"
for workflow in "${workflows[@]}"; do
workflow="$(xargs <<< "$workflow")"
[[ -n "$workflow" ]] || continue
if [[ "$workflow" == "$VERSION_WORKFLOW" ]]; then
gh workflow run "$workflow" --repo "$GITHUB_REPOSITORY" --ref "$TAG" -f version="$VERSION"
else
gh workflow run "$workflow" --repo "$GITHUB_REPOSITORY" --ref "$TAG"
fi
done