From 089c51e48af9e943f918149e5cc088609f1c62dc Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Thu, 12 Feb 2026 14:45:00 -0800 Subject: [PATCH] Fix release automation This uses the new clickops-style release automation I've been working with, as well as switching over to more durable GitHub App authentication. Also trying out Cargo Trusted Publishing with this one! See: https://github.com/9999years/command-error/pull/31 --- .github/workflows/label-prs.yaml | 59 ----------------- .github/workflows/release.yaml | 92 +++++++++++++++----------- .github/workflows/version.yaml | 109 ++++++++++--------------------- 3 files changed, 87 insertions(+), 173 deletions(-) delete mode 100644 .github/workflows/label-prs.yaml diff --git a/.github/workflows/label-prs.yaml b/.github/workflows/label-prs.yaml deleted file mode 100644 index 89064b6..0000000 --- a/.github/workflows/label-prs.yaml +++ /dev/null @@ -1,59 +0,0 @@ ---- -# This workflow runs when PRs are opened and labels them `patch`. - -on: - pull_request_target: - types: - - opened - - reopened - -name: Label PRs with `patch` by default - -jobs: - # It seems like GitHub doesn't correctly populate the PR labels for the - # `opened` event, so we use the GitHub API to fetch them separately. - - get-labels: - name: Get PR labels - runs-on: ubuntu-latest - if: > - ! startsWith(github.event.pull_request.head.ref, 'release/') - outputs: - labels: ${{ steps.get-labels.outputs.labels }} - steps: - - name: Get PR labels from GitHub API - id: get-labels - env: - REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - LABELS=$(gh api "repos/$REPO/issues/$NUMBER/labels") - echo "PR #$NUMBER is labeled with: $LABELS" - echo "labels=$LABELS" >> "$GITHUB_OUTPUT" - - label: - name: Label PR with `patch` - needs: - - get-labels - permissions: - contents: read - pull-requests: write - # This has been endlessly frustrating. I have no clue why I've had such bad - # luck with this particular `if`, especially when I use the same logic - # elsewhere in these actions and it seems to Just Work there. Misery! - # Misery for Rebecca for 1000 years!!! - # - # total_hours_wasted_here = 4 - if: > - ! ( contains(fromJSON(needs.get-labels.outputs.labels).*.name, 'release') - || contains(fromJSON(needs.get-labels.outputs.labels).*.name, 'minor') - || contains(fromJSON(needs.get-labels.outputs.labels).*.name, 'major') - ) - - runs-on: ubuntu-latest - steps: - - name: Label PR with `patch` - uses: actions/labeler@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e1d419c..9474405 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,6 +8,7 @@ on: - closed branches: - main + workflow_dispatch: name: Build and publish a release @@ -15,19 +16,24 @@ jobs: # We make `if_merged` a `needs:` of the other jobs here to only run this # workflow on merged PRs. if_merged: - name: Check that PR was merged and not closed - if: github.event.pull_request.merged == true - && contains(github.event.pull_request.labels.*.name, 'release') - runs-on: ubuntu-latest permissions: + issues: write pull-requests: write + name: Check that PR was merged and not closed + if: github.event_name == 'workflow_dispatch' + || ( + github.event.pull_request.merged == true + && contains(github.event.pull_request.labels.*.name, 'release') + ) + runs-on: ubuntu-latest steps: - run: | echo "This is a canonical hack to run GitHub Actions on merged PRs" echo "See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-pull_request-workflow-when-a-pull-request-merges" - name: Comment on PR with link to this action - uses: peter-evans/create-or-update-comment@v4 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + if: github.event_name == 'pull_request' with: issue-number: ${{ github.event.pull_request.number }} body: | @@ -40,21 +46,17 @@ jobs: needs: if_merged runs-on: ubuntu-latest outputs: - version: ${{ steps.get_cargo_metadata.outputs.version }} + version: ${{ steps.get_version_number.outputs.version }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - - uses: cachix/install-nix-action@v22 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - extra_nix_config: | - extra-experimental-features = nix-command flakes - accept-flake-config = true + - uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0 - name: Get version number - id: get_cargo_metadata - run: echo "version=$(nix run .#get-crate-version)" >> "$GITHUB_OUTPUT" + id: get_version_number + run: | + echo "version=$(nix eval --raw ".#packages.x86_64-linux.default.version")" >> "$GITHUB_OUTPUT" build: name: Release Build @@ -68,22 +70,22 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] + environment: release + permissions: + id-token: write # See: rust-lang/crates-io-auth-action steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + # TODO: What am I doing here? Is there a reason I'm not using Nix? Is + # it for `x86_64-darwin` builds? It must be. Does anyone even use those? - name: Install rustup uses: dtolnay/rust-toolchain@stable if: runner.os == 'macOS' with: target: x86_64-apple-darwin, aarch64-apple-darwin - - uses: cachix/install-nix-action@v22 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - extra_nix_config: | - extra-experimental-features = nix-command flakes - accept-flake-config = true + - uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0 - name: Log versions if: runner.os == 'macOS' @@ -138,7 +140,7 @@ jobs: target/release/git-gr-aarch64-linux - name: Upload macOS executable - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: runner.os == 'macOS' with: name: macos @@ -146,7 +148,7 @@ jobs: target/release/git-gr-macos - name: Upload Linux executables - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: runner.os == 'Linux' with: name: linux @@ -154,10 +156,13 @@ jobs: target/release/git-gr-x86_64-linux target/release/git-gr-aarch64-linux + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + - name: Publish to crates.io if: runner.os == 'Linux' env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} run: | nix run .#cargo -- publish --no-verify @@ -170,13 +175,22 @@ jobs: - version permissions: contents: write + issues: write pull-requests: write steps: + # See: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + id: generate_token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Tag the release - uses: mathieudutour/github-tag-action@v6.2 + uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b # v6.2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - commit_sha: ${{ github.event.pull_request.merge_commit_sha }} + github_token: ${{ steps.generate_token.outputs.token }} + commit_sha: ${{ github.sha }} + # Note: This action automatically applies a prefix for us! custom_tag: ${{ needs.version.outputs.version }} - name: Download artifacts @@ -186,31 +200,33 @@ jobs: # # For example, the following artifact: # - # - uses: actions/upload-artifact@v4 + # - uses: actions/upload-artifact@v5 # with: # name: linux - # path: target/release/ghciwatch-aarch64-linux + # path: target/release/git-gr-aarch64-linux # - # will be downloaded to `linux/ghciwatch-aarch64-linux`. - uses: actions/download-artifact@v4 + # will be downloaded to `linux/git-gr-aarch64-linux`. + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - name: Create release id: create_release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: + token: ${{ steps.generate_token.outputs.token }} draft: false prerelease: false generate_release_notes: true tag_name: v${{ needs.version.outputs.version }} files: | - linux/* - macos/* + macos/git-gr-macos + linux/git-gr-x86_64-linux + linux/git-gr-aarch64-linux - name: Comment on PR with link to the release - uses: peter-evans/create-or-update-comment@v4 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + if: github.event_name == 'pull_request' with: + token: ${{ steps.generate_token.outputs.token }} issue-number: ${{ github.event.pull_request.number }} body: | [Release ${{ needs.version.outputs.version }}][release] was built and published successfully! diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml index 00a394e..b273a88 100644 --- a/.github/workflows/version.yaml +++ b/.github/workflows/version.yaml @@ -1,99 +1,65 @@ --- -# This workflow runs when PRs labeled `major`, `minor`, or `patch` are closed -# and increments version numbers. Then, it opens a PR labeled `release` for the -# changes. When that PR is merged, a release is created (see `release.yaml`). - on: - pull_request: - types: - - closed - branches: - - main + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type to perform' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major name: Update versions and create release PR jobs: - # We make `if_merged` a `needs:` of the other jobs here to only run this - # workflow on merged PRs. - if_merged: - name: Check that PR was merged and not closed - if: github.event.pull_request.merged == true - && ( contains(github.event.pull_request.labels.*.name, 'major') - || contains(github.event.pull_request.labels.*.name, 'minor') - || contains(github.event.pull_request.labels.*.name, 'patch') - ) - runs-on: ubuntu-latest - steps: - - run: | - echo "This is a canonical hack to run GitHub Actions on merged PRs" - echo "See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-pull_request-workflow-when-a-pull-request-merges" - - bump_type: - name: Determine version bump type - needs: if_merged - runs-on: ubuntu-latest - outputs: - bump_type: ${{ steps.bump_type.outputs.bump_type }} - steps: - - name: Set output - id: bump_type - env: - is_major: ${{ contains(github.event.pull_request.labels.*.name, 'major') }} - is_minor: ${{ contains(github.event.pull_request.labels.*.name, 'minor') }} - is_patch: ${{ contains(github.event.pull_request.labels.*.name, 'patch') }} - run: | - if [[ "$is_major" == "true" ]]; then - echo "bump_type=major" >> "$GITHUB_OUTPUT" - elif [[ "$is_minor" == "true" ]]; then - echo "bump_type=minor" >> "$GITHUB_OUTPUT" - elif [[ "$is_patch" == "true" ]]; then - echo "bump_type=patch" >> "$GITHUB_OUTPUT" - fi - version: name: Bump version and create release PR - permissions: - pull-requests: write - needs: - - if_merged - - bump_type runs-on: ubuntu-latest steps: + # See: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + id: generate_token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: # Fetch all history/tags (needed to compute versions) fetch-depth: 0 - - uses: cachix/install-nix-action@v22 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - extra_nix_config: | - extra-experimental-features = nix-command flakes - accept-flake-config = true + - uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0 - name: Get old version number id: old_cargo_metadata - run: echo "version=$(nix run .#get-crate-version)" >> "$GITHUB_OUTPUT" + run: | + echo "version=$(nix eval --raw ".#packages.x86_64-linux.default.version")" >> "$GITHUB_OUTPUT" - name: Increment `Cargo.toml` version - run: nix run .#make-release-commit -- ${{ needs.bump_type.outputs.bump_type }} + run: nix run .#make-release-commit -- ${{ inputs.bump_type }} - name: Get new version number id: new_cargo_metadata - run: echo "version=$(nix run .#get-crate-version)" >> "$GITHUB_OUTPUT" + run: | + echo "version=$(nix eval --raw ".#packages.x86_64-linux.default.version")" >> "$GITHUB_OUTPUT" - name: Create release PR id: release_pr - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 with: - # We push with the repo-scoped GitHub token to avoid branch - # protections. This token is tied to my account (@9999years) which is - # excluded from branch protection restrictions. - # # I'd love a better way of implementing this but GitHub doesn't have # one: https://github.com/github-community/community/discussions/13836 - token: ${{ secrets.REPO_GITHUB_TOKEN }} + # + # Also, PRs created with the default `secrets.GITHUB_TOKEN` won't + # trigger `pull_request` workflows, so regular CI won't run either. + # See: https://github.com/orgs/community/discussions/65321 + # + # See: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens + token: ${{ steps.generate_token.outputs.token }} branch: release/${{ steps.new_cargo_metadata.outputs.version }} delete-branch: true base: main @@ -102,12 +68,3 @@ jobs: Update version to ${{ steps.new_cargo_metadata.outputs.version }} with [cargo-release](https://github.com/crate-ci/cargo-release). Merge this PR to build and publish a new release. labels: release - - - name: Comment on PR with link to release PR - uses: peter-evans/create-or-update-comment@v3 - with: - issue-number: ${{ github.event.pull_request.number }} - body: | - [A PR to release these changes has been created, bumping the version from ${{ steps.old_cargo_metadata.outputs.version }} to ${{ steps.new_cargo_metadata.outputs.version }}.][pr] - - [pr]: ${{ steps.release_pr.outputs.pull-request-url }}