Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 92 additions & 33 deletions .github/workflows/promote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,50 @@ jobs:
git tag "$STABLE_TAG"
git push origin "$STABLE_TAG"

# ORDER MATTERS. Everything that actually publishes the release runs before the
# main-sync below. The stable tag is already pushed at this point, so the build
# and the announcement depend on nothing the sync produces — while the sync is a
# PR round-trip against a remote that fails for reasons of its own. It used to
# run first, and every one of its failures silently skipped both steps under it:
# v1.7.0, v1.8.0 and v1.9.0 all shipped without an automated build (dispatched by
# hand minutes later) and without a #releases announcement.
- name: Trigger build workflow
env:
GH_TOKEN: ${{ secrets.OPENSCREEN_RELEASE_TOKEN }}
STABLE_TAG: ${{ steps.version.outputs.stable_tag }}
run: |
set -euo pipefail
# GITHUB_TOKEN tag pushes don't fire build.yml in this setup, dispatch it.
#
# --ref pins the build to the stable tag (the frozen release-branch tip), which
# is also what makes running this before the main-sync correct: the tag already
# points at the released snapshot, so checkout gets the matching package.json +
# code whatever main looks like, and signing/notarization is enabled (tag has
# no '-').
gh workflow run build.yml \
--ref "${STABLE_TAG}" \
-f release_tag="${STABLE_TAG}" \
-f arch=both \
--repo "$GITHUB_REPOSITORY"

- name: Announce stable on Discord
if: success()
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
DISCORD_RELEASE_CHANNEL_ID: ${{ vars.DISCORD_RELEASE_CHANNEL_ID }}
GITHUB_TOKEN: ${{ secrets.OPENSCREEN_RELEASE_TOKEN }}
STABLE_TAG: ${{ steps.version.outputs.stable_tag }}
RC_TAG: ${{ steps.version.outputs.rc_tag }}
EXTRA: ${{ inputs.release_notes_extra }}
KIND: stable
run: node .github/scripts/discord-release-announce.mjs

# Bookkeeping, not publishing: the release is already out by now. Allowed to fail
# so a stuck PR never masks a successful release — the summary reports it and it
# is trivially redone by hand.
- name: Merge release branch into main
id: sync_main
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.OPENSCREEN_RELEASE_TOKEN }}
STABLE_VERSION: ${{ steps.version.outputs.stable_version }}
Expand All @@ -127,44 +170,53 @@ jobs:
--body "Sync main with the released snapshot (RC + cherry-picked bugfixes + version bump). Rebase-merged via PAT; bypass applies because EtienneLescot is a ruleset bypass actor." \
--repo "$GITHUB_REPOSITORY" || echo "(PR already exists — skipping)"
PR_NUMBER=$(gh pr list --head "${BRANCH}-sync" --state open --json number -q '.[0].number' --repo "$GITHUB_REPOSITORY")
if [[ -n "$PR_NUMBER" ]]; then
gh pr merge "$PR_NUMBER" --rebase --delete-branch --admin \
--repo "$GITHUB_REPOSITORY"
if [[ -z "$PR_NUMBER" ]]; then
# L'échec de `gh pr create` ci-dessus est converti en echo, donc on
# arrive ici aussi bien quand la PR existait déjà que quand sa création
# a échoué pour une autre raison. Sortir en 0 dans le second cas
# reportait sync_main en succès alors que le test d'ascendance du haut
# avait justement établi que main ne contient pas la branche, et
# l'avertissement de fin sautait. On re-teste plutôt que de supposer :
# quelqu'un a pu merger la synchro à la main entre-temps.
git fetch origin main "$BRANCH"
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main; then
echo "origin/${BRANCH} is already an ancestor of origin/main — nothing to merge."
exit 0
fi
echo "::error::No open sync PR exists and main does not contain ${BRANCH}."
exit 1
fi
# GitHub computes mergeability asynchronously. Merging ~3s after creating the
# PR raced that computation every time: v1.9.0 got "This branch can't be
# rebased" 2.9s after create, v1.8.0 "has merge conflicts" after 1.7s — and
# both merged by hand, unchanged, minutes later. Wait for a real verdict.
for _ in $(seq 1 30); do
STATE=$(gh pr view "$PR_NUMBER" --json mergeStateStatus -q .mergeStateStatus \
--repo "$GITHUB_REPOSITORY" 2>/dev/null || echo UNKNOWN)
echo "mergeStateStatus=${STATE}"
if [[ "$STATE" != "UNKNOWN" ]]; then break; fi
sleep 5
done
# Retry anyway: leaving UNKNOWN is necessary, not sufficient. MERGED must be
# set by a successful merge — a bare `&& break` loop exits 0 after five
# failures and reports a merge that never happened.
MERGED=0
for _ in $(seq 1 5); do
if gh pr merge "$PR_NUMBER" --rebase --delete-branch --admin \
--repo "$GITHUB_REPOSITORY"; then
MERGED=1
break
fi
sleep 15
done
if [[ "$MERGED" -ne 1 ]]; then
echo "::error::Could not rebase-merge sync PR #${PR_NUMBER} into main after 5 attempts. The release itself is published; merge it by hand."
exit 1
fi
# The release branch itself was already used to publish and contains frozen
# history — leave it in place for forensics. A v1.6.0 release branch should
# never be deleted until the next major cuts over.

- name: Trigger build workflow
env:
GH_TOKEN: ${{ secrets.OPENSCREEN_RELEASE_TOKEN }}
STABLE_TAG: ${{ steps.version.outputs.stable_tag }}
run: |
set -euo pipefail
# GITHUB_TOKEN tag pushes don't fire build.yml in this setup, dispatch it.
#
# --ref pins the build to the stable tag (the frozen release-branch tip). The
# prior main-merge step usually leaves main at the stable version already, but
# relying on that is fragile; building the tag guarantees checkout has the
# matching package.json + code and enables signing/notarization (tag has no '-').
gh workflow run build.yml \
--ref "${STABLE_TAG}" \
-f release_tag="${STABLE_TAG}" \
-f arch=both \
--repo "$GITHUB_REPOSITORY"

- name: Announce stable on Discord
if: success()
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
DISCORD_RELEASE_CHANNEL_ID: ${{ vars.DISCORD_RELEASE_CHANNEL_ID }}
GITHUB_TOKEN: ${{ secrets.OPENSCREEN_RELEASE_TOKEN }}
STABLE_TAG: ${{ steps.version.outputs.stable_tag }}
RC_TAG: ${{ steps.version.outputs.rc_tag }}
EXTRA: ${{ inputs.release_notes_extra }}
KIND: stable
run: node .github/scripts/discord-release-announce.mjs

- name: Workflow summary
run: |
{
Expand All @@ -173,4 +225,11 @@ jobs:
echo "- Stable tag: \`${{ steps.version.outputs.stable_tag }}\`"
echo "- Promoted from: \`${{ steps.version.outputs.rc_tag }}\`"
echo "- Tier 3 (homebrew/winget/nix/aur) will fire on the published release via OPENSCREEN_RELEASE_TOKEN."
if [[ "${{ steps.sync_main.outcome }}" != "success" ]]; then
echo ""
echo "> [!WARNING]"
echo "> **main was not synced.** The release is published and the build was"
echo "> dispatched; only the \`release/v${{ steps.version.outputs.stable_version }}-sync\` → main PR is"
echo "> outstanding. Merge it by hand, then main is back in line."
fi
} >> "$GITHUB_STEP_SUMMARY"
Loading