diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1778b48e..0b417567 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,6 +16,12 @@ jobs: contents: write packages: write runs-on: ubuntu-latest + outputs: + # Gates the docs deploy on the release actually existing rather than on this + # job's overall conclusion, so a failure in a downstream distribution channel + # (Homebrew, ghcr) cannot skip the schema publish. + published: ${{ steps.released.outputs.published }} + version: ${{ steps.version.outputs.version }} steps: - uses: actions/checkout@v7 with: @@ -34,13 +40,26 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + + # Idempotent so a release that failed partway (e.g. a bad tap credential) + # can be re-dispatched with the same tag instead of dying here. - name: Create Tag if: ${{ github.event.inputs.tag != '' }} - run: | - git tag ${{ github.event.inputs.tag }} - git push origin ${{ github.event.inputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.inputs.tag }} + run: | + set -euo pipefail + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + echo "Tag ${TAG} already exists locally - reusing" + elif git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} already exists on origin - fetching" + git fetch origin "refs/tags/${TAG}:refs/tags/${TAG}" + else + git tag "${TAG}" + git push origin "${TAG}" + fi + - name: Extract version from tag id: version run: | @@ -51,8 +70,10 @@ jobs: fi echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "Releasing version: ${VERSION}" + - name: Install release tools run: go install github.com/goreleaser/goreleaser/v2@v2.12.5 + - uses: flowexec/action@v1 with: executable: 'publish release --param VERSION=${{ steps.version.outputs.version }}' @@ -62,15 +83,34 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_FLOW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_FLOW_GITHUB_TOKEN }} + # Records whether the GitHub release exists, regardless of what happened + # after it was cut. Runs even on failure so the docs gate is accurate. + - name: Check release published + id: released + if: always() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh release view "${{ steps.version.outputs.version }}" \ + --repo "${{ github.repository }}" >/dev/null 2>&1; then + echo "published=true" >> $GITHUB_OUTPUT + echo "Release ${{ steps.version.outputs.version }} is published." + else + echo "published=false" >> $GITHUB_OUTPUT + echo "Release ${{ steps.version.outputs.version }} was NOT published." + fi + # Publishing the docs is part of releasing: the site serves the JSON schemas # that editors validate flowfiles against, so skipping it ships a release whose # schema the world cannot see. This runs here rather than off the tag push # because a tag pushed with GITHUB_TOKEN does not trigger workflows. release-docs: needs: release-binary - # Only for a dispatched release. A tag pushed by a human triggers + # Gate on the release having been published, not on release-binary's overall + # conclusion - a Homebrew or ghcr failure must not stop the schemas shipping. + # Only for a dispatched release: a tag pushed by a human triggers # release-docs.yaml on its own, and calling it here too would deploy twice. - if: github.event_name == 'workflow_dispatch' + if: ${{ always() && github.event_name == 'workflow_dispatch' && needs.release-binary.outputs.published == 'true' }} permissions: contents: read uses: ./.github/workflows/release-docs.yaml