yams/.github/workflows/release-candidate.yml
trvon 438f8a8a7b
ci(release): refuse stable releases from non-version refs
A stable workflow_dispatch took the release tag from GITHUB_REF_NAME
without checking it, so dispatching from main published a release
tagged main. Require refs/tags/v* or refs/tags/yams-v*.

Fetch main and experimental with explicit refspecs in the candidate
preflight so a tag named main cannot shadow the branch, and drop the
workflow_run trust path from draft reconciliation now that the workflow
is dispatch-only.
2026-09-22 12:18:20 -06:00

121 lines
3.9 KiB
YAML

# yamllint disable rule:line-length
name: Release Candidate
"on":
push:
branches:
- experimental
pull_request:
branches:
- main
workflow_dispatch: {}
permissions:
contents: read
pull-requests: read
concurrency:
group: release-candidate-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
preview:
name: Validate promotion and preview release
if: >-
github.event_name != 'pull_request' ||
(github.head_ref == 'experimental' &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout immutable candidate
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
submodules: false
- name: Resolve immutable promotion range
id: refs
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_SHA: ${{ github.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
run: |
set -euo pipefail
git fetch --force origin +refs/heads/main:refs/remotes/origin/main +refs/heads/experimental:refs/remotes/origin/experimental --tags
base_sha="$(git rev-parse origin/main)"
if [ "$EVENT_NAME" = "pull_request" ]; then
candidate_sha="$PR_HEAD_SHA"
elif [ "$EVENT_NAME" = "push" ]; then
candidate_sha="$EVENT_SHA"
else
candidate_sha="$(git rev-parse origin/experimental)"
fi
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
echo "candidate_sha=$candidate_sha" >> "$GITHUB_OUTPUT"
- name: Validate release candidate invariants
shell: bash
env:
BASE_SHA: ${{ steps.refs.outputs.base_sha }}
CANDIDATE_SHA: ${{ steps.refs.outputs.candidate_sha }}
run: |
set -euo pipefail
python3 scripts/ci/validate_release_candidate.py \
--root . \
--base "$BASE_SHA" \
--candidate "$CANDIDATE_SHA" \
--json-output release-candidate.json \
--markdown-output release-candidate.md
cat release-candidate.md >> "$GITHUB_STEP_SUMMARY"
- name: Prepare immutable local experimental branch
shell: bash
env:
CANDIDATE_SHA: ${{ steps.refs.outputs.candidate_sha }}
run: |
set -euo pipefail
git checkout --detach "$CANDIDATE_SHA"
git branch --force experimental "$CANDIDATE_SHA"
- name: Generate Release Please dry-run preview
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
npx --yes release-please@17.11.2 release-pr \
--repo-url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--target-branch=experimental \
--config-file=release-please-config.json \
--manifest-file=.release-please-manifest.json \
--token="$GITHUB_TOKEN" \
--local \
--local-path=. \
--dry-run 2>&1 | tee release-please-preview.log
- name: Add Release Please preview to summary
if: always()
shell: bash
run: |
{
echo "## Release Please dry-run"
echo
echo '```text'
sed -E 's/\x1B\[[0-9;]*[mK]//g' release-please-preview.log 2>/dev/null || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload release-candidate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: release-candidate-${{ steps.refs.outputs.candidate_sha }}
path: |
release-candidate.json
release-candidate.md
release-please-preview.log
if-no-files-found: warn
retention-days: 14