mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
fix(ci): make the Homebrew/Winget bump workflows actually fire
All four bump workflows triggered on `release: types: [released]`, which never fires here: create-release.yml publishes the release with GITHUB_TOKEN, and GitHub suppresses workflow-triggering events for GITHUB_TOKEN actions. They had zero runs across every release up to v1.13.1. Switch them to `workflow_run` on "Create Release Assets" completion, filtered to a successful tag push. That also removes a latent race: `released` fired while the matrix legs were still uploading assets, whereas workflow_run fires after all of them finish. The workflow_run payload carries no draft/prerelease flags, so add a resolve-release composite action that reads them back from the API and feeds assert-stable-release, keeping the defense-in-depth guard intact instead of inferring stability from the tag string alone. Also gate the cask/winget bumps on the package existing upstream. Neither `amethyst-nostr` nor `VitorPamplona.Amethyst` has been bootstrapped, and bump-cask-pr/winget-releaser can only update an existing package — without the gate, fixing the trigger would file a spurious [release-ops] issue on every release. Docs: correct the claims this uncovered — Homebrew/Winget are not shipping, macOS is arm64-only (no Intel DMG), the release carries 31 assets (13 Android, not 12), Maven Central publishes from a step inside deploy-android and lags repo1 by tens of minutes, RELEASE_NOTES_ID is minor-releases-only, and note the git-credential-manager hang that blocks the release push.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
name: Resolve Release
|
||||
description: >-
|
||||
Resolve a bump workflow's target tag and that release's real published state.
|
||||
Companion to assert-stable-release: this one FETCHES the facts, that one
|
||||
ENFORCES them. Split so the enforcement stays a pure function of its inputs.
|
||||
|
||||
Handles both entry points of the bump workflows:
|
||||
- workflow_run -> tag comes from the triggering run's head_branch
|
||||
- workflow_dispatch (manual recovery) -> tag comes from the input
|
||||
In both cases draft/prerelease are read back from the GitHub API rather than
|
||||
inferred, so a draft or prerelease can never slip through to a third-party
|
||||
package repo just because the trigger payload lacked the flags.
|
||||
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag to resolve (e.g. vX.Y.Z)"
|
||||
required: true
|
||||
github_token:
|
||||
description: "Token used to read the release via the GH API"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
tag:
|
||||
description: "The resolved tag, verbatim (e.g. v1.13.1)"
|
||||
value: ${{ steps.resolve.outputs.tag }}
|
||||
ver:
|
||||
description: "The tag with the leading 'v' stripped (e.g. 1.13.1)"
|
||||
value: ${{ steps.resolve.outputs.ver }}
|
||||
is_prerelease:
|
||||
description: "'true' if the GH Release is flagged prerelease"
|
||||
value: ${{ steps.resolve.outputs.is_prerelease }}
|
||||
is_draft:
|
||||
description: "'true' if the GH Release is still a draft"
|
||||
value: ${{ steps.resolve.outputs.is_draft }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve tag and release state
|
||||
id: resolve
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
GH_TOKEN: ${{ inputs.github_token }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::No tag to resolve (neither workflow_run.head_branch nor the dispatch input was set)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A missing release here is a real fault, not something to paper over:
|
||||
# every caller is about to publish this version to an external package
|
||||
# manager. Fail loudly and let the caller's Report-failure step file it.
|
||||
if ! META=$(gh release view "$TAG" --repo "$REPO" --json isDraft,isPrerelease 2>&1); then
|
||||
echo "::error::No GH Release found for tag $TAG in $REPO -- refusing to bump"
|
||||
echo "$META"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IS_DRAFT=$(echo "$META" | jq -r '.isDraft')
|
||||
IS_PRERELEASE=$(echo "$META" | jq -r '.isPrerelease')
|
||||
|
||||
{
|
||||
echo "tag=$TAG"
|
||||
echo "ver=${TAG#v}"
|
||||
echo "is_draft=$IS_DRAFT"
|
||||
echo "is_prerelease=$IS_PRERELEASE"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "resolved tag=$TAG ver=${TAG#v} draft=$IS_DRAFT prerelease=$IS_PRERELEASE"
|
||||
@@ -19,9 +19,14 @@ name: Bump Homebrew Formula (amy CLI)
|
||||
# auto-bump here (symmetric to the cask action in bump-homebrew.yml) — see the
|
||||
# "TODO(bootstrap)" note at the bottom of this file.
|
||||
|
||||
# Trigger: after "Create Release Assets" succeeds for a tag push. NOT
|
||||
# `release: types: [released]` — that event never fires, because the release is
|
||||
# created by create-release.yml under GITHUB_TOKEN and GitHub suppresses
|
||||
# workflow-triggering events for it. See the full note in bump-homebrew.yml.
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_run:
|
||||
workflows: ["Create Release Assets"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -38,44 +43,49 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
# Serialize per tag; do not cancel in-progress runs.
|
||||
group: bump-homebrew-formula-${{ github.event.release.tag_name || inputs.tag }}
|
||||
group: bump-homebrew-formula-${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync-formula:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
|
||||
# See bump-homebrew.yml for why these three conditions: successful, tag-push
|
||||
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'v'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Resolve release
|
||||
id: rel
|
||||
uses: ./.github/actions/resolve-release
|
||||
with:
|
||||
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Re-assert stable release
|
||||
uses: ./.github/actions/assert-stable-release
|
||||
with:
|
||||
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
|
||||
is_draft: ${{ github.event.release.draft || 'false' }}
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ github.event.release.tag_name || inputs.tag }}"
|
||||
VER="${TAG#v}"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "ver=$VER" >> "$GITHUB_OUTPUT"
|
||||
tag: ${{ steps.rel.outputs.tag }}
|
||||
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
|
||||
is_draft: ${{ steps.rel.outputs.is_draft }}
|
||||
|
||||
- name: Download jvm bundle and compute sha256
|
||||
id: asset
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ steps.ver.outputs.tag }}"
|
||||
VER="${{ steps.ver.outputs.ver }}"
|
||||
TAG="${{ steps.rel.outputs.tag }}"
|
||||
VER="${{ steps.rel.outputs.ver }}"
|
||||
URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/amy-${VER}-jvm.tar.gz"
|
||||
echo "Fetching $URL"
|
||||
# The `released` event can fire a hair before every matrix leg finishes
|
||||
# uploading; retry with backoff (mirrors the repo's push/pull retry ethos).
|
||||
# workflow_run fires only after every upload leg has finished, so the
|
||||
# asset should already be there. Retry anyway for release-CDN
|
||||
# propagation (mirrors the repo's push/pull retry ethos).
|
||||
ok=0
|
||||
for i in 1 2 3 4 5; do
|
||||
if curl -fsSL -o amy-jvm.tar.gz "$URL"; then ok=1; break; fi
|
||||
@@ -110,13 +120,13 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base: main
|
||||
branch: chore/bump-amy-formula-${{ steps.ver.outputs.tag }}
|
||||
branch: chore/bump-amy-formula-${{ steps.rel.outputs.tag }}
|
||||
add-paths: cli/packaging/homebrew/amy.rb
|
||||
commit-message: 'chore: sync amy Homebrew formula to ${{ steps.ver.outputs.tag }}'
|
||||
title: 'chore: sync amy Homebrew formula to ${{ steps.ver.outputs.tag }}'
|
||||
commit-message: 'chore: sync amy Homebrew formula to ${{ steps.rel.outputs.tag }}'
|
||||
title: 'chore: sync amy Homebrew formula to ${{ steps.rel.outputs.tag }}'
|
||||
body: |
|
||||
Auto-synced `cli/packaging/homebrew/amy.rb` to the
|
||||
`${{ steps.ver.outputs.tag }}` release:
|
||||
`${{ steps.rel.outputs.tag }}` release:
|
||||
|
||||
- `url` -> `${{ steps.asset.outputs.url }}`
|
||||
- `sha256` -> `${{ steps.asset.outputs.sha256 }}`
|
||||
@@ -136,7 +146,7 @@ jobs:
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
|
||||
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -17,9 +17,14 @@ name: Bump Homebrew Formula (geode relay)
|
||||
# human-reviewed new-formula PR (the one-time bootstrap). Once it lands, wire the
|
||||
# auto-bump here — see the "TODO(bootstrap)" note at the bottom of this file.
|
||||
|
||||
# Trigger: after "Create Release Assets" succeeds for a tag push. NOT
|
||||
# `release: types: [released]` — that event never fires, because the release is
|
||||
# created by create-release.yml under GITHUB_TOKEN and GitHub suppresses
|
||||
# workflow-triggering events for it. See the full note in bump-homebrew.yml.
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_run:
|
||||
workflows: ["Create Release Assets"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -36,44 +41,49 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
# Serialize per tag; do not cancel in-progress runs.
|
||||
group: bump-homebrew-geode-formula-${{ github.event.release.tag_name || inputs.tag }}
|
||||
group: bump-homebrew-geode-formula-${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync-formula:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
|
||||
# See bump-homebrew.yml for why these three conditions: successful, tag-push
|
||||
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'v'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Resolve release
|
||||
id: rel
|
||||
uses: ./.github/actions/resolve-release
|
||||
with:
|
||||
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Re-assert stable release
|
||||
uses: ./.github/actions/assert-stable-release
|
||||
with:
|
||||
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
|
||||
is_draft: ${{ github.event.release.draft || 'false' }}
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ github.event.release.tag_name || inputs.tag }}"
|
||||
VER="${TAG#v}"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "ver=$VER" >> "$GITHUB_OUTPUT"
|
||||
tag: ${{ steps.rel.outputs.tag }}
|
||||
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
|
||||
is_draft: ${{ steps.rel.outputs.is_draft }}
|
||||
|
||||
- name: Download jvm bundle and compute sha256
|
||||
id: asset
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ steps.ver.outputs.tag }}"
|
||||
VER="${{ steps.ver.outputs.ver }}"
|
||||
TAG="${{ steps.rel.outputs.tag }}"
|
||||
VER="${{ steps.rel.outputs.ver }}"
|
||||
URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/geode-${VER}-jvm.tar.gz"
|
||||
echo "Fetching $URL"
|
||||
# The `released` event can fire a hair before every matrix leg finishes
|
||||
# uploading; retry with backoff (mirrors the repo's push/pull retry ethos).
|
||||
# workflow_run fires only after every upload leg has finished, so the
|
||||
# asset should already be there. Retry anyway for release-CDN
|
||||
# propagation (mirrors the repo's push/pull retry ethos).
|
||||
ok=0
|
||||
for i in 1 2 3 4 5; do
|
||||
if curl -fsSL -o geode-jvm.tar.gz "$URL"; then ok=1; break; fi
|
||||
@@ -108,13 +118,13 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base: main
|
||||
branch: chore/bump-geode-formula-${{ steps.ver.outputs.tag }}
|
||||
branch: chore/bump-geode-formula-${{ steps.rel.outputs.tag }}
|
||||
add-paths: geode/packaging/homebrew/geode.rb
|
||||
commit-message: 'chore: sync geode Homebrew formula to ${{ steps.ver.outputs.tag }}'
|
||||
title: 'chore: sync geode Homebrew formula to ${{ steps.ver.outputs.tag }}'
|
||||
commit-message: 'chore: sync geode Homebrew formula to ${{ steps.rel.outputs.tag }}'
|
||||
title: 'chore: sync geode Homebrew formula to ${{ steps.rel.outputs.tag }}'
|
||||
body: |
|
||||
Auto-synced `geode/packaging/homebrew/geode.rb` to the
|
||||
`${{ steps.ver.outputs.tag }}` release:
|
||||
`${{ steps.rel.outputs.tag }}` release:
|
||||
|
||||
- `url` -> `${{ steps.asset.outputs.url }}`
|
||||
- `sha256` -> `${{ steps.asset.outputs.sha256 }}`
|
||||
@@ -134,7 +144,7 @@ jobs:
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
|
||||
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
name: Bump Homebrew Cask
|
||||
|
||||
# Fires when a GH Release is published (not draft, not prerelease).
|
||||
# `release.types: [released]` event fires only for stable releases — still
|
||||
# double-checked by .github/actions/assert-stable-release for defense-in-depth.
|
||||
# Fires after "Create Release Assets" finishes successfully for a tag push.
|
||||
#
|
||||
# NOT `release: types: [released]`. That event never fires here: the release is
|
||||
# created by create-release.yml using GITHUB_TOKEN, and GitHub deliberately does
|
||||
# not raise workflow-triggering events for actions taken by GITHUB_TOKEN (the
|
||||
# recursion guard). This workflow sat silently dead through every release up to
|
||||
# v1.13.1 for exactly that reason.
|
||||
#
|
||||
# `workflow_run` has no such restriction, and it is also strictly better timed:
|
||||
# it fires once ALL upload legs have finished, whereas `released` fired while
|
||||
# assets were still uploading (hence the download retry loops in the sibling
|
||||
# formula workflows).
|
||||
#
|
||||
# Draft/prerelease state is not in the workflow_run payload, so it is read back
|
||||
# from the API by .github/actions/resolve-release and enforced by
|
||||
# .github/actions/assert-stable-release.
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_run:
|
||||
workflows: ["Create Release Assets"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -22,39 +36,72 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
# Serialize bumps per tag; do not cancel in-progress bumps.
|
||||
group: bump-homebrew-${{ github.event.release.tag_name || inputs.tag }}
|
||||
group: bump-homebrew-${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
|
||||
# On workflow_run, filter down to a SUCCESSFUL TAG build. `event == 'push'`
|
||||
# excludes create-release.yml's workflow_dispatch dry-runs (which carry a
|
||||
# synthetic test_tag and publish nothing), and the head_branch prefix keeps
|
||||
# non-tag runs out. Exact tag format is still enforced downstream.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'v'))
|
||||
runs-on: ubuntu-latest # brew runs on Linux — saves macOS runner quota
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Resolve release
|
||||
id: rel
|
||||
uses: ./.github/actions/resolve-release
|
||||
with:
|
||||
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Re-assert stable release
|
||||
uses: ./.github/actions/assert-stable-release
|
||||
with:
|
||||
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
|
||||
is_draft: ${{ github.event.release.draft || 'false' }}
|
||||
tag: ${{ steps.rel.outputs.tag }}
|
||||
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
|
||||
is_draft: ${{ steps.rel.outputs.is_draft }}
|
||||
|
||||
# `brew bump-cask-pr` can only bump a cask that ALREADY EXISTS in the tap.
|
||||
# `amethyst-nostr` has never been submitted to Homebrew/homebrew-cask, so
|
||||
# until the one-time bootstrap PR lands (BUILDING.md § Bootstrap) this
|
||||
# workflow must no-op rather than fail — otherwise every single release
|
||||
# files a spurious [release-ops] issue.
|
||||
- name: Check the cask exists in homebrew-cask
|
||||
id: cask
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if curl -fsSL -o /dev/null https://formulae.brew.sh/api/cask/amethyst-nostr.json; then
|
||||
echo "bootstrapped=true" >> "$GITHUB_OUTPUT"
|
||||
echo "cask amethyst-nostr found in homebrew-cask; proceeding with bump"
|
||||
else
|
||||
echo "bootstrapped=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::Cask 'amethyst-nostr' is not in Homebrew/homebrew-cask yet, so there is nothing to bump for ${{ steps.rel.outputs.tag }}. Amethyst is NOT shipping via Homebrew. Submit the one-time new-cask PR (BUILDING.md § Bootstrap) to activate this channel."
|
||||
fi
|
||||
|
||||
- name: Bump cask (push-or-update PR)
|
||||
if: steps.cask.outputs.bootstrapped == 'true'
|
||||
uses: macauley/action-homebrew-bump-cask@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW_TOKEN }}
|
||||
tap: homebrew/cask
|
||||
cask: amethyst-nostr
|
||||
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
tag: ${{ steps.rel.outputs.tag }}
|
||||
|
||||
- name: Report failure
|
||||
if: failure()
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
|
||||
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
name: Bump Winget Manifest
|
||||
|
||||
# Fires after "Create Release Assets" finishes successfully for a tag push.
|
||||
#
|
||||
# NOT `release: types: [released]`. That event never fires here: the release is
|
||||
# created by create-release.yml using GITHUB_TOKEN, and GitHub does not raise
|
||||
# workflow-triggering events for GITHUB_TOKEN actions. This workflow sat
|
||||
# silently dead through every release up to v1.13.1 for exactly that reason.
|
||||
# See the longer note in bump-homebrew.yml.
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_run:
|
||||
workflows: ["Create Release Assets"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -18,30 +26,62 @@ permissions:
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: bump-winget-${{ github.event.release.tag_name || inputs.tag }}
|
||||
group: bump-winget-${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
|
||||
# See bump-homebrew.yml for why these three conditions: successful, tag-push
|
||||
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'v'))
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Resolve release
|
||||
id: rel
|
||||
uses: ./.github/actions/resolve-release
|
||||
with:
|
||||
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Re-assert stable release
|
||||
uses: ./.github/actions/assert-stable-release
|
||||
with:
|
||||
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
|
||||
is_draft: ${{ github.event.release.draft || 'false' }}
|
||||
tag: ${{ steps.rel.outputs.tag }}
|
||||
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
|
||||
is_draft: ${{ steps.rel.outputs.is_draft }}
|
||||
|
||||
# winget-releaser UPDATES an existing package; `VitorPamplona.Amethyst`
|
||||
# has never been submitted to microsoft/winget-pkgs. Until the one-time
|
||||
# new-package PR lands (BUILDING.md § Bootstrap), no-op instead of failing
|
||||
# every release with a spurious [release-ops] issue.
|
||||
- name: Check the package exists in winget-pkgs
|
||||
id: pkg
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
URL=https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/v/VitorPamplona/Amethyst
|
||||
if curl -fsSL -o /dev/null -H "Accept: application/vnd.github+json" "$URL"; then
|
||||
echo "bootstrapped=true" >> "$GITHUB_OUTPUT"
|
||||
echo "VitorPamplona.Amethyst found in winget-pkgs; proceeding with submission"
|
||||
else
|
||||
echo "bootstrapped=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::Package 'VitorPamplona.Amethyst' is not in microsoft/winget-pkgs yet, so there is nothing to update for ${{ steps.rel.outputs.tag }}. Amethyst is NOT shipping via Winget. Submit the one-time new-package PR (BUILDING.md § Bootstrap) to activate this channel."
|
||||
fi
|
||||
|
||||
- name: Submit manifest to winget-pkgs
|
||||
if: steps.pkg.outputs.bootstrapped == 'true'
|
||||
uses: vedantmgoyal9/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
|
||||
with:
|
||||
identifier: VitorPamplona.Amethyst
|
||||
version: ${{ github.event.release.tag_name || inputs.tag }}
|
||||
version: ${{ steps.rel.outputs.tag }}
|
||||
# Asset naming contract: scripts/asset-name.sh
|
||||
installers-regex: '^amethyst-desktop-.*-windows-x64\.msi$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
@@ -51,7 +91,7 @@ jobs:
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
|
||||
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
|
||||
+35
-9
@@ -325,15 +325,29 @@ Quartz library in one pipeline.
|
||||
|
||||
3. **Wait** for the `Create Release Assets` workflow to finish (~25–30 min).
|
||||
|
||||
4. **Verify**:
|
||||
- GH Release contains 8 desktop assets + 12 Android assets
|
||||
4. **Verify** — the GH Release should hold **31 assets**:
|
||||
- **8 desktop** — `dmg` (macOS arm64), `msi` + `zip` (Windows), `deb`, `rpm`,
|
||||
`AppImage`, `flatpak`, `tar.gz` (Linux). There is **no Intel/x64 macOS
|
||||
DMG** — `jpackage` cannot cross-compile and no Intel runner leg is
|
||||
configured, so macOS ships arm64-only.
|
||||
- **13 Android** — 5 Google Play APKs + 5 F-Droid APKs + 2 AABs + the
|
||||
F-Droid `.apks` set built for Accrescent.
|
||||
- **5 amy** + **5 geode** bundles.
|
||||
- Asset sizes look sane (see §Enforce asset size budget — CI auto-fails at 1 GB/asset)
|
||||
- Intel + ARM DMGs both present
|
||||
- Android flow unchanged
|
||||
|
||||
Quick diff against the previous release, which catches a silently-dropped
|
||||
matrix leg better than any count:
|
||||
|
||||
```bash
|
||||
diff <(gh release view v1.13.0 --json assets --jq '.assets[].name' | sed 's/1\.13\.0/VER/g' | sort) \
|
||||
<(gh release view v1.13.1 --json assets --jq '.assets[].name' | sed 's/1\.13\.1/VER/g' | sort)
|
||||
```
|
||||
|
||||
5. **Stable vs prerelease** — a tag containing `-rc`, `-beta`, `-alpha`, `-dev`,
|
||||
or `-snapshot` is auto-classified as prerelease. Stable tags trigger the
|
||||
Homebrew + Winget bump workflows.
|
||||
or `-snapshot` is auto-classified as prerelease. Only stable tags run the
|
||||
Homebrew + Winget bump workflows (and those are no-ops until the one-time
|
||||
bootstrap PRs land — see § Bootstrap).
|
||||
|
||||
### Dry-run (no tag push)
|
||||
|
||||
@@ -473,11 +487,11 @@ distributes; the official Amethyst rollout for each is in
|
||||
| Channel | How it ships | Push or pull |
|
||||
|---|---|---|
|
||||
| **GitHub Releases** | The release workflow builds + signs all assets and attaches them to the tag's Release | Automatic (CI) |
|
||||
| **Maven Central** | Same workflow runs `publishAllPublicationsToMavenCentral` for `quartz` | Automatic (CI) |
|
||||
| **Maven Central** | Same workflow runs `publishAllPublicationsToMavenCentral` for `quartz` — a *step* at the end of the `deploy-android` job, not a job of its own, so it does not appear in a job list | Automatic (CI) |
|
||||
| **Google Play** | Download the signed `amethyst-googleplay-<version>.aab` from the GH Release and upload it in Play Console | **Manual push** |
|
||||
| **F-Droid** | F-Droid's build server detects the new tag and **builds the `fdroid` flavor from source** per its recipe in the external [`fdroiddata`](https://gitlab.com/fdroid/fdroiddata) repo, then signs + publishes itself | **Pull (build-from-source)** |
|
||||
| **Zapstore** | The [`zsp`](https://zapstore.dev/) CLI reads [`zapstore.yaml`](zapstore.yaml) and publishes a Nostr software-release event signed with the app's nsec | **Manual push (Nostr)** |
|
||||
| **Homebrew + Winget** | `bump-homebrew.yml` / `bump-winget.yml` open version-bump PRs on stable tags | Automatic (CI) |
|
||||
| **Homebrew + Winget** | `bump-homebrew.yml` / `bump-winget.yml` open version-bump PRs on stable tags — **currently no-ops**: neither package has been bootstrapped upstream yet (§ Bootstrap) | Automatic (CI), inactive |
|
||||
|
||||
Two channels need the build to stay split into product flavors (see
|
||||
`amethyst/build.gradle.kts` → `productFlavors`):
|
||||
@@ -498,6 +512,15 @@ reads an optional per-release changelog from
|
||||
|
||||
## Bootstrap runbook (one-time)
|
||||
|
||||
> **Status as of v1.13.1: neither Homebrew nor Winget has been bootstrapped.**
|
||||
> `https://formulae.brew.sh/api/cask/amethyst-nostr.json` and
|
||||
> `microsoft/winget-pkgs/manifests/v/VitorPamplona/Amethyst` both 404, so
|
||||
> **Amethyst does not currently ship through either channel.** The bump
|
||||
> workflows detect this and skip with a `::warning::` instead of failing, so a
|
||||
> green release run does *not* mean Homebrew/Winget shipped. The two subsections
|
||||
> below are the work that activates them; until then treat the desktop app as
|
||||
> GitHub-Releases-only on macOS and Windows.
|
||||
|
||||
### Secrets to provision in GitHub repo settings
|
||||
|
||||
The full secret inventory is in [§ Secrets the CI needs](#secrets-the-ci-needs).
|
||||
@@ -510,8 +533,11 @@ their token type and scope:
|
||||
| `WINGET_TOKEN` | Submit Winget manifests | Classic PAT — `public_repo` — 90d expiry (dedicated bot account preferred; `vedantmgoyal9/winget-releaser` does not support fine-grained) |
|
||||
|
||||
Rotate both on a 90-day cadence. Owner: assigned via `RELEASE_OPS.md`
|
||||
or equivalent issue tracker. On rotation, paste new token and run
|
||||
`gh workflow run bump-homebrew.yml` on the most recent stable tag to verify.
|
||||
or equivalent issue tracker. On rotation, paste the new token and run
|
||||
`gh workflow run bump-homebrew.yml -f tag=<most-recent-stable-tag>` to verify
|
||||
(the `tag` input is required — that dispatch path exists for exactly this).
|
||||
Note the verification is only meaningful once the cask exists upstream;
|
||||
before that the run skips the token-using step entirely.
|
||||
|
||||
### Homebrew cask (one-time initial PR)
|
||||
|
||||
|
||||
+72
-22
@@ -14,7 +14,7 @@ specific to shipping the official Amethyst artifacts.
|
||||
|
||||
## At a glance
|
||||
|
||||
A release is one tag push that fans out to five distribution channels:
|
||||
A release is one tag push that fans out to four live distribution channels:
|
||||
|
||||
| Channel | Mechanism | Who pushes |
|
||||
|---|---|---|
|
||||
@@ -22,10 +22,11 @@ A release is one tag push that fans out to five distribution channels:
|
||||
| **Google Play** | **Manual** — download the signed AAB from the GH Release, upload in Play Console | Maintainer |
|
||||
| **F-Droid** | **Pull** — F-Droid's build server builds the `fdroid` flavor from source when it sees the new tag | F-Droid (we just maintain the recipe + metadata) |
|
||||
| **Zapstore** | `zsp publish` reads `zapstore.yaml`, signs a Nostr release event with Amethyst's nsec | Maintainer |
|
||||
| **Homebrew + Winget** | Automatic — `bump-homebrew.yml` / `bump-winget.yml` fire on stable tags | CI |
|
||||
| **Homebrew + Winget** | ⚠️ **Not shipping.** The bump workflows run, but skip: neither package exists upstream yet | Nobody (see § 3) |
|
||||
|
||||
Maven Central (the `quartz` library) also publishes automatically from the same
|
||||
workflow.
|
||||
workflow — as a *step* at the end of the `deploy-android` job, not a job of its
|
||||
own, so don't expect to find it in the run's job list.
|
||||
|
||||
---
|
||||
|
||||
@@ -61,8 +62,10 @@ workflow.
|
||||
`scripts/translators.sh --seed` with `CROWDIN_PROJECT_ID` /
|
||||
`CROWDIN_PERSONAL_TOKEN` set, which refreshes the file.)
|
||||
|
||||
3. **Publish the release-notes note on Nostr** with Amethyst's account and paste
|
||||
its event id into `amethyst/build.gradle.kts`:
|
||||
3. **Publish the release-notes note on Nostr** — *minor releases only.* In
|
||||
practice this id has only ever been bumped on `x.y.0` (1.11.0, 1.12.0,
|
||||
1.13.0); patch releases leave it pointing at their minor's note. Publish with
|
||||
Amethyst's account and paste the event id into `amethyst/build.gradle.kts`:
|
||||
```kotlin
|
||||
buildConfigField("String", "RELEASE_NOTES_ID", "\"<new-event-id-hex>\"")
|
||||
```
|
||||
@@ -85,17 +88,33 @@ workflow.
|
||||
Commit, tag, push — see [`BUILDING.md` § Release runbook](BUILDING.md#release-runbook)
|
||||
for the exact commands. The tag must equal `app` from the catalog (the workflow
|
||||
asserts this and fails fast otherwise). A clean `vMAJOR.MINOR.PATCH` tag is
|
||||
classified **stable** and triggers the Homebrew/Winget bumps; anything with a
|
||||
`-rc`/`-beta`/`-alpha`/`-dev` suffix is a prerelease and skips them.
|
||||
classified **stable** and runs the Homebrew/Winget bump workflows; anything with
|
||||
a `-rc`/`-beta`/`-alpha`/`-dev` suffix is a prerelease and skips them.
|
||||
|
||||
Heads-up on the `git push`: this repo has `git-credential-manager` configured as
|
||||
a credential helper, and it blocks on an interactive prompt (a plain
|
||||
`GIT_TERMINAL_PROMPT=0` does **not** stop it — the push just hangs). If that
|
||||
happens, push using `gh`'s helper for the one command:
|
||||
|
||||
```bash
|
||||
git -c credential.helper= -c credential.helper='!gh auth git-credential' push upstream main
|
||||
```
|
||||
|
||||
When the `Create Release Assets` workflow finishes (~25–30 min) the GH Release
|
||||
holds, per the asset-name contract:
|
||||
holds **31 assets**, per the asset-name contract:
|
||||
|
||||
- **Android:** 5 Google Play APKs + 5 F-Droid APKs + 2 AABs
|
||||
(`amethyst-googleplay-*-v…apk` / `.aab`, `amethyst-fdroid-*-v…apk` / `.aab`)
|
||||
- **Desktop:** 8 assets (DMG/MSI/DEB/RPM/AppImage/zip/tar.gz)
|
||||
- **CLI:** the `amy` artifacts
|
||||
- **Maven Central:** `com.vitorpamplona.quartz:quartz:<version>` published
|
||||
- **Android (13):** 5 Google Play APKs + 5 F-Droid APKs + 2 AABs + the F-Droid
|
||||
`.apks` set for Accrescent
|
||||
(`amethyst-googleplay-*-v…apk` / `.aab`, `amethyst-fdroid-*-v…apk` / `.aab` / `.apks`)
|
||||
- **Desktop (8):** DMG (macOS **arm64 only** — there is no Intel DMG),
|
||||
MSI + zip, DEB, RPM, AppImage, flatpak, tar.gz
|
||||
- **CLI (5):** the `amy` artifacts
|
||||
- **Relay (5):** the `geode` artifacts, plus the geode Docker image
|
||||
- **Maven Central:** `com.vitorpamplona.quartz:quartz:<version>` published.
|
||||
`repo1.maven.org` lags the publish by tens of minutes — a 404 right after the
|
||||
run is normal. Confirm the step's log says "Deployment is being published to
|
||||
Maven Central", and compare against the *previous* version's POM before
|
||||
concluding anything is broken.
|
||||
|
||||
---
|
||||
|
||||
@@ -165,11 +184,36 @@ RELAY_URLS="wss://relay.zapstore.dev,wss://relay.damus.io,wss://nos.lol,wss://vi
|
||||
Keep `wss://relay.zapstore.dev` in the list — that is the relay the Zapstore app
|
||||
itself reads from.
|
||||
|
||||
### Homebrew + Winget — automatic
|
||||
`bump-homebrew.yml` and `bump-winget.yml` fire on stable tags and open PRs
|
||||
against `Homebrew/homebrew-cask` (cask `amethyst-nostr`) and
|
||||
`microsoft/winget-pkgs` (`VitorPamplona.Amethyst`). No action unless one fails —
|
||||
then see BUILDING.md § Bootstrap and § Incident response.
|
||||
### Homebrew + Winget — ⚠️ not shipping yet
|
||||
|
||||
`bump-homebrew.yml` and `bump-winget.yml` are wired to open PRs against
|
||||
`Homebrew/homebrew-cask` (cask `amethyst-nostr`) and `microsoft/winget-pkgs`
|
||||
(`VitorPamplona.Amethyst`) — but **neither package has ever been submitted
|
||||
upstream**, so both workflows detect that and skip with a `::warning::`. As of
|
||||
**v1.13.1** these two channels deliver nothing; macOS and Windows users get the
|
||||
desktop app from GitHub Releases only.
|
||||
|
||||
Two separate faults kept this invisible until v1.13.1, both now fixed:
|
||||
|
||||
1. **The workflows never ran at all** — for *any* release. They triggered on
|
||||
`release: types: [released]`, and GitHub does not raise workflow-triggering
|
||||
events for a release created by `GITHUB_TOKEN`, which is exactly how
|
||||
`create-release.yml` creates it. They now trigger on `workflow_run` after
|
||||
`Create Release Assets` succeeds, which also fixes a latent race — the old
|
||||
event fired while assets were still uploading.
|
||||
2. **Nothing exists upstream to bump.** `brew bump-cask-pr` and
|
||||
`winget-releaser` can only *update* an existing package. The first
|
||||
submission is a manual, human-reviewed PR: BUILDING.md § Homebrew cask
|
||||
(one-time initial PR) and § Winget (one-time initial submission).
|
||||
|
||||
Until someone does that bootstrap, a green release run means the bump workflows
|
||||
*skipped cleanly* — not that Homebrew/Winget shipped. Check the run's warnings
|
||||
if you want to confirm which case you're in.
|
||||
|
||||
The two `amy` / `geode` Homebrew **formula** workflows are different: they only
|
||||
sync the in-repo reference `.rb` files and open a PR against *this* repo, so
|
||||
they do real work on every release. Merge those PRs to keep the formulae ready
|
||||
for their eventual homebrew-core submission.
|
||||
|
||||
---
|
||||
|
||||
@@ -222,13 +266,19 @@ Owner assignments and rotation reminders live with the team (issue tracker).
|
||||
|
||||
## 6. Post-release verification
|
||||
|
||||
- [ ] GH Release: expected asset count, Intel + ARM DMGs, sizes sane.
|
||||
- [ ] Maven Central: `quartz:<version>` resolves (allow propagation time).
|
||||
- [ ] GH Release: 31 assets, sizes sane, and the asset-name set matches the
|
||||
previous release (see the `diff` one-liner in BUILDING.md § Release
|
||||
runbook). macOS is arm64-only — do **not** look for an Intel DMG.
|
||||
- [ ] Maven Central: `quartz:<version>` resolves (allow tens of minutes of
|
||||
propagation; the publish step's log is the authoritative signal).
|
||||
- [ ] Play Console: rollout started, no policy rejection.
|
||||
- [ ] Zapstore: release event visible.
|
||||
- [ ] F-Droid: new version detected (may lag days).
|
||||
- [ ] Homebrew + Winget bump PRs opened (stable only).
|
||||
- [ ] In-app "Release Notes" link opens the note matching `RELEASE_NOTES_ID`.
|
||||
- [ ] `amy` / `geode` Homebrew formula-sync PRs opened against this repo — merge them.
|
||||
- [ ] Homebrew + Winget: **expected to skip** until bootstrapped (§ 3). If they
|
||||
ever start opening real PRs, that means someone landed the bootstrap.
|
||||
- [ ] In-app "Release Notes" link opens the note matching `RELEASE_NOTES_ID`
|
||||
(only bumped on minor releases — patches keep pointing at the x.y.0 note).
|
||||
- [ ] Push still works on a `play` build (only if the push contract changed —
|
||||
see § 4); UnifiedPush still works on an `fdroid` build.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user