CI / check (push) Skipped
CI / e2e (push) Skipped
main is protected (enable_push: false, empty whitelist), so @semantic-release/git's commit-back is rejected by the pre-receive hook -- and it would be rejected *after* the tag was pushed, leaving a tagged release the run then reports as failed. Found by trying to push this branch to main. Whitelisting the CI user was the alternative and is declined: it weakens a protection someone set deliberately and lets a bot push to main without the checks every human PR has to pass. So the release page is the changelog. The changelog plugin now writes a gitignored .release-notes.md, which exists only to carry the notes into gitea-release.sh without interpolating them into a shell command, and CHANGELOG.md is a signpost -- a file claiming to be a changelog while silently never updating is worse than no file. Tags are not protected, so the tag push is unaffected.
169 lines
7.3 KiB
YAML
169 lines
7.3 KiB
YAML
name: Release
|
|
|
|
# The sixth workflow, and the one that decides whether the other three
|
|
# run at all. On every push to main it reads the Conventional Commits
|
|
# since the last tag, and if any of them is releasable it writes the
|
|
# changelog, pushes the tag, and creates the Gitea release whose body is
|
|
# that changelog section. The publishing workflows are keyed on `v*`, so
|
|
# the tag push is what starts them.
|
|
#
|
|
# **Why the tag is pushed with PACKAGE_TOKEN and not the Actions token.**
|
|
# Gitea, like GitHub, does not start a workflow from a ref pushed by a
|
|
# workflow's own token (go-gitea#33123). The token is what decides this,
|
|
# not the workflow — so semantic-release is handed a repositoryUrl
|
|
# carrying a *user* PAT, and the resulting push is attributed to a person
|
|
# and triggers the `v*` workflows normally.
|
|
#
|
|
# That limitation is used deliberately in the bootstrap step below, where
|
|
# a tag that must *not* trigger anything is pushed with the Actions token
|
|
# instead.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
# Cutting a tag is not a thing to cancel halfway: a superseded run must
|
|
# finish, not be killed between `git push --tags` and the release POST.
|
|
concurrency:
|
|
group: release-main
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:24.04
|
|
env:
|
|
SERVER_URL: ${{ github.server_url }}
|
|
OWNER: ${{ github.repository_owner }}
|
|
REPO: ${{ github.repository }}
|
|
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
|
DEBIAN_FRONTEND: noninteractive
|
|
steps:
|
|
- name: System packages
|
|
run: |
|
|
set -eu
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends ca-certificates curl git jq
|
|
|
|
- name: Node toolchain
|
|
run: |
|
|
set -eu
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
apt-get install -y -qq --no-install-recommends nodejs
|
|
node --version
|
|
|
|
# By hand rather than actions/checkout, like the other five: that is
|
|
# a JS action and needs node inside the container before any step has
|
|
# installed it. The full history is required — semantic-release
|
|
# reads tags and walks commits, and a shallow clone silently makes
|
|
# every release look like the first one.
|
|
- name: Clone repo at this commit
|
|
run: |
|
|
set -eu
|
|
git clone --quiet \
|
|
"https://x-access-token:${PACKAGE_TOKEN}@${SERVER_URL#https://}/${REPO}.git" /src
|
|
# -B main rather than --detach, which the other five workflows
|
|
# use: semantic-release resolves the release branch and then
|
|
# pushes a commit and a tag to it, and a detached HEAD is a
|
|
# worse starting point for both than a local branch named after
|
|
# the one being released. Pinned to this commit, not to
|
|
# whatever main points at by the time the container started.
|
|
git -C /src checkout --quiet -B main "${{ github.sha }}"
|
|
git config --global --add safe.directory /src
|
|
git -C /src log --oneline -1
|
|
|
|
# Nothing currently pushes a `chore(release):` commit — main is a
|
|
# protected branch, so .releaserc.yml carries no @semantic-release/git
|
|
# and the release page is the changelog. This guard is kept for the
|
|
# day someone adds that plugin back: without it the commit-back is a
|
|
# push to the branch this workflow runs on, and the loop is a release
|
|
# per release. Six lines against that is cheap.
|
|
- name: Skip a changelog commit, if one ever exists
|
|
id: guard
|
|
working-directory: /src
|
|
run: |
|
|
set -eu
|
|
subject=$(git log -1 --format='%s')
|
|
case "$subject" in
|
|
"chore(release):"*)
|
|
echo "this is the release commit itself; nothing to do"
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
;;
|
|
*)
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
;;
|
|
esac
|
|
|
|
# semantic-release calls the first release of a repo with no tags
|
|
# 1.0.0, and offers no option to say otherwise. A floor tag is the
|
|
# only way to start at 0.0.1, so this creates one — once, ever.
|
|
#
|
|
# **It is pushed with the Actions token on purpose.** v0.0.0 is a
|
|
# floor, not a shipment: pushing it with a user PAT would start the
|
|
# Arch, Homebrew and Android workflows for a version that does not
|
|
# exist. The very limitation the header describes is what makes
|
|
# this inert.
|
|
- name: Seed the version floor
|
|
if: steps.guard.outputs.skip == 'false'
|
|
working-directory: /src
|
|
env:
|
|
ACTIONS_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
set -eu
|
|
git fetch --quiet --tags origin
|
|
|
|
if [ -n "$(git tag --list 'v[0-9]*')" ]; then
|
|
echo "floor already set; newest tag is $(git describe --tags --abbrev=0 --match 'v[0-9]*')"
|
|
exit 0
|
|
fi
|
|
|
|
# Prefer the Actions token because a ref it pushes starts no
|
|
# workflow, which is the whole point for a tag that is a floor
|
|
# rather than a shipment. Falling back to the PAT is safe
|
|
# rather than merely convenient: all four publishing workflows
|
|
# skip v0.0.0 explicitly, so the worst case is four jobs that
|
|
# start and immediately say there is nothing to build.
|
|
token="${ACTIONS_TOKEN:-$PACKAGE_TOKEN}"
|
|
[ -n "$ACTIONS_TOKEN" ] || echo "note: GITEA_TOKEN is unset; using the PAT"
|
|
|
|
echo "no v* tag exists — seeding v0.0.0 so the first release is 0.0.1"
|
|
git tag v0.0.0 "${{ github.sha }}"
|
|
git push --quiet \
|
|
"https://x-access-token:${token}@${SERVER_URL#https://}/${REPO}.git" \
|
|
refs/tags/v0.0.0
|
|
echo "seeded v0.0.0 at ${{ github.sha }}"
|
|
|
|
# Pinned rather than installed into the repo: this is a Go project
|
|
# and a package.json at its root invites the npm plugin and every
|
|
# tool that looks for one. conventional-changelog-conventionalcommits
|
|
# is in the list because both the analyzer and the notes generator
|
|
# name that preset and neither depends on it.
|
|
#
|
|
# **That preset is held at 9 and the reason is worth keeping.** At
|
|
# 10 it is silently incompatible with the writer that
|
|
# release-notes-generator@14 pulls in (^8): every release note comes
|
|
# out as a bare `## 0.0.1 (date)` heading with **no sections and no
|
|
# commits under it**, and nothing errors. The version would have
|
|
# been right, the tag would have been right, every job would have
|
|
# been green, and the release body would have been empty. Check the
|
|
# notes, not the exit code, before moving any of these.
|
|
- name: Run semantic-release
|
|
if: steps.guard.outputs.skip == 'false'
|
|
working-directory: /src
|
|
run: |
|
|
set -eu
|
|
git config user.name "yellowjacket-ci"
|
|
git config user.email "yj@yellowjacket.app"
|
|
|
|
npx --yes \
|
|
-p semantic-release@25 \
|
|
-p @semantic-release/commit-analyzer@13 \
|
|
-p @semantic-release/release-notes-generator@14 \
|
|
-p @semantic-release/changelog@7 \
|
|
-p @semantic-release/exec@7 \
|
|
-p conventional-changelog-conventionalcommits@9 \
|
|
semantic-release \
|
|
--repository-url "https://x-access-token:${PACKAGE_TOKEN}@${SERVER_URL#https://}/${REPO}.git"
|