Merge pull request #106 from Sea-Haven-Industries/feat/release-on-reusable-change

feat(release): cut a tag and release when a reusable workflow changes
This commit is contained in:
Adam Moussa 2026-07-28 17:05:28 -04:00 • committed by GitHub
commit 2fbfb2e7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

109
.github/workflows/release-on-merge.yaml vendored Normal file
View file

@ -0,0 +1,109 @@
name: Release on reusable change
# Cuts a tag and GitHub Release for THIS repo when a merge to main changes a
# reusable workflow.
#
# Why this exists: every caller in the org pins its `uses:` reference to a
# commit SHA of this repo. Dependabot's github-actions updater resolves a newer
# SHA for a pinned reference by looking at that repo's tags and releases. This
# repo has had neither, so a pinned reference has nothing to be bumped to and
# Dependabot correctly reports no update. Tagging each change to a reusable
# gives it something to diff against.
#
# The trigger is deliberately narrow. Only workflows carrying `on:
# workflow_call` are consumed by other repos, so only those warrant a release.
# Changes to this repo's own CI, its labeler caller, this file, the starter
# templates under workflow-templates/ and the docs change nothing for a caller
# pinned to a SHA, so they do not cut one. The exclusions below are the
# non-reusable workflows; add to this list when adding another workflow that is
# not `workflow_call`, or it will cut a release it does not need to.
#
# Version scheme: patch bump from the highest existing vMAJOR.MINOR.PATCH tag,
# starting at 1.0.0 when there are none. Use the manual dispatch with an
# explicit version for a minor or major bump, which is the case whenever a
# reusable gains a required input, renames one, or otherwise breaks a caller.
on:
push:
branches: [main]
paths:
- ".github/workflows/**"
- "!.github/workflows/ci.yaml"
- "!.github/workflows/labeler.yaml"
- "!.github/workflows/release-on-merge.yaml"
workflow_dispatch:
inputs:
version:
description: 'Explicit version, e.g. "1.1.0". Leave empty to patch-bump the highest existing tag.'
type: string
required: false
default: ""
# Runs are serialised, never cancelled. Two merges landing close together would
# otherwise both read the same highest tag, compute the same next version, and
# the second would find the tag already created and no-op — leaving that change
# unreleased. Queuing makes the second run read the tag the first one pushed.
concurrency:
group: release-on-merge
cancel-in-progress: false
permissions:
contents: read
jobs:
version:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.next.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
# Tags are the input to the version calculation, so they must be
# fetched; a shallow checkout without them would restart at 1.0.0.
fetch-depth: 0
fetch-tags: true
- name: Compute the next version
id: next
env:
OVERRIDE: ${{ inputs.version }}
run: |
set -euo pipefail
if [ -n "${OVERRIDE}" ]; then
echo "version=${OVERRIDE#v}" >> "${GITHUB_OUTPUT}"
echo "Using the explicitly supplied version ${OVERRIDE}."
exit 0
fi
# Only plain vMAJOR.MINOR.PATCH tags take part. -v:refname sorts by
# version rather than lexically, so v1.0.10 ranks above v1.0.9.
latest="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1)"
if [ -z "${latest}" ]; then
next="1.0.0"
echo "No existing version tag. Starting at ${next}."
else
current="${latest#v}"
major="${current%%.*}"
rest="${current#*.}"
minor="${rest%%.*}"
patch="${rest#*.}"
next="${major}.${minor}.$((patch + 1))"
echo "Highest existing tag is ${latest}. Next version is ${next}."
fi
echo "version=${next}" >> "${GITHUB_OUTPUT}"
release:
needs: version
# Referenced by local path, not by SHA. A repo calling its own reusable
# cannot pin to a commit of itself without the pin lagging the file it
# points at by one merge every time either changes.
uses: ./.github/workflows/release.yaml
permissions:
contents: write
with:
version: ${{ needs.version.outputs.version }}