diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4531c9a91..e6a1a774f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -47,105 +47,7 @@ jobs: fi gh release edit "$TAG" --draft=false --latest - homebrew: - name: Update Homebrew tap formula - needs: publish - runs-on: ubuntu-latest - # Only update for real releases. `success()` keeps the implicit `needs: publish` - # success gate — a custom `if:` that omits it would replace that gate and let - # this job run even when `publish` failed. The remaining checks mirror the - # publish job's release-trigger gating. - if: success() && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && startsWith(github.event.workflow_run.head_branch, 'release/v') - permissions: - contents: write # Push the formula update branch. - pull-requests: write # Open and auto-merge the formula PR. - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: main - # Do not persist any token in the runner's git config: the push/PR step - # below authenticates explicitly, so the (potentially powerful) formula - # token is never left available to `pnpm install`/`tsx` in between. - persist-credentials: false - - - name: Configure git - uses: ./.github/actions/git-config - - - name: Setup mise - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v2 - with: - experimental: true - - - name: Setup Takumi Guard npm registry - uses: flatt-security/setup-takumi-guard-npm@9a5d797c2085b6326d3a2985c08e3a114b9b88c1 # v1 - - - name: Install dependencies - run: pnpm install --ignore-scripts - - - name: Determine tag - id: tag - env: - HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - run: | - TAG="${HEAD_BRANCH#release/}" - if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "::error::Invalid release tag derived from branch: $HEAD_BRANCH" - exit 1 - fi - { - echo "tag=$TAG" - echo "version=${TAG#v}" - } >> "$GITHUB_OUTPUT" - - - name: Download release checksums - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ steps.tag.outputs.tag }} - run: gh release download "$TAG" --pattern SHA256SUMS --output SHA256SUMS - - - name: Regenerate formula - env: - VERSION: ${{ steps.tag.outputs.version }} - run: | - pnpm exec tsx scripts/generate-homebrew-formula.ts "$VERSION" SHA256SUMS Formula/rulesync.rb - rm -f SHA256SUMS - - - name: Open auto-merge pull request - env: - # Prefer a maintainer PAT when present: a branch-protected `main` blocks - # auto-merge of a GITHUB_TOKEN-authored PR because that token cannot - # trigger the required status checks. Without the PAT we fall back to - # GITHUB_TOKEN and leave the PR open for a manual merge. - GH_TOKEN: ${{ secrets.HOMEBREW_FORMULA_TOKEN || secrets.GITHUB_TOKEN }} - TAG: ${{ steps.tag.outputs.tag }} - run: | - if git diff --quiet -- Formula/rulesync.rb; then - echo "Formula already up to date for ${TAG}; nothing to do." - exit 0 - fi - - BRANCH="homebrew-formula/${TAG}" - git switch -c "$BRANCH" - git add Formula/rulesync.rb - git commit -m "chore: update Homebrew formula to ${TAG}" - # Authenticate the push here only (checkout persisted no credentials). - git push --force-with-lease \ - "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ - "HEAD:refs/heads/${BRANCH}" - - # Reuse an existing open PR for this branch so re-running the job for the - # same tag is idempotent rather than failing in `gh pr create`. - PR_URL="$(gh pr list --head "$BRANCH" --base main --state open --json url --jq '.[0].url // empty')" - if [ -z "$PR_URL" ]; then - PR_URL="$(gh pr create --base main --head "$BRANCH" \ - --title "chore: update Homebrew formula to ${TAG}" \ - --body "Automated formula update for the ${TAG} release.")" - echo "Opened ${PR_URL}" - else - echo "Reusing existing PR ${PR_URL}" - fi - - if ! gh pr merge "$PR_URL" --auto --merge; then - echo "::warning::Could not enable auto-merge; merge the formula PR manually." - fi + # The Homebrew tap formula (Formula/rulesync.rb) is no longer updated by a + # dedicated CI job here. The /goal-release command regenerates it from the + # release's SHA256SUMS after the release PR merges and the Publish Assets + # workflow uploads the binaries (scripts/generate-homebrew-formula.ts). diff --git a/.rulesync/commands/goal-release.md b/.rulesync/commands/goal-release.md index 6d8b48fe2..18c9aa87f 100644 --- a/.rulesync/commands/goal-release.md +++ b/.rulesync/commands/goal-release.md @@ -3,9 +3,10 @@ targets: - "*" description: >- Cut a release end to end: run /draft-release to open the release PR and - draft GitHub release, wait for CI to turn green, then run /merge-pr to merge - the release PR. Use when the user wants to draft and merge a release in one - go, or triggers on "/goal-release". + draft GitHub release, wait for CI to turn green, run /merge-pr to merge the + release PR, then update the Homebrew formula from the published release + assets. Use when the user wants to draft and merge a release in one go, or + triggers on "/goal-release". --- # Goal Release Command @@ -14,7 +15,8 @@ new_version = $ARGUMENTS This command drives a release all the way to merge. It runs `/draft-release` to open the release pull request (and create the draft GitHub release), waits for -the PR's CI checks to pass, and then runs `/merge-pr` to merge it. +the PR's CI checks to pass, runs `/merge-pr` to merge it, and finally updates +the Homebrew tap formula once the release assets are built. ## 1. Draft the Release @@ -88,11 +90,88 @@ or release configuration. That restriction does not apply here — merging the version-bump release PR is this command's explicit purpose, and the user opted into it by invoking `/goal-release`. -## 5. Final Report +## 5. Update the Homebrew Formula + +After the release PR merges, the `Publish Assets` workflow builds the platform +binaries, tags the release, and uploads the assets (including `SHA256SUMS`) to +the draft GitHub release. The Homebrew formula embeds those checksums, so it +can only be regenerated once that workflow finishes — this step replaces the +old `homebrew` job that `.github/workflows/publish.yml` used to run. + +1. Wait for the `Publish Assets` run triggered by the merge to complete: + + ```bash + gh run list --workflow "Publish Assets" --branch "release/v" \ + --limit 1 --json databaseId,status,conclusion,headBranch + gh run watch + ``` + + The workflow triggers on every closed PR to `main` (non-release runs are + job-level skipped but still listed), so filter by the release branch as + shown. The run can take a few seconds to appear right after the merge — if + the list is empty, wait and retry. Confirm the watched run belongs to this + release (its `headBranch` is the `release/v` branch of the PR + merged in Step 4) and concluded with `success`. If it failed, stop and + report — the formula must not be regenerated from stale assets. + +2. Wait until the GitHub release is published (no longer a draft). The + `Publish` workflow (`.github/workflows/publish.yml`) runs after + `Publish Assets`, publishes the npm package, and then flips the release + public. Draft-release asset URLs return 404 for unauthenticated users, and + the Homebrew tap serves the formula straight from `main`, so merging the + formula before the release is public would break `brew install` for + everyone: + + ```bash + gh release view v --json isDraft --jq .isDraft + ``` + + Poll until this prints `false`. If the `Publish` workflow failed (e.g. npm + publish error) and the release stays a draft, stop and report instead of + updating the formula. + +3. Regenerate the formula from the released checksums: + + ```bash + git checkout main && git pull + gh release download v --pattern SHA256SUMS --dir ./tmp --clobber + pnpm exec tsx scripts/generate-homebrew-formula.ts ./tmp/SHA256SUMS Formula/rulesync.rb + rm -f ./tmp/SHA256SUMS + ``` + +4. If `git diff --quiet -- Formula/rulesync.rb` reports no change, the formula + is already up to date — skip ahead to the final report. + +5. Otherwise commit the regenerated formula on a branch and merge it right + away (`main` is branch-protected, so the change must land via a PR; merging + it immediately with admin rights is this command's explicit purpose, same + as the release PR itself): + + ```bash + git switch -c homebrew-formula/v + git add Formula/rulesync.rb + git commit -m "chore: update Homebrew formula to v" + git push -u origin homebrew-formula/v + gh pr create --base main --title "chore: update Homebrew formula to v" \ + --body "Automated formula update for the v release." + gh pr merge --admin --merge --delete-branch + ``` + + If a previous attempt already pushed the `homebrew-formula/v` + branch or opened its PR, reuse them instead of failing: force-push the + branch with `git push --force-with-lease` and skip `gh pr create` when + `gh pr list --head homebrew-formula/v` shows an open PR. + + Only `Formula/rulesync.rb` may be committed here. If anything else shows up + in `git status`, stop and report instead of committing it. + +## 6. Final Report Report to the user: - The merged release PR number and title. -- The new version and a link to the draft GitHub release (publishing the - release is intentionally left as a manual follow-up step). +- The new version and a link to the GitHub release (published automatically by + the `Publish` workflow, which Step 5 waits for). +- Whether the Homebrew formula was updated (and the formula PR number) or was + already up to date. - Any CI fixes that were needed along the way. diff --git a/.rulesync/skills/draft-release/SKILL.md b/.rulesync/skills/draft-release/SKILL.md index 21a9f5bab..afb4e48e4 100644 --- a/.rulesync/skills/draft-release/SKILL.md +++ b/.rulesync/skills/draft-release/SKILL.md @@ -29,3 +29,5 @@ Let's resume the release process. 8. As a precaution, verify that `getVersion()` in `src/cli/index.ts` is updated to the ${new_version}. 9. Run `gh pr create` to the main branch. 10. Create a **draft** release using `gh release create v${new_version} --draft --title v${new_version} --notes-file ./tmp/release-notes/*.md` command on the `github.com/dyoshikawa/rulesync` repository. This creates a draft release so that the publish-assets workflow can upload assets later. + +Note: the Homebrew tap formula (`Formula/rulesync.rb`) is NOT updated here. It embeds sha256 checksums of the release binaries, which only exist after the release PR merges and the `Publish Assets` workflow uploads them. The `/goal-release` command performs that update as its post-merge step (regenerate with `scripts/generate-homebrew-formula.ts` from the release's `SHA256SUMS` and land it via an immediately merged PR). When releasing manually without `/goal-release`, run that step yourself after the assets are published. diff --git a/Formula/rulesync.rb b/Formula/rulesync.rb index 45c6c78c3..51f83db8c 100644 --- a/Formula/rulesync.rb +++ b/Formula/rulesync.rb @@ -1,8 +1,8 @@ # typed: false # frozen_string_literal: true -# This file is generated on each release by the "homebrew" job in -# .github/workflows/publish.yml (scripts/generate-homebrew-formula.ts). +# This file is regenerated on each release by the /goal-release command +# (scripts/generate-homebrew-formula.ts) after the release assets are built. # Do not edit it by hand; changes are overwritten on the next release. class Rulesync < Formula desc "Unified AI rules management CLI that generates config files for AI dev tools" diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index fc81c5bdc..a4aa842ad 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -26,8 +26,8 @@ rulesync --version ``` The formula installs the prebuilt binary for your platform (macOS/Linux, arm64 -and x64), so it does not depend on a Node.js runtime. It is updated -automatically on every release. Homebrew does not support Windows; use npm or the +and x64), so it does not depend on a Node.js runtime. It is updated as part of +each release. Homebrew does not support Windows; use npm or the single-binary download below there. ## Single Binary diff --git a/scripts/generate-homebrew-formula.ts b/scripts/generate-homebrew-formula.ts index 2a5c43cea..8549aaafe 100644 --- a/scripts/generate-homebrew-formula.ts +++ b/scripts/generate-homebrew-formula.ts @@ -70,8 +70,8 @@ export function generateHomebrewFormula(version: string, shas: FormulaShas): str return `# typed: false # frozen_string_literal: true -# This file is generated on each release by the "homebrew" job in -# .github/workflows/publish.yml (scripts/generate-homebrew-formula.ts). +# This file is regenerated on each release by the /goal-release command +# (scripts/generate-homebrew-formula.ts) after the release assets are built. # Do not edit it by hand; changes are overwritten on the next release. class Rulesync < Formula desc "Unified AI rules management CLI that generates config files for AI dev tools" diff --git a/skills/rulesync/installation.md b/skills/rulesync/installation.md index 1063dc637..1a567a946 100644 --- a/skills/rulesync/installation.md +++ b/skills/rulesync/installation.md @@ -26,8 +26,8 @@ rulesync --version ``` The formula installs the prebuilt binary for your platform (macOS/Linux, arm64 -and x64), so it does not depend on a Node.js runtime. It is updated -automatically on every release. Homebrew does not support Windows; use npm or the +and x64), so it does not depend on a Node.js runtime. It is updated as part of +each release. Homebrew does not support Windows; use npm or the single-binary download below there. ## Single Binary