feat: tag and publish a Gitea release on every push to master
Release / release (push) Successful in 6s
Release / release (push) Successful in 6s
Replicate the dsh-facet release stack: a Gitea Actions workflow (.gitea/workflows/release.yml) + .gitea/scripts/release.sh read the commit subjects since the last tag, bump package.json, prepend CHANGELOG.md, tag vX.Y.Z and publish the release via API. Agreed install ref is #semver:^0.1.0, and tags are what 'dsh plugin --profile web update' moves to, so the workflow is what makes updates work. Add the release.test.sh suite (43 checks), npm scripts (test:release / release:plan / release:notes), AGENTS.md with the commit conventions, CHANGELOG.md seed, and the README *Releases* rules.
This commit is contained in:
1 parent
147227bb3c
commit
604eb44127
7 files changed
+760
-1
No files matched your search
@@ -0,0 +1,149 @@
|
||||
# Publish a release every time master moves.
|
||||
#
|
||||
# What this is for, beyond tidiness: a dsh plugin installed as a bare
|
||||
# `git+https://…/repo.git` is resolved to a commit and pinned in the profile's
|
||||
# lockfile. With no tags, there is nothing for `dsh plugin update` to move to —
|
||||
# which is why updating appeared to do nothing and a reinstall was the only way
|
||||
# through. Tags are what make an update possible; this workflow is what creates
|
||||
# them.
|
||||
#
|
||||
# The work lives in `.gitea/scripts/release.sh` so it can be run and tested
|
||||
# without a runner (`bash .gitea/scripts/release.test.sh`, or `npm run
|
||||
# test:release`).
|
||||
#
|
||||
# The bump comes from the commit subjects in the range being released:
|
||||
# `feat` is a minor, `!:` (or an explicit `[major]`) is a major, and everything
|
||||
# else — `fix:`, `docs:`, prose — is a patch. `[skip release]` in a subject
|
||||
# suppresses the release for that push.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
# Manual runs, for the first release (no tags yet) or to publish a release
|
||||
# whose push failed after the tag was created.
|
||||
workflow_dispatch:
|
||||
|
||||
# One release at a time. Two pushes in quick succession would otherwise race to
|
||||
# tag the same commit and one of them would lose.
|
||||
concurrency:
|
||||
group: release-master
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
# The job token is what pushes the version commit, the tag, and the release.
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BRANCH: master
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Tags are the whole point, so the clone must carry them.
|
||||
fetch-depth: 0
|
||||
ref: master
|
||||
# Gitea mints a token for every job and exposes it as
|
||||
# secrets.GITEA_TOKEN; `permissions: contents: write` above is what
|
||||
# lets it push. No hand-made secret is needed.
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Decide whether this push releases
|
||||
id: decide
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="$(bash .gitea/scripts/release.sh last-tag)"
|
||||
range="$(bash .gitea/scripts/release.sh range)"
|
||||
echo "range=${range}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
verdict="$(bash .gitea/scripts/release.sh should-release "$range")"
|
||||
echo "release=${verdict}" >> "$GITHUB_OUTPUT"
|
||||
echo "last tag: ${tag:-none} range: ${range:-everything} release: ${verdict}"
|
||||
|
||||
- name: Work out the version and the notes
|
||||
if: steps.decide.outputs.release == 'yes'
|
||||
id: version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bump="$(bash .gitea/scripts/release.sh bump "${{ steps.decide.outputs.range }}")"
|
||||
version="$(bash .gitea/scripts/release.sh next-version "$bump")"
|
||||
bash .gitea/scripts/release.sh notes "${{ steps.decide.outputs.range }}" > /tmp/notes.md
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
echo "bump=${bump}" >> "$GITHUB_OUTPUT"
|
||||
echo "releasing v${version} (${bump})"
|
||||
cat /tmp/notes.md
|
||||
|
||||
- name: Bump, changelog, commit and tag
|
||||
if: steps.decide.outputs.release == 'yes'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "actions@notexpectedyet.com"
|
||||
|
||||
# Idempotency: a run that pushed the tag and then failed at the
|
||||
# release call must not fail here on the re-run.
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
echo "tag v${VERSION} already exists — publishing the release for it"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bash .gitea/scripts/release.sh set-version "$VERSION"
|
||||
bash .gitea/scripts/release.sh changelog-prepend "$VERSION" "$(date -u +%Y-%m-%d)" /tmp/notes.md
|
||||
git add package.json CHANGELOG.md
|
||||
# `[skip release]` is the guard that has to hold, because the script
|
||||
# is what decides. `[skip actions]` is the cheap extra: Gitea reads it
|
||||
# and can skip queueing a run for this commit at all.
|
||||
git commit -m "Release v${VERSION} [skip release] [skip actions]"
|
||||
git tag "v${VERSION}"
|
||||
git push origin "HEAD:${BRANCH}"
|
||||
git push origin "v${VERSION}"
|
||||
|
||||
- name: Publish the release
|
||||
if: steps.decide.outputs.release == 'yes'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
SERVER: ${{ github.server_url }}
|
||||
REPO: ${{ github.repository }}
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The body is built by an encoder rather than by string interpolation:
|
||||
# release notes are multi-line markdown and quoting them into JSON by
|
||||
# hand is a reliable way to publish a malformed release.
|
||||
python3 - <<'PY' > /tmp/release.json
|
||||
import json, os
|
||||
with open('/tmp/notes.md', encoding='utf-8') as handle:
|
||||
body = handle.read()
|
||||
version = os.environ['VERSION']
|
||||
print(json.dumps({
|
||||
'tag_name': 'v' + version,
|
||||
'name': 'v' + version,
|
||||
'body': body,
|
||||
'draft': False,
|
||||
'prerelease': False,
|
||||
}))
|
||||
PY
|
||||
|
||||
status="$(curl -sS -o /tmp/release-response.json -w '%{http_code}' \
|
||||
-X POST "${SERVER}/api/v1/repos/${REPO}/releases" \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary @/tmp/release.json)"
|
||||
|
||||
# 409 is "a release for this tag already exists": the job has nothing
|
||||
# left to do rather than something to report.
|
||||
if [ "$status" = "409" ]; then
|
||||
echo "v${VERSION} is already released"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$status" != "201" ] && [ "$status" != "200" ]; then
|
||||
echo "release creation failed (HTTP ${status})" >&2
|
||||
cat /tmp/release-response.json >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "published v${VERSION}"
|
||||
Reference in new issue
Block a user