From 3aa4df18cb8180275d9cfb5b64e35358c787b79a Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 27 Apr 2026 20:05:29 +0300 Subject: [PATCH] feat: add workflow for drafting release notes and extract changelog --- .github/scripts/extract-changelog.py | 57 ++++++++++++++++++ .github/workflows/draft-release.yml | 87 +++++++++++++++++++++++++++ .github/workflows/release-drafter.yml | 16 ----- 3 files changed, 144 insertions(+), 16 deletions(-) create mode 100755 .github/scripts/extract-changelog.py create mode 100644 .github/workflows/draft-release.yml delete mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/scripts/extract-changelog.py b/.github/scripts/extract-changelog.py new file mode 100755 index 000000000..65d45843a --- /dev/null +++ b/.github/scripts/extract-changelog.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Extract the latest version's release notes from docs/changelog/index.md. + +Outputs JSON with "version", "date", and "body" fields for use in GitHub Actions. +""" + +import json +import re +import sys +from pathlib import Path + +CHANGELOG_PATH = Path("docs/changelog/index.md") + + +def extract_latest() -> dict | None: + """Extract the latest release entry from the changelog.""" + text = CHANGELOG_PATH.read_text(encoding="utf-8") + + # Match the first version heading: ### VERSION DATE { id="..." } + pattern = re.compile( + r'^###\s+(\S+)\s+(.*?)\s*\{[^}]*\}\s*$', + re.MULTILINE, + ) + + match = pattern.search(text) + if not match: + return None + + version = match.group(1) + date = match.group(2).strip() + start = match.end() + + # Find the next version heading or the "---" separator + next_pattern = re.compile( + r'^###\s+\S+\s+|^---\s*$', + re.MULTILINE, + ) + next_match = next_pattern.search(text, start) + end = next_match.start() if next_match else len(text) + + body = text[start:end].strip() + + return {"version": version, "date": date, "body": body} + + +def main() -> None: + entry = extract_latest() + if not entry: + print("No version entry found in changelog", file=sys.stderr) + sys.exit(1) + + json.dump(entry, sys.stdout, ensure_ascii=False) + print() # trailing newline + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml new file mode 100644 index 000000000..350fc9265 --- /dev/null +++ b/.github/workflows/draft-release.yml @@ -0,0 +1,87 @@ +name: Draft Release Notes + +on: + push: + branches: + - "main" + paths: + - "docs/changelog/index.md" + tags: + - "[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + sync-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Extract latest changelog entry + id: changelog + run: | + python3 .github/scripts/extract-changelog.py > /tmp/changelog.json + + - name: Parse version and date + id: meta + run: | + VERSION=$(python3 -c "import json; print(json.load(open('/tmp/changelog.json'))['version'])") + DATE=$(python3 -c "import json; print(json.load(open('/tmp/changelog.json'))['date'])") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "date=$DATE" >> "$GITHUB_OUTPUT" + + - name: Set tag and draft flag + id: flags + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" + echo "draft=false" >> "$GITHUB_OUTPUT" + echo "make_latest=true" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ steps.meta.outputs.version }}" >> "$GITHUB_OUTPUT" + echo "draft=true" >> "$GITHUB_OUTPUT" + echo "make_latest=false" >> "$GITHUB_OUTPUT" + fi + + - name: Write release notes to file + run: | + python3 -c " + import json + with open('/tmp/changelog.json') as f: + data = json.load(f) + with open('/tmp/release-body.md', 'w') as f: + f.write(data['body']) + " + + - name: Create or update GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${{ steps.flags.outputs.tag }}" + TITLE="${{ steps.meta.outputs.version }} – ${{ steps.meta.outputs.date }}" + NOTES_FILE="/tmp/release-body.md" + DRAFT="${{ steps.flags.outputs.draft }}" + MAKE_LATEST="${{ steps.flags.outputs.make_latest }}" + + ARGS=( + --title "$TITLE" + --notes-file "$NOTES_FILE" + ) + + if [[ "$DRAFT" == "true" ]]; then + ARGS+=(--draft) + fi + + if [[ "$MAKE_LATEST" == "true" ]]; then + ARGS+=(--latest) + fi + + if gh release view "$TAG" &>/dev/null; then + echo "Release $TAG exists – editing" + gh release edit "$TAG" "${ARGS[@]}" + else + echo "Release $TAG does not exist – creating" + gh release create "$TAG" "${ARGS[@]}" + fi diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 6eaab8566..000000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Release Drafter - -on: - push: - branches: - - "main" - workflow_dispatch: - -permissions: {} - -jobs: - draft-release: - permissions: - contents: write - pull-requests: write - uses: mkdocs-ng/.github/.github/workflows/release-drafter.yml@main