The floor tag marks what has already been released, so tagging the
commit being pushed leaves nothing between the floor and HEAD --
semantic-release then correctly reports there is nothing to release.
That is what the first run did: it seeded v0.0.0 on the merge commit
itself and cut no release.
HEAD^ is the first parent, so on a merge commit it is main as it was
before the merge and everything the merge brought in is releasable.
The tag has been moved to 6fb7b5e by hand; this is so the next repo
never needs that.
183 lines
8.1 KiB
YAML
183 lines
8.1 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"
|
|
|
|
# **On the parent, not on HEAD.** The floor marks what has
|
|
# already been released, so tagging the commit being pushed
|
|
# leaves nothing between the floor and HEAD — semantic-release
|
|
# then correctly reports there is nothing to release, which is
|
|
# exactly what the first run of this workflow did. HEAD^ is the
|
|
# first parent, so on the merge commit this fires for it is main
|
|
# as it was before the merge, and everything the merge brought
|
|
# in is releasable.
|
|
floor=$(git rev-parse "${{ github.sha }}^" 2>/dev/null || true)
|
|
if [ -z "$floor" ]; then
|
|
echo "HEAD has no parent, so no commit can precede the floor" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "no v* tag exists — seeding v0.0.0 so the first release is 0.0.1"
|
|
git tag v0.0.0 "$floor"
|
|
git push --quiet \
|
|
"https://x-access-token:${token}@${SERVER_URL#https://}/${REPO}.git" \
|
|
refs/tags/v0.0.0
|
|
echo "seeded v0.0.0 at $floor (parent of ${{ 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"
|