Release #816
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Continuous release pipeline for Homeboy. | |
| # | |
| # Runs every 15 minutes (and on manual dispatch). Checks for releasable | |
| # conventional commits since the last tag. If found: | |
| # 1. Quality gate (audit, lint, test) | |
| # 2. Version bump + changelog generation from conventional commits | |
| # 3. Cross-platform binary builds via cargo-dist | |
| # 4. Publish to GitHub Releases, crates.io, and Homebrew | |
| # 5. Auto-refactor (post-release, never blocks the release) | |
| # | |
| # No human input needed — version is computed from commit types: | |
| # fix: → patch, feat: → minor, BREAKING CHANGE → major | |
| # chore:/ci:/docs:/test: → no release | |
| name: Release | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| on: | |
| schedule: | |
| - cron: '*/15 * * * *' | |
| workflow_dispatch: | |
| inputs: | |
| dry-run: | |
| description: 'Preview the release without making changes' | |
| type: boolean | |
| default: false | |
| # Only one release pipeline at a time. If a cron fires while a release | |
| # is already running, it queues (never cancels). The queued run starts | |
| # after the first finishes and its check job exits in seconds because | |
| # HEAD is already tagged — zero wasted work. | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| # ── Step 1: Check for releasable commits ── | |
| # Fast exit if nothing to release (most cron runs hit this). | |
| check: | |
| name: Check for releasable commits | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should-release: ${{ steps.check.outputs.should-release }} | |
| bump-type: ${{ steps.release-check.outputs.release-bump-type }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Restore last-failed SHA to avoid retrying the same broken commit | |
| - name: Restore failure cache | |
| id: failure-cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: .release-last-failed | |
| key: release-last-failed-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| release-last-failed-${{ github.ref_name }}- | |
| - name: Dry-run release check | |
| id: release-check | |
| uses: Extra-Chill/homeboy-action@v2 | |
| with: | |
| source: '.' | |
| commands: release | |
| release-dry-run: 'true' | |
| - name: Decide whether to release | |
| id: check | |
| run: | | |
| # Skip if HEAD matches the last failed attempt (no new commits to retry) | |
| HEAD_SHA="$(git rev-parse HEAD)" | |
| if [ -f .release-last-failed ]; then | |
| LAST_FAILED="$(tr -d '[:space:]' < .release-last-failed)" | |
| if [ "${HEAD_SHA}" = "${LAST_FAILED}" ]; then | |
| echo "::notice::HEAD ${HEAD_SHA:0:8} matches last failed release attempt — skipping until new commits" | |
| echo "should-release=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| fi | |
| RELEASE_VERSION="${{ steps.release-check.outputs.release-version }}" | |
| BUMP_TYPE="${{ steps.release-check.outputs.release-bump-type }}" | |
| if [ -z "${RELEASE_VERSION}" ]; then | |
| echo "should-release=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No releasable commits at HEAD ${HEAD_SHA:0:8}" | |
| else | |
| echo "should-release=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Release dry-run predicts v${RELEASE_VERSION} (${BUMP_TYPE})" | |
| fi | |
| # ── Step 2: Build once ── | |
| # Compile homeboy from source once and share the binary with all | |
| # quality gate jobs. Eliminates 3× redundant cargo builds. | |
| gate-build: | |
| name: Build | |
| needs: check | |
| if: needs.check.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-release-gate-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-release-gate- | |
| - name: Build homeboy | |
| run: cargo build --release | |
| - name: Upload binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: homeboy-binary | |
| path: target/release/homeboy | |
| retention-days: 1 | |
| # ── Step 3: Quality gate (read-only checks + auto-refactor) ── | |
| # Unlike PR CI (scoped to changed files), release quality gate runs | |
| # unscoped against the entire codebase. | |
| # Serial pipeline: audit → lint → test → release. | |
| # All quality gates are read-only (autofix: false). The dedicated | |
| # auto-refactor job at the end owns all code changes. | |
| gate-audit: | |
| name: Audit | |
| needs: | |
| - check | |
| - gate-build | |
| if: needs.check.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download homeboy binary | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: homeboy-binary | |
| path: .homeboy-bin | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.HOMEBOY_APP_ID }} | |
| private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} | |
| - uses: Extra-Chill/homeboy-action@v2 | |
| with: | |
| binary-path: .homeboy-bin/homeboy | |
| commands: audit | |
| autofix: 'false' | |
| app-token: ${{ steps.app-token.outputs.token || '' }} | |
| gate-lint: | |
| name: Lint | |
| needs: | |
| - check | |
| - gate-build | |
| - gate-audit | |
| if: needs.check.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download homeboy binary | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: homeboy-binary | |
| path: .homeboy-bin | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.HOMEBOY_APP_ID }} | |
| private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} | |
| - uses: Extra-Chill/homeboy-action@v2 | |
| with: | |
| binary-path: .homeboy-bin/homeboy | |
| commands: lint | |
| autofix: 'false' | |
| app-token: ${{ steps.app-token.outputs.token || '' }} | |
| gate-test: | |
| name: Test | |
| needs: | |
| - check | |
| - gate-build | |
| - gate-lint | |
| if: needs.check.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download homeboy binary | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: homeboy-binary | |
| path: .homeboy-bin | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.HOMEBOY_APP_ID }} | |
| private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} | |
| - uses: Extra-Chill/homeboy-action@v2 | |
| with: | |
| binary-path: .homeboy-bin/homeboy | |
| commands: test | |
| autofix: 'false' | |
| app-token: ${{ steps.app-token.outputs.token || '' }} | |
| gate-refactor: | |
| name: Auto-refactor | |
| needs: | |
| - check | |
| - gate-build | |
| - gate-audit | |
| - gate-lint | |
| - gate-test | |
| if: ${{ always() && needs.check.outputs.should-release == 'true' && needs.gate-build.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download homeboy binary | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: homeboy-binary | |
| path: .homeboy-bin | |
| - name: Prepare binary | |
| run: chmod +x .homeboy-bin/homeboy | |
| # Rust toolchain needed for cargo fmt (called by lint fixer in autofix) | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.HOMEBOY_APP_ID }} | |
| private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} | |
| # Delegate to homeboy-action for consistent autofix behavior: | |
| # structured commit messages, revert detection, loop guards, | |
| # baseline management, re-run verification, and PR creation. | |
| - name: Run autofix via homeboy-action | |
| uses: Extra-Chill/homeboy-action@v2 | |
| with: | |
| binary-path: .homeboy-bin/homeboy | |
| commands: audit,lint,test | |
| autofix: 'true' | |
| autofix-mode: always | |
| autofix-open-pr: 'true' | |
| app-token: ${{ steps.app-token.outputs.token }} | |
| # ── Step 3: Version bump + changelog + tag ── | |
| prepare: | |
| name: Prepare Release | |
| needs: | |
| - check | |
| - gate-build | |
| - gate-lint | |
| - gate-test | |
| - gate-audit | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release-version: ${{ steps.release.outputs.release-version }} | |
| release-tag: ${{ steps.release.outputs.release-tag }} | |
| released: ${{ steps.release.outputs.released }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.HOMEBOY_APP_ID }} | |
| private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} | |
| # Rust toolchain needed so run-release.sh can regenerate Cargo.lock | |
| # after bumping Cargo.toml version | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Extra-Chill/homeboy-action@v2 | |
| id: release | |
| with: | |
| source: '.' | |
| commands: release | |
| release-dry-run: ${{ inputs.dry-run || 'false' }} | |
| app-token: ${{ steps.app-token.outputs.token || '' }} | |
| # ── Step 4: Build cross-platform binaries ── | |
| plan: | |
| name: Plan Build Matrix | |
| needs: prepare | |
| if: needs.prepare.outputs.released == 'true' | |
| runs-on: ubuntu-22.04 | |
| outputs: | |
| val: ${{ steps.plan.outputs.manifest }} | |
| tag-flag: ${{ format('--tag={0}', needs.prepare.outputs.release-tag) }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release-tag }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install dist | |
| shell: bash | |
| run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" | |
| - name: Cache dist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/dist | |
| - id: plan | |
| run: | | |
| dist host --steps=create --tag=${{ needs.prepare.outputs.release-tag }} --output-format=json > plan-dist-manifest.json | |
| echo "dist ran successfully" | |
| cat plan-dist-manifest.json | |
| echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" | |
| - name: Upload dist-manifest.json | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-plan-dist-manifest | |
| path: plan-dist-manifest.json | |
| build-local-artifacts: | |
| name: build (${{ join(matrix.targets, ', ') }}) | |
| needs: | |
| - prepare | |
| - plan | |
| if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} | |
| runs-on: ${{ matrix.runner }} | |
| container: ${{ matrix.container && matrix.container.image || null }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json | |
| steps: | |
| - name: enable windows longpaths | |
| run: | | |
| git config --global core.longpaths true | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release-tag }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install Rust non-interactively if not already installed | |
| if: ${{ matrix.container }} | |
| run: | | |
| if ! command -v cargo > /dev/null 2>&1; then | |
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| fi | |
| - name: Install dist | |
| run: ${{ matrix.install_dist.run }} | |
| - name: Fetch local artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - name: Install dependencies | |
| run: | | |
| ${{ matrix.packages_install }} | |
| - name: Build artifacts | |
| run: | | |
| dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json | |
| echo "dist ran successfully" | |
| - id: cargo-dist | |
| name: Post-build | |
| shell: bash | |
| run: | | |
| echo "paths<<EOF" >> "$GITHUB_OUTPUT" | |
| dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| cp dist-manifest.json "$BUILD_MANIFEST_NAME" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-build-local-${{ join(matrix.targets, '_') }} | |
| path: | | |
| ${{ steps.cargo-dist.outputs.paths }} | |
| ${{ env.BUILD_MANIFEST_NAME }} | |
| build-global-artifacts: | |
| needs: | |
| - prepare | |
| - plan | |
| - build-local-artifacts | |
| runs-on: ubuntu-22.04 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release-tag }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install cached dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/ | |
| - run: chmod +x ~/.cargo/bin/dist | |
| - name: Fetch local artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - id: cargo-dist | |
| shell: bash | |
| run: | | |
| dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json | |
| echo "dist ran successfully" | |
| echo "paths<<EOF" >> "$GITHUB_OUTPUT" | |
| jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| cp dist-manifest.json "$BUILD_MANIFEST_NAME" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-build-global | |
| path: | | |
| ${{ steps.cargo-dist.outputs.paths }} | |
| ${{ env.BUILD_MANIFEST_NAME }} | |
| # ── Step 5: Publish ── | |
| host: | |
| name: Create GitHub Release | |
| needs: | |
| - prepare | |
| - plan | |
| - build-local-artifacts | |
| - build-global-artifacts | |
| if: ${{ always() && needs.plan.result == 'success' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| runs-on: ubuntu-22.04 | |
| outputs: | |
| val: ${{ steps.host.outputs.manifest }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release-tag }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install cached dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/ | |
| - run: chmod +x ~/.cargo/bin/dist | |
| - name: Fetch artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - id: host | |
| shell: bash | |
| run: | | |
| dist host --tag=${{ needs.prepare.outputs.release-tag }} --steps=upload --steps=release --output-format=json > dist-manifest.json | |
| echo "artifacts uploaded and released successfully" | |
| cat dist-manifest.json | |
| echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" | |
| - name: Upload dist-manifest.json | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-dist-manifest | |
| path: dist-manifest.json | |
| - name: Download GitHub Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Cleanup | |
| run: | | |
| rm -f artifacts/*-dist-manifest.json | |
| - name: Create GitHub Release | |
| env: | |
| PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" | |
| ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" | |
| ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" | |
| RELEASE_COMMIT: "${{ github.sha }}" | |
| run: | | |
| echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt | |
| TAG="${{ needs.prepare.outputs.release-tag }}" | |
| if gh release view "$TAG" > /dev/null 2>&1; then | |
| echo "Release $TAG already exists. Uploading assets..." | |
| gh release upload "$TAG" artifacts/* --clobber | |
| else | |
| echo "Creating release $TAG..." | |
| gh release create "$TAG" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* | |
| fi | |
| publish-homebrew-formula: | |
| needs: | |
| - prepare | |
| - plan | |
| - host | |
| runs-on: ubuntu-22.04 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PLAN: ${{ needs.plan.outputs.val }} | |
| GITHUB_USER: "axo bot" | |
| GITHUB_EMAIL: "admin+bot@axo.dev" | |
| if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: true | |
| repository: "Extra-Chill/homebrew-tap" | |
| token: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| - name: Fetch homebrew formulae | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: Formula/ | |
| merge-multiple: true | |
| - name: Commit formula files | |
| run: | | |
| git config --global user.name "${GITHUB_USER}" | |
| git config --global user.email "${GITHUB_EMAIL}" | |
| for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do | |
| filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) | |
| name=$(echo "$filename" | sed "s/\.rb$//") | |
| version=$(echo "$release" | jq .app_version --raw-output) | |
| export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" | |
| brew update | |
| brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true | |
| git add "Formula/${filename}" | |
| git commit -m "${name} ${version}" | |
| done | |
| git push | |
| publish-crates: | |
| needs: | |
| - prepare | |
| - plan | |
| - host | |
| runs-on: ubuntu-22.04 | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| if: ${{ env.CARGO_REGISTRY_TOKEN != '' }} | |
| with: | |
| ref: ${{ needs.prepare.outputs.release-tag }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| if: ${{ env.CARGO_REGISTRY_TOKEN != '' }} | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Publish to crates.io | |
| if: ${{ env.CARGO_REGISTRY_TOKEN != '' }} | |
| shell: bash | |
| run: | | |
| cargo publish --locked || { | |
| if cargo search homeboy --limit 1 | grep -q '^homeboy = '; then | |
| echo "Version already published on crates.io; skipping" | |
| exit 0 | |
| fi | |
| exit 1 | |
| } | |
| announce: | |
| needs: | |
| - plan | |
| - host | |
| - publish-homebrew-formula | |
| - publish-crates | |
| if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') && (needs.publish-crates.result == 'skipped' || needs.publish-crates.result == 'success') }} | |
| runs-on: ubuntu-22.04 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| submodules: recursive | |
| # ── Record failed SHA to prevent retry loops ── | |
| # If gate or prepare failed, cache HEAD SHA so the next cron run | |
| # skips retrying until new commits arrive. | |
| record-failure: | |
| name: Record failure | |
| needs: | |
| - check | |
| - gate-build | |
| - gate-lint | |
| - gate-test | |
| - gate-audit | |
| - prepare | |
| if: ${{ always() && needs.check.outputs.should-release == 'true' && (needs.gate-build.result == 'failure' || needs.gate-lint.result == 'failure' || needs.gate-test.result == 'failure' || needs.gate-audit.result == 'failure' || needs.prepare.result == 'failure') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Save failed SHA | |
| run: git rev-parse HEAD > .release-last-failed | |
| - name: Cache failed SHA | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: .release-last-failed | |
| key: release-last-failed-${{ github.ref_name }}-${{ github.sha }} | |
| # ── Clear failure cache on success ── | |
| # If release succeeded, clear the failure cache so it doesn't | |
| # interfere with future runs. | |
| clear-failure: | |
| name: Clear failure cache | |
| needs: | |
| - prepare | |
| if: ${{ needs.prepare.outputs.released == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Clear failure cache | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Delete release-last-failed cache entries for this ref only | |
| gh cache list --json id,key --jq '.[] | select(.key | startswith("release-last-failed-${{ github.ref_name }}-")) | .id' | while read -r id; do | |
| gh cache delete "$id" 2>/dev/null || true | |
| done |