d29d400dd3
Files changed: - .gitea/workflows/release.yml - AGENTS.md - CHANGES.md - DEVELOPMENT.md - README.md - VERSION - docs/version-model.md - instructions/dev/version-parts.md - tools/CONTRACT.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/migrate_cmd.py - tools/chemenu/commands/version_cmd.py - tools/chemenu/kb_state.py - tools/chemenu/tests/test_dist_cmd.py - tools/chemenu/tests/test_docs_verify.py - tools/chemenu/tests/test_doctor.py - tools/chemenu/tests/test_migrate_cmd.py - tools/chemenu/tests/test_version_cmd.py - tools/chemenu/version.py
166 lines
6.6 KiB
YAML
166 lines
6.6 KiB
YAML
# Publish a release when VERSION moves on main.
|
|
#
|
|
# The release artifact is exactly a `dist export` tree, packed with a top-level
|
|
# directory: unpack it, run instructions/setup-instance.md, and there is a
|
|
# working wiki instance - no checkout of this repo required. CI already proved
|
|
# that path works before this workflow ever runs.
|
|
#
|
|
# The tag is created here, by CI, and never by an agent: AGENTS.md invariant 5
|
|
# ("never call raw git commit/push") stays intact because nothing in a session
|
|
# has to tag anything.
|
|
#
|
|
# Auth is `${{ gitea.token }}` - the short-lived per-job token this Gitea
|
|
# instance issues - not a 1Password secret. Nothing here reaches outside the
|
|
# instance, so the Zero-Trust secret path that the container-build workflows
|
|
# use has nothing to carry.
|
|
|
|
name: Release
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- VERSION
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: linux-docker
|
|
container:
|
|
image: debian:trixie-slim
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
WIKITOOL_SESSION_ID: release-${{ github.run_id }}
|
|
WIKI_TRACE_DIR: /tmp/wikitool-trace
|
|
BUILD_DIR: /tmp/build
|
|
# The address a *reader* uses. `github.server_url` is whatever the runner
|
|
# registered against (an internal one here), which is right for the API
|
|
# call below and wrong for a URL baked into every distributed instance.
|
|
PUBLIC_BASE_URL: https://gitea.nehmer.net
|
|
|
|
steps:
|
|
- name: System dependencies
|
|
# `nodejs` is for act_runner, not for us - see the note in ci.yml.
|
|
run: |
|
|
set -eu
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends \
|
|
python3 python3-venv git nodejs ripgrep ca-certificates curl jq tar gzip
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Tool environment
|
|
run: |
|
|
set -eu
|
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
python3 -m venv tools/.venv
|
|
tools/.venv/bin/pip install --quiet --upgrade pip
|
|
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
|
|
|
- name: Resolve the version and refuse to re-release it
|
|
id: version
|
|
env:
|
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
TOKEN: ${{ gitea.token }}
|
|
run: |
|
|
set -eu
|
|
version="$(cat VERSION | tr -d '[:space:]')"
|
|
|
|
# A running candidate (`X.Y.Z-beta.N`) is never released - betas are
|
|
# a dev-checkout state, not a distributed one (see
|
|
# instructions/dev/version-parts.md). This guard sits *before* the
|
|
# API query below: without it, every `version bump` on a candidate
|
|
# would push VERSION and trigger a wasted round-trip against the
|
|
# releases API for a tag that was never going to be created. Ending
|
|
# the job cleanly here (not `exit 1`) is what keeps a beta bump a
|
|
# normal, unremarkable push rather than a failing CI run - skipping
|
|
# every later step is what "cleanly" means in Actions: mark this one
|
|
# skip and gate the rest on it.
|
|
case "$version" in
|
|
*-beta.*)
|
|
echo "VERSION is a running candidate (${version}) - nothing to release. Skipping."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
;;
|
|
esac
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
|
|
tag="v${version}"
|
|
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
|
|
|
status="$(curl -s -o /dev/null -w '%{http_code}' \
|
|
-H "Authorization: token ${TOKEN}" "${API}/releases/tags/${tag}")"
|
|
if [ "$status" = "200" ]; then
|
|
echo "Release ${tag} already exists. VERSION was touched without being raised;"
|
|
echo 'bump it with `tools/wikitool version bump` instead of re-releasing.'
|
|
exit 1
|
|
fi
|
|
echo "No release ${tag} yet - proceeding."
|
|
|
|
- name: Release notes from CHANGES.md
|
|
# `version notes` fails when the changelog has no entry for this
|
|
# version, which is the last place that mistake can still be caught.
|
|
if: steps.version.outputs.skip != 'true'
|
|
run: |
|
|
set -eu
|
|
tools/wikitool docs verify
|
|
tools/wikitool version notes > /tmp/release-notes.md
|
|
cat /tmp/release-notes.md
|
|
|
|
- name: Build the distribution tarball
|
|
id: build
|
|
if: steps.version.outputs.skip != 'true'
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
TAG: ${{ steps.version.outputs.tag }}
|
|
run: |
|
|
set -eu
|
|
name="chemenu-stack-${VERSION}"
|
|
mkdir -p "$BUILD_DIR"
|
|
tools/wikitool dist export "${BUILD_DIR}/${name}" \
|
|
--source-repo "${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}" \
|
|
--source-commit "${GITHUB_SHA}" \
|
|
--release-url "${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}" \
|
|
--update-url "${PUBLIC_BASE_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/latest"
|
|
|
|
tar -czf "${BUILD_DIR}/${name}.tar.gz" -C "$BUILD_DIR" "$name"
|
|
( cd "$BUILD_DIR" && sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256" )
|
|
cat "${BUILD_DIR}/${name}.tar.gz.sha256"
|
|
echo "name=${name}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Publish the release
|
|
if: steps.version.outputs.skip != 'true'
|
|
env:
|
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
TOKEN: ${{ gitea.token }}
|
|
TAG: ${{ steps.version.outputs.tag }}
|
|
NAME: ${{ steps.build.outputs.name }}
|
|
run: |
|
|
set -eu
|
|
# Creating the release creates the tag, pinned to this commit.
|
|
payload="$(jq -n \
|
|
--arg tag "$TAG" \
|
|
--arg target "$GITHUB_SHA" \
|
|
--arg name "$TAG" \
|
|
--rawfile body /tmp/release-notes.md \
|
|
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body,
|
|
draft: false, prerelease: false}')"
|
|
|
|
release="$(curl -sS -f -X POST "${API}/releases" \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload")"
|
|
id="$(printf '%s' "$release" | jq -r '.id')"
|
|
echo "Created release ${TAG} (id ${id})."
|
|
|
|
for asset in "${NAME}.tar.gz" "${NAME}.tar.gz.sha256"; do
|
|
curl -sS -f -X POST "${API}/releases/${id}/assets?name=${asset}" \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-F "attachment=@${BUILD_DIR}/${asset}" > /dev/null
|
|
echo "Uploaded ${asset}."
|
|
done
|
|
|
|
echo "Done: ${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
|