docs(release): add 1.19.0-beta.3 notes #260
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: CI | |
| permissions: {} | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - '**' | |
| tags: | |
| - '*' | |
| pull_request: | |
| branches: | |
| - vibe | |
| - main | |
| - master | |
| concurrency: | |
| group: "${{ github.workflow }}-${{ github.ref }}" | |
| cancel-in-progress: true | |
| jobs: | |
| release-candidate: | |
| name: Release candidate | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_commit: ${{ steps.release-candidate.outputs.release_commit }} | |
| release_version: ${{ steps.release-candidate.outputs.release_version }} | |
| should_release: ${{ steps.release-candidate.outputs.should_release }} | |
| tag_name: ${{ steps.release-candidate.outputs.tag_name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve release candidate | |
| id: release-candidate | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| python <<'PY' | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| VERSION_RE = re.compile(r"^v?([0-9]+\.[0-9]+\.[0-9]+)([-.][0-9A-Za-z.-]+)?$") | |
| REPO = os.environ["GITHUB_REPOSITORY"] | |
| def run(*args, check=True): | |
| return subprocess.run(args, check=check, text=True, capture_output=True).stdout.strip() | |
| def output(**values): | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh: | |
| for key, value in values.items(): | |
| print(f"{key}={value}", file=fh) | |
| def skip(reason): | |
| print(f"::notice::{reason}") | |
| output(should_release="false", tag_name="", release_version="", release_commit="") | |
| sys.exit(0) | |
| def version_key(tag): | |
| match = VERSION_RE.match(tag) | |
| if not match: | |
| return None | |
| major, minor, patch = (int(part) for part in match.group(1).split(".")) | |
| suffix = (match.group(2) or "").lstrip("-.").lower() | |
| suffix_parts = re.split(r"[-.]", suffix) if suffix else [] | |
| suffix_num = next((int(part) for part in suffix_parts if part.isdigit()), 0) | |
| if not suffix or "stable" in suffix_parts: | |
| suffix_rank = 40 | |
| elif "rc" in suffix_parts: | |
| suffix_rank = 30 | |
| elif "beta" in suffix_parts: | |
| suffix_rank = 20 | |
| elif "alpha" in suffix_parts: | |
| suffix_rank = 10 | |
| else: | |
| suffix_rank = 5 | |
| return (major, minor, patch, suffix_rank, suffix_num, suffix) | |
| def is_prerelease_key(key): | |
| return key[3] < 40 | |
| def release_notes_for(tag, commit): | |
| match = VERSION_RE.match(tag) | |
| if not match: | |
| return None | |
| release_version = tag[1:] if tag.startswith("v") else tag | |
| for candidate in ( | |
| Path("release_notes") / f"{release_version}.md", | |
| Path("release_notes") / f"{tag}.md", | |
| ): | |
| notes_path = candidate.as_posix() | |
| if subprocess.run( | |
| ["git", "cat-file", "-e", f"{commit}:{notes_path}"], | |
| text=True, | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ).returncode == 0: | |
| return notes_path | |
| return None | |
| event_name = os.environ["EVENT_NAME"] | |
| if event_name not in {"push", "workflow_dispatch"}: | |
| skip("Release gate only applies to push and manual workflow_dispatch events.") | |
| run("git", "fetch", "--force", "--tags") | |
| release_tags = set(filter(None, run( | |
| "gh", "api", "--paginate", f"repos/{REPO}/releases", "--jq", ".[].tag_name" | |
| ).splitlines())) | |
| release_versions = [version_key(tag) for tag in release_tags] | |
| release_versions = [key for key in release_versions if key is not None] | |
| candidate_tags = run("git", "tag", "--list").splitlines() | |
| valid_candidates = [] | |
| for tag in candidate_tags: | |
| if tag.startswith("v"): | |
| print(f"Skipping {tag}: release source tags must be v-less.") | |
| continue | |
| key = version_key(tag) | |
| if key is None: | |
| continue | |
| legacy_tag = f"v{tag}" | |
| existing_release_tag = next( | |
| (candidate for candidate in (tag, legacy_tag) if candidate in release_tags), | |
| None, | |
| ) | |
| if existing_release_tag is not None: | |
| print(f"Skipping {tag}: release already exists as {existing_release_tag}.") | |
| continue | |
| comparable_releases = [ | |
| release_key for release_key in release_versions | |
| if is_prerelease_key(release_key) == is_prerelease_key(key) | |
| ] | |
| current_release_version = max(comparable_releases) if comparable_releases else None | |
| if current_release_version is not None and key <= current_release_version: | |
| print(f"Skipping {tag}: not newer than the current release.") | |
| continue | |
| release_commit = run("git", "rev-list", "-n", "1", tag) | |
| if event_name == "push": | |
| head_commit = run("git", "rev-parse", "HEAD") | |
| if release_commit != head_commit: | |
| print(f"Skipping {tag}: tag commit {release_commit} is not the pushed HEAD.") | |
| continue | |
| if subprocess.run( | |
| ["git", "merge-base", "--is-ancestor", release_commit, "HEAD"], | |
| text=True, | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ).returncode != 0: | |
| print(f"Skipping {tag}: tag commit {release_commit} is not reachable from this run ref.") | |
| continue | |
| notes_file = release_notes_for(tag, release_commit) | |
| if notes_file is None: | |
| print(f"Skipping {tag}: no exact matching release_notes file in the tag commit.") | |
| continue | |
| # Preserve the exact source tag. Release titles and artifact names may | |
| # use a display-only v prefix, but the GitHub Release must stay attached | |
| # to the existing tag instead of asking the API to synthesize another. | |
| valid_candidates.append((key, tag, notes_file, release_commit)) | |
| if not valid_candidates: | |
| skip("No unreleased newer tag with exact matching release notes was found.") | |
| _, tag_name, notes_file, release_commit = max(valid_candidates, key=lambda item: item[0]) | |
| release_version = tag_name[1:] if tag_name.startswith("v") else tag_name | |
| print(f"Release candidate: {tag_name} ({release_commit}) using {notes_file}") | |
| output( | |
| should_release="true", | |
| tag_name=tag_name, | |
| release_version=release_version, | |
| release_commit=release_commit, | |
| ) | |
| PY | |
| build-windows: | |
| name: Windows | |
| needs: | |
| - release-candidate | |
| if: github.event_name != 'push' || needs.release-candidate.outputs.should_release == 'true' | |
| permissions: | |
| actions: read | |
| contents: read | |
| uses: ./.github/workflows/ci-windows.yml | |
| with: | |
| build_only: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| build_tests: ${{ needs.release-candidate.outputs.should_release != 'true' }} | |
| release_commit: ${{ needs.release-candidate.outputs.release_commit || github.sha }} | |
| release_version: ${{ needs.release-candidate.outputs.release_version || '0.0.0' }} | |
| release_tag: ${{ needs.release-candidate.outputs.tag_name }} | |
| release_artifact_retention_days: ${{ needs.release-candidate.outputs.should_release == 'true' && 14 || 1 }} | |
| symbol_product_name: Vibeshine | |
| publish_symbols: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| symbol_release_draft: true | |
| symbol_release_prefix: shine | |
| require_truehdr_runtime: ${{ needs.release-candidate.outputs.should_release == 'true' }} | |
| secrets: | |
| symbol_token: ${{ secrets.SYMBOL_TOKEN }} | |
| truehdr_runtime_token: ${{ secrets.TRUEHDR_RUNTIME_TOKEN }} | |
| awaiting-signing: | |
| name: Awaiting manual signing | |
| if: needs.release-candidate.outputs.should_release == 'true' | |
| needs: | |
| - release-candidate | |
| - build-windows | |
| permissions: {} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Record next release action | |
| env: | |
| TAG_NAME: ${{ needs.release-candidate.outputs.tag_name }} | |
| BUILD_RUN_ID: ${{ github.run_id }} | |
| run: | | |
| { | |
| echo "## Release build complete" | |
| echo | |
| echo "Unsigned release artifacts are retained for 14 days." | |
| echo "When ready to approve SignPath, run [**Sign and publish release**](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/workflows/sign-release.yml)." | |
| echo | |
| echo "- \`release_tag\`: \`${TAG_NAME}\`" | |
| echo "- Optional recovery \`build_run_id\`: \`${BUILD_RUN_ID}\`" | |
| echo | |
| echo "Leave \`build_run_id\` empty; it is an optional recovery override. The signing workflow automatically selects the newest valid successful CI tag build for this exact tag and commit, then verifies its retained artifacts before submitting either signing request." | |
| } >> "${GITHUB_STEP_SUMMARY}" |