Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 4 additions & 102 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
93 changes: 86 additions & 7 deletions .rulesync/commands/goal-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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<version>" \
--limit 1 --json databaseId,status,conclusion,headBranch
gh run watch <run_id>
```

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<version>` 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<version> --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<version> --pattern SHA256SUMS --dir ./tmp --clobber
pnpm exec tsx scripts/generate-homebrew-formula.ts <version> ./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<version>
git add Formula/rulesync.rb
git commit -m "chore: update Homebrew formula to v<version>"
git push -u origin homebrew-formula/v<version>
gh pr create --base main --title "chore: update Homebrew formula to v<version>" \
--body "Automated formula update for the v<version> release."
gh pr merge --admin --merge --delete-branch
```

If a previous attempt already pushed the `homebrew-formula/v<version>`
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<version>` 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.
2 changes: 2 additions & 0 deletions .rulesync/skills/draft-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions Formula/rulesync.rb
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions scripts/generate-homebrew-formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions skills/rulesync/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading