From a6026e6b914d8c6beef4cdc7a0f15061600008ff Mon Sep 17 00:00:00 2001 From: AuraMindNest Date: Tue, 2 Jun 2026 15:13:09 -0600 Subject: [PATCH] #77 : Release tagging workflow. --- .github/README.md | 1 + .github/workflows/release.yml | 81 +++++++++++++++++++++++++++++++++++ docs/deployment-runbook.md | 42 ++++++++++++++++++ pyproject.toml | 2 +- uv.lock | 2 +- 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/README.md b/.github/README.md index 1fbe194..d90b67f 100644 --- a/.github/README.md +++ b/.github/README.md @@ -14,6 +14,7 @@ GitHub Actions and CI/CD helpers for this repository. |------|------| | [`workflows/ci.yml`](workflows/ci.yml) | Umbrella **CI** — runs on push/PR to `main` and `develop` | | [`workflows/cd.yml`](workflows/cd.yml) | **Deploy** — after CI succeeds on `develop` (staging); no `workflow_dispatch` trigger | +| [`workflows/release.yml`](workflows/release.yml) | **Release** — manual `workflow_dispatch` only; tags `main` from `pyproject.toml` (`v`) and creates a GitHub Release with Weblate compatibility metadata | | [`workflows/ci-lint.yml`](workflows/ci-lint.yml) | Lint and format (prek) | | [`workflows/ci-test.yml`](workflows/ci-test.yml) | Unit tests and coverage | | [`workflows/ci-package.yml`](workflows/ci-package.yml) | Build and package checks | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8ecfe3a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2026 William Jin +# +# SPDX-License-Identifier: BSL-1.0 + +name: Release + +# Standalone tagging and GitHub Releases. +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: main + fetch-depth: 0 + + - name: Parse versions from pyproject.toml + id: versions + run: | + set -euo pipefail + python3 <<'PY' >> "$GITHUB_OUTPUT" + import re + import tomllib + from pathlib import Path + + data = tomllib.loads(Path("pyproject.toml").read_text()) + plugin_version = data["project"]["version"] + weblate_version = None + for dep in data["project"]["dependencies"]: + match = re.fullmatch(r"Weblate\[all\]==(.+)", dep) + if match: + weblate_version = match.group(1) + break + if not weblate_version: + raise SystemExit("Weblate[all]== pin not found in pyproject.toml") + print(f"plugin_version={plugin_version}") + print(f"weblate_version={weblate_version}") + print(f"tag=v{plugin_version}") + PY + + - name: Fail if tag already exists + run: | + set -euo pipefail + tag="${{ steps.versions.outputs.tag }}" + if git ls-remote --tags origin "refs/tags/${tag}" | grep -q .; then + echo "Tag ${tag} already exists on origin" + exit 1 + fi + + - name: Configure git for tag push + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Create and push tag + run: | + set -euo pipefail + tag="${{ steps.versions.outputs.tag }}" + weblate_version="${{ steps.versions.outputs.weblate_version }}" + git tag -a "${tag}" -m "Release ${tag} (Weblate ${weblate_version})" + git push origin "${tag}" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + tag="${{ steps.versions.outputs.tag }}" + weblate_version="${{ steps.versions.outputs.weblate_version }}" + gh release create "${tag}" \ + --title "${tag} (Weblate ${weblate_version})" \ + --generate-notes \ + --notes "Built against Weblate ${weblate_version}." diff --git a/docs/deployment-runbook.md b/docs/deployment-runbook.md index 6a27cac..5997d71 100644 --- a/docs/deployment-runbook.md +++ b/docs/deployment-runbook.md @@ -212,6 +212,48 @@ The full pipeline (`cd.yml`) triggers on a successful CI run against `develop`: Concurrency is locked per branch (`cancel-in-progress: false`) so deploys never overlap. +## Release tagging + +Standalone GitHub Actions workflow ([`release.yml`](../.github/workflows/release.yml)). Run it only when you want to publish a version tag and GitHub Release. + +### When to run + +Use **Actions → Release → Run workflow** whenever the current `main` commit should be tagged. Typical cases: + +- After you are satisfied with what is on `main` (deploy or not) +- When `pyproject.toml` on `main` already has the intended `version` and Weblate pin + +The workflow does not check deploy status or server health. + +### What it does + +1. Checks out `main` and reads [`pyproject.toml`](../pyproject.toml): + - Plugin version: `[project].version` (e.g. `1.0.0`) + - Weblate pin: `Weblate[all]==…` (e.g. `2026.5`) +2. Fails if tag `v` already exists on `origin` (prevents duplicate releases) +3. Creates annotated tag `v` on current `main` HEAD and pushes it +4. Creates a GitHub Release with auto-generated notes, title `v (Weblate )`, and body noting Weblate compatibility + +Use the release title and body to verify which Weblate version the tagged tree was built against. + +### Prerequisites + +- `version` in `pyproject.toml` on `main` must be the release you intend (bump on `develop` and promote, or commit on `main`, before running) +- Tag `v` must not already exist + +### Failure modes + +| Failure | Likely cause | +|---------|----------------| +| Tag already exists | Re-ran without bumping `version` in `pyproject.toml` | +| Wrong release contents | `main` HEAD did not include the expected `pyproject.toml` | +| `gh release create` failed | Permissions or network; check whether the tag was pushed and finish the release manually on GitHub | + +### Important + +- Tagging and GitHub Releases **do not deploy** or change servers +- Deleting a GitHub Release **does not roll back** a deploy; reverting production is a separate server/git operation (see deploy sections above) + ## Troubleshooting ### Container stays unhealthy diff --git a/pyproject.toml b/pyproject.toml index 198b87a..649f7ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ license-files = ["LICENSE"] name = "cppa-weblate-plugin" readme = "README.md" requires-python = ">=3.12" -version = "0.1.0" +version = "1.0.0" [project.optional-dependencies] dev = [ diff --git a/uv.lock b/uv.lock index b21a62d..2ce6056 100644 --- a/uv.lock +++ b/uv.lock @@ -701,7 +701,7 @@ dependencies = [ ] name = "cppa-weblate-plugin" source = {editable = "."} -version = "0.1.0" +version = "1.0.0" [package.dev-dependencies] dev = [