mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(release): make desktop releases immutable (#3568)
## Summary - add a manual desktop release preparer that regenerates one version-only candidate from current `origin/main` - validate deterministic complete changelog accounting, candidate authorship, allowed files, exact-head approval, required checks, and two-parent merge topology before tagging the reviewed candidate - move desktop tags/releases from `v*` to `desktop-v*` while preserving relay, chart, push-chart, and mobile behavior - stage all four platform outputs in Actions artifacts and grant GitHub release write access only to one final all-platform-gated publisher - publish the versioned release only after complete artifact assembly; update stable `latest.json` last; never promote prereleases or published rebuild outputs ## Safety properties - desktop tags point to the reviewed candidate SHA, not the merge commit - release builds remain tag-bound and reverify tag == checked-out HEAD - one final writer fails closed on artifact basename collisions - per-tag concurrency serializes publication without cancellation - published reruns do not replace immutable versioned assets or promote signatures from a rebuild - candidate branches use an explicit remote OID lease when regenerated ## Validation - `scripts/test-desktop-release-candidate.sh` - `scripts/test-release-ref-contract.sh` - `scripts/test-mobile-release-contract.sh` - changed workflow YAML parsing (Ruby Psych) - changed shell syntax (`bash -n`) - `git diff --check` - push hooks: branch-skew, Rust workspace tests (1,853 passed), desktop Tauri tests (3 passed) ## Coordinated companion - squareup/buzz-releases#79 updates the manually entered desktop source-tag contract to stable-only `desktop-v*` - merge the private contract companion before the first namespaced desktop release ## Rollout blockers (no settings changed here) Before the first candidate/release: 1. enable merge commits in repository settings 2. allow `merge` in ruleset `13596885` 3. require approval after the last push in ruleset `13596885` 4. include `refs/tags/desktop-v*` explicitly in release ruleset `14378754` 5. prove the non-publishing candidate/merge/tag/artifact validation path before any production release Do not test the old workflow with a prerelease: it can still mutate the production rolling updater release. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -4,7 +4,7 @@ name: Auto-tag on Release PR Merge
|
||||
# prefix; the main chart lane also auto-detects a Chart.yaml version bump so
|
||||
# a chart feature PR can publish its own new version when merged:
|
||||
#
|
||||
# version-bump/<v> → tag v<v> → release.yml (desktop app)
|
||||
# version-bump/<v> → tag desktop-v<v> → release.yml (desktop app)
|
||||
# relay-release/<v> → tag relay-v<v> → docker.yml (relay image)
|
||||
# chart-release/<v> → tag chart-v<v> → helm-chart.yml (main helm chart)
|
||||
# push-chart-release/<v> → tag push-chart-v<v> → push-gateway-helm-chart.yml
|
||||
@@ -35,6 +35,11 @@ permissions:
|
||||
|
||||
jobs:
|
||||
auto-tag:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
checks: read
|
||||
statuses: read
|
||||
if: >
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
@@ -57,7 +62,7 @@ jobs:
|
||||
case "$BRANCH" in
|
||||
version-bump/*)
|
||||
VERSION="${BRANCH#version-bump/}"
|
||||
TAG_PREFIX="v" ;;
|
||||
TAG_PREFIX="desktop-v" ;;
|
||||
relay-release/*)
|
||||
VERSION="${BRANCH#relay-release/}"
|
||||
TAG_PREFIX="relay-v" ;;
|
||||
@@ -85,9 +90,34 @@ jobs:
|
||||
{
|
||||
echo "enabled=true"
|
||||
echo "tag=${TAG_PREFIX}${VERSION}"
|
||||
if [[ "$TAG_PREFIX" == desktop-v ]]; then
|
||||
echo "target_sha=${{ github.event.pull_request.head.sha }}"
|
||||
echo "desktop=true"
|
||||
else
|
||||
echo "target_sha=$GITHUB_SHA"
|
||||
echo "desktop=false"
|
||||
fi
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "Tagging ${TAG_PREFIX}${VERSION}"
|
||||
|
||||
|
||||
- name: Verify immutable reviewed desktop candidate
|
||||
if: steps.release.outputs.desktop == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.release.outputs.tag }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
PR_PUSHER: ${{ github.event.pull_request.head.user.login }}
|
||||
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
run: |
|
||||
VERSION="${VERSION#desktop-v}"
|
||||
export VERSION
|
||||
scripts/verify-desktop-release-merge.sh
|
||||
|
||||
- name: Create release tagger token
|
||||
if: steps.release.outputs.enabled == 'true'
|
||||
id: release-tagger
|
||||
@@ -102,21 +132,22 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.release-tagger.outputs.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
TARGET_SHA: ${{ steps.release.outputs.target_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Check gh's exit status, not its output. A missing ref returns a 404
|
||||
# JSON body on stdout, which must not be mistaken for an existing tag.
|
||||
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --silent 2>/dev/null; then
|
||||
EXISTING_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)"
|
||||
if [ "$EXISTING_SHA" = "$GITHUB_SHA" ]; then
|
||||
echo "Tag $TAG already exists at $GITHUB_SHA — skipping tag creation"
|
||||
if [ "$EXISTING_SHA" = "$TARGET_SHA" ]; then
|
||||
echo "Tag $TAG already exists at $TARGET_SHA — skipping tag creation"
|
||||
exit 0
|
||||
else
|
||||
echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $GITHUB_SHA)"
|
||||
echo "::error::Tag $TAG already exists at $EXISTING_SHA (expected $TARGET_SHA)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
|
||||
-f ref="refs/tags/$TAG" \
|
||||
-f sha="$GITHUB_SHA" \
|
||||
-f sha="$TARGET_SHA" \
|
||||
--silent
|
||||
|
||||
@@ -76,6 +76,8 @@ jobs:
|
||||
- '.github/workflows/ci.yml'
|
||||
- name: Release workflow source contract
|
||||
run: scripts/test-release-ref-contract.sh
|
||||
- name: Desktop release candidate contract
|
||||
run: scripts/test-desktop-release-candidate.sh
|
||||
- name: Mobile release contract
|
||||
run: |
|
||||
scripts/test-mobile-release-contract.sh
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Prepare Desktop Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Semver to prepare (for example 0.5.1)
|
||||
required: true
|
||||
|
||||
env:
|
||||
RELEASE_AUTOMATION_NAME: Carl
|
||||
RELEASE_AUTOMATION_EMAIL: c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
if: github.repository == 'block/buzz'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Create short-lived release preparer token
|
||||
id: preparer
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.BUZZ_RELEASE_TAGGER_CLIENT_ID }}
|
||||
private-key: ${{ secrets.BUZZ_RELEASE_TAGGER_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.preparer.outputs.token }}
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
- name: Prepare immutable candidate and open or update PR
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.preparer.outputs.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: scripts/prepare-desktop-release.sh "$VERSION"
|
||||
+144
-174
@@ -1,14 +1,13 @@
|
||||
name: Release
|
||||
|
||||
concurrency:
|
||||
group: desktop-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Semver version matching the v-prefixed dispatch tag"
|
||||
required: true
|
||||
- 'desktop-v[0-9]*'
|
||||
|
||||
jobs:
|
||||
# Shared setup: verify the immutable release tag, determine the version, and
|
||||
@@ -19,23 +18,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
source_sha: ${{ steps.source.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: version
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [[ "$EVENT_NAME" == "push" ]]; then
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
else
|
||||
VERSION="$INPUT_VERSION"
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
run: echo "version=${GITHUB_REF_NAME#desktop-v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate version
|
||||
env:
|
||||
@@ -56,42 +46,9 @@ jobs:
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
scripts/verify-release-ref.sh v "$VERSION"
|
||||
scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
echo "source_sha=$(git rev-parse 'HEAD^{commit}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create versioned GitHub release
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
NOTES=""
|
||||
if [[ -f CHANGELOG.md ]]; then
|
||||
NOTES=$(awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found && !/^\$/" CHANGELOG.md)
|
||||
fi
|
||||
if [[ -z "$NOTES" ]]; then
|
||||
NOTES="Buzz Desktop v${VERSION}"
|
||||
fi
|
||||
PRERELEASE_FLAGS=()
|
||||
if [[ "$VERSION" =~ -(test|alpha|beta|rc)([.-]|$) ]]; then
|
||||
PRERELEASE_FLAGS=(--prerelease --latest=false)
|
||||
fi
|
||||
gh release create "v${VERSION}" \
|
||||
--target "$RELEASE_SHA" \
|
||||
--title "Buzz Desktop v${VERSION}" \
|
||||
--notes "$NOTES" \
|
||||
"${PRERELEASE_FLAGS[@]}"
|
||||
|
||||
- name: Create rolling auto-update release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create buzz-desktop-latest \
|
||||
--prerelease \
|
||||
--title "Buzz Desktop Auto-Update" \
|
||||
--notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
|
||||
2>/dev/null || true
|
||||
|
||||
release:
|
||||
name: Release
|
||||
if: github.repository == 'block/buzz'
|
||||
@@ -99,7 +56,7 @@ jobs:
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
id-token: write # required by block/apple-codesign-action for OIDC
|
||||
outputs:
|
||||
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
||||
@@ -114,7 +71,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
@@ -272,13 +229,19 @@ jobs:
|
||||
fi
|
||||
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Find the updater .tar.gz and .sig
|
||||
# Find the updater .tar.gz and .sig. Give each architecture a unique
|
||||
# release basename before artifacts are merged by the final writer.
|
||||
ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1)
|
||||
SIG="${ARCHIVE}.sig"
|
||||
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
||||
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
|
||||
exit 1
|
||||
fi
|
||||
RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_aarch64.app.tar.gz"
|
||||
mv "$ARCHIVE" "$RENAMED"
|
||||
mv "$SIG" "${RENAMED}.sig"
|
||||
ARCHIVE="$RENAMED"
|
||||
SIG="${RENAMED}.sig"
|
||||
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
||||
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
||||
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
||||
@@ -289,23 +252,15 @@ jobs:
|
||||
env:
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
- name: Upload arm64 DMG to versioned GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DMG_PATH: ${{ steps.artifacts.outputs.dmg }}
|
||||
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
"$SIG_PATH" \
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
- name: Stage Apple Silicon release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-macos-arm64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.artifacts.outputs.dmg }}
|
||||
${{ steps.artifacts.outputs.archive }}
|
||||
${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
release-macos-x64:
|
||||
name: Release macOS (Intel)
|
||||
@@ -314,7 +269,7 @@ jobs:
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
id-token: write # required by block/apple-codesign-action for OIDC
|
||||
outputs:
|
||||
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
||||
@@ -330,7 +285,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
@@ -443,6 +398,11 @@ jobs:
|
||||
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
|
||||
exit 1
|
||||
fi
|
||||
RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_x64.app.tar.gz"
|
||||
mv "$ARCHIVE" "$RENAMED"
|
||||
mv "$SIG" "${RENAMED}.sig"
|
||||
ARCHIVE="$RENAMED"
|
||||
SIG="${RENAMED}.sig"
|
||||
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
||||
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
||||
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
||||
@@ -453,23 +413,15 @@ jobs:
|
||||
env:
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
- name: Upload Intel DMG to versioned GitHub release
|
||||
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
"$SIG_PATH" \
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
- name: Stage Intel macOS release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-macos-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.unsigned.outputs.dmg }}
|
||||
${{ steps.artifacts.outputs.archive }}
|
||||
${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
release-linux:
|
||||
name: Release Linux
|
||||
@@ -480,7 +432,7 @@ jobs:
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
env:
|
||||
# AppImage tools (linuxdeploy, appimagetool) are themselves AppImages.
|
||||
# Containers lack FUSE, so we must use the extract-and-run fallback.
|
||||
@@ -555,7 +507,7 @@ jobs:
|
||||
- name: Verify tag-bound release source
|
||||
env:
|
||||
VERSION: ${{ needs.setup.outputs.version }}
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
@@ -689,29 +641,16 @@ jobs:
|
||||
SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
|
||||
|
||||
# NOTE: .deb is NOT auto-updatable (Tauri updater constraint — only AppImage supports it on Linux)
|
||||
- name: Upload Linux artifacts to versioned GitHub release
|
||||
env:
|
||||
VERSION: ${{ needs.setup.outputs.version }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DEB_PATH: ${{ steps.linux-artifacts.outputs.deb }}
|
||||
APPIMAGE_PATH: ${{ steps.linux-artifacts.outputs.appimage }}
|
||||
run: |
|
||||
gh release upload "v$VERSION" \
|
||||
"$DEB_PATH" \
|
||||
"$APPIMAGE_PATH" \
|
||||
--clobber
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
"$SIG_PATH" \
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARCHIVE_PATH: ${{ steps.linux-artifacts.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
|
||||
- name: Stage Linux release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-linux-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.linux-artifacts.outputs.deb }}
|
||||
${{ steps.linux-artifacts.outputs.appimage }}
|
||||
${{ steps.linux-artifacts.outputs.archive }}
|
||||
${{ steps.linux-artifacts.outputs.sig }}
|
||||
|
||||
release-windows:
|
||||
name: Release Windows
|
||||
@@ -719,7 +658,7 @@ jobs:
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
outputs:
|
||||
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
||||
sig: ${{ steps.read-sig.outputs.sig }}
|
||||
@@ -735,7 +674,7 @@ jobs:
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
shell: bash
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
@@ -745,7 +684,7 @@ jobs:
|
||||
with:
|
||||
node-version: 24.14.1
|
||||
# Disable dependency caching: a writable cache in this release workflow
|
||||
# (contents: write, feeds a signed installer) is a poisoning vector. pnpm
|
||||
# (contents: read, feeds a signed installer) is a poisoning vector. pnpm
|
||||
# install runs uncached below.
|
||||
package-manager-cache: false
|
||||
|
||||
@@ -827,25 +766,14 @@ jobs:
|
||||
env:
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
- name: Upload Windows installer to versioned GitHub release
|
||||
shell: bash
|
||||
run: gh release upload "v${VERSION}" "$EXE_PATH" --clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
EXE_PATH: ${{ steps.artifacts.outputs.exe }}
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
shell: bash
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
"$SIG_PATH" \
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
- name: Stage Windows release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-windows-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.artifacts.outputs.exe }}
|
||||
${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
assemble-manifest:
|
||||
name: Assemble multi-platform latest.json
|
||||
@@ -853,7 +781,11 @@ jobs:
|
||||
if: |
|
||||
always() &&
|
||||
needs.setup.result == 'success' &&
|
||||
github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
needs.release.result == 'success' &&
|
||||
needs.release-macos-x64.result == 'success' &&
|
||||
needs.release-linux.result == 'success' &&
|
||||
needs.release-windows.result == 'success' &&
|
||||
github.ref == format('refs/tags/desktop-v{0}', needs.setup.outputs.version)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup, release, release-macos-x64, release-linux, release-windows]
|
||||
timeout-minutes: 10
|
||||
@@ -870,7 +802,26 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- name: Download staged release artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: desktop-release-*
|
||||
path: staged-by-platform
|
||||
|
||||
- name: Flatten staged artifacts without basename collisions
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir staged
|
||||
while IFS= read -r -d '' file; do
|
||||
name="$(basename "$file")"
|
||||
[[ ! -e "staged/$name" ]] || {
|
||||
echo "::error::release artifact basename collision: $name"
|
||||
exit 1
|
||||
}
|
||||
cp "$file" "staged/$name"
|
||||
done < <(find staged-by-platform -type f -print0)
|
||||
|
||||
- name: Write signature files
|
||||
env:
|
||||
@@ -899,7 +850,7 @@ jobs:
|
||||
write_sig "$RESULT_LINUX" linux-x86_64 "$SIG_LINUX"
|
||||
write_sig "$RESULT_WIN" windows-x86_64 "$SIG_WIN"
|
||||
|
||||
- name: Verify archive URLs are accessible
|
||||
- name: Verify draft release has every updater archive
|
||||
env:
|
||||
RESULT_ARM64: ${{ needs.release.result }}
|
||||
RESULT_X64: ${{ needs.release-macos-x64.result }}
|
||||
@@ -911,39 +862,19 @@ jobs:
|
||||
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
||||
ARCHIVES=()
|
||||
|
||||
add_archive() {
|
||||
local result="$1" platform="$2" archive="$3"
|
||||
if [[ "$result" == "success" ]]; then
|
||||
[[ -n "$archive" ]] || { echo "::error::Missing archive name for successful platform: $platform"; exit 1; }
|
||||
ARCHIVES+=("$archive")
|
||||
fi
|
||||
}
|
||||
|
||||
add_archive "$RESULT_ARM64" darwin-aarch64 "$ARCHIVE_ARM64"
|
||||
add_archive "$RESULT_X64" darwin-x86_64 "$ARCHIVE_X64"
|
||||
add_archive "$RESULT_LINUX" linux-x86_64 "$ARCHIVE_LINUX"
|
||||
add_archive "$RESULT_WIN" windows-x86_64 "$ARCHIVE_WIN"
|
||||
|
||||
for name in "${ARCHIVES[@]}"; do
|
||||
echo "Checking $BASE/$name ..."
|
||||
success=false
|
||||
for attempt in 1 2 3; do
|
||||
if curl -fsI "$BASE/$name" > /dev/null 2>&1; then
|
||||
success=true
|
||||
break
|
||||
fi
|
||||
echo "Attempt $attempt failed for $name, retrying in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
if [ "$success" != "true" ]; then
|
||||
echo "::error::Archive not accessible after 3 attempts: $BASE/$name"
|
||||
exit 1
|
||||
assets=$(find staged -type f -exec basename {} \;)
|
||||
for spec in \
|
||||
"$RESULT_ARM64:$ARCHIVE_ARM64" \
|
||||
"$RESULT_X64:$ARCHIVE_X64" \
|
||||
"$RESULT_LINUX:$ARCHIVE_LINUX" \
|
||||
"$RESULT_WIN:$ARCHIVE_WIN"; do
|
||||
result="${spec%%:*}"
|
||||
archive="${spec#*:}"
|
||||
if [[ "$result" == success ]]; then
|
||||
[[ -n "$archive" ]] || { echo "::error::successful platform has no archive"; exit 1; }
|
||||
grep -Fxq "$archive" <<<"$assets" || { echo "::error::draft release missing $archive"; exit 1; }
|
||||
fi
|
||||
done
|
||||
echo "All archive URLs verified."
|
||||
|
||||
- name: Generate unified latest.json
|
||||
env:
|
||||
@@ -957,7 +888,7 @@ jobs:
|
||||
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
||||
BASE="https://github.com/block/buzz/releases/download/desktop-v${VERSION}"
|
||||
TRIPLES=()
|
||||
|
||||
add_triple() {
|
||||
@@ -977,6 +908,45 @@ jobs:
|
||||
bash desktop/scripts/generate-oss-latest-json.sh "$VERSION" "${TRIPLES[@]}" > latest.json
|
||||
cat latest.json
|
||||
|
||||
- name: Upload latest.json to rolling release
|
||||
- name: Create or verify versioned draft
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest latest.json --clobber
|
||||
set -euo pipefail
|
||||
NOTES_FILE="${RUNNER_TEMP}/release-notes.md"
|
||||
awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found" CHANGELOG.md > "$NOTES_FILE"
|
||||
[[ -s "$NOTES_FILE" ]] || { echo "::error::missing non-empty changelog block for v${VERSION}"; exit 1; }
|
||||
PRERELEASE_FLAGS=()
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
PRERELEASE_FLAGS=(--prerelease --latest=false)
|
||||
fi
|
||||
if gh release view "desktop-v${VERSION}" >/dev/null 2>&1; then
|
||||
EXISTING_SHA=$(gh release view "desktop-v${VERSION}" --json targetCommitish --jq .targetCommitish)
|
||||
IS_DRAFT=$(gh release view "desktop-v${VERSION}" --json isDraft --jq .isDraft)
|
||||
[[ "$EXISTING_SHA" == "${{ needs.setup.outputs.source_sha }}" ]] || {
|
||||
echo "::error::existing release targets $EXISTING_SHA, not the immutable source"; exit 1;
|
||||
}
|
||||
if [[ "$IS_DRAFT" != true ]]; then
|
||||
echo "already_published=true" >> "$GITHUB_ENV"
|
||||
fi
|
||||
else
|
||||
gh release create "desktop-v${VERSION}" \
|
||||
--draft \
|
||||
--target "${{ needs.setup.outputs.source_sha }}" \
|
||||
--title "Buzz Desktop v${VERSION}" \
|
||||
--notes-file "$NOTES_FILE" \
|
||||
"${PRERELEASE_FLAGS[@]}"
|
||||
fi
|
||||
|
||||
- name: Upload complete artifact set to versioned draft
|
||||
if: env.already_published != 'true'
|
||||
run: |
|
||||
mapfile -t files < <(find staged -type f -print)
|
||||
[[ "${#files[@]}" -gt 0 ]] || { echo "::error::no staged release artifacts"; exit 1; }
|
||||
gh release upload "desktop-v${VERSION}" "${files[@]}" --clobber
|
||||
|
||||
- name: Publish complete versioned release
|
||||
if: env.already_published != 'true'
|
||||
run: gh release edit "desktop-v${VERSION}" --draft=false
|
||||
|
||||
- name: Upload latest.json to rolling release last
|
||||
if: ${{ env.already_published != 'true' && !contains(needs.setup.outputs.version, '-') }}
|
||||
run: gh release upload buzz-desktop-latest latest.json --clobber
|
||||
|
||||
Reference in New Issue
Block a user