From 132529a7276eb9c57e17eca4b4cc8ff84bfbc920 Mon Sep 17 00:00:00 2001 From: harumiWeb Date: Sat, 11 Apr 2026 23:17:45 +0900 Subject: [PATCH 1/2] Add homebrew tap dist pipeline --- .github/workflows/release.yml | 36 ++++++++++++++++++++++ .goreleaser.yaml | 26 ++++++++++++++++ CHANGELOG.md | 4 +++ README.en.md | 21 +++++++++++-- README.md | 21 +++++++++++-- docs/adr/0003-release-and-update-policy.md | 9 ++++-- 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 796ee2b..5ad2a6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,41 @@ jobs: - name: Build packages run: go build ./... + - name: Initialize Homebrew tap repository + env: + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + run: | + set -euo pipefail + + tap_api_url="https://api.github.com/repos/harumiWeb/homebrew-eitango/git/ref/heads/main" + status_code="$(curl -sS -o /dev/null -w '%{http_code}' \ + -H 'Accept: application/vnd.github+json' \ + -H "Authorization: Bearer ${HOMEBREW_TAP_GITHUB_TOKEN}" \ + "${tap_api_url}")" + + if [ "${status_code}" = "200" ]; then + exit 0 + fi + + if [ "${status_code}" != "404" ] && [ "${status_code}" != "409" ]; then + echo "unexpected tap branch status: ${status_code}" >&2 + exit 1 + fi + + tmpdir="$(mktemp -d)" + trap 'rm -rf "${tmpdir}"' EXIT + + git -C "${tmpdir}" init -b main + cat <<'EOF' > "${tmpdir}/README.md" + # homebrew-eitango + + Homebrew tap for eitango release formulas. + EOF + git -C "${tmpdir}" add README.md + git -C "${tmpdir}" -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' commit -m 'Initialize tap repository' + git -C "${tmpdir}" remote add origin "https://x-access-token:${HOMEBREW_TAP_GITHUB_TOKEN}@github.com/harumiWeb/homebrew-eitango.git" + git -C "${tmpdir}" push origin main + - name: Publish release artifacts uses: goreleaser/goreleaser-action@v6 with: @@ -41,4 +76,5 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} WINGET_GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e23a6cd..ffca325 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -66,6 +66,32 @@ archives: - goos: linux formats: [none] +brews: + - name: eitango + ids: + - unix-archives + directory: Formula + homepage: https://github.com/harumiWeb/eitango + description: Interactive English vocabulary learning CLI built with Go. + license: Apache-2.0 + commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}" + repository: + owner: harumiWeb + name: homebrew-eitango + branch: main + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + commit_author: + name: github-actions[bot] + email: 41898282+github-actions[bot]@users.noreply.github.com + install: | + bin.install "eitango" + doc.install "README.md", "README.en.md" + pkgshare.install "LICENSE", "THIRD_PARTY_NOTICES.md" + (pkgshare/"third_party").install "third_party/licenses" + test: | + system "#{bin}/eitango", "--help" + skip_upload: "{{ if .Prerelease }}true{{ else }}false{{ end }}" + winget: - name: eitango publisher: HarumiWeb diff --git a/CHANGELOG.md b/CHANGELOG.md index e127a95..5b4eb77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ## [Unreleased] +### Added + +- release パイプラインが Homebrew tap `harumiWeb/homebrew-eitango` へ formula を publish できるようになり、macOS / Linux で `brew` install 導線を提供できるようにしました。 + ## [0.7.0] - 2026-04-11 ### Added diff --git a/README.en.md b/README.en.md index 4ce232f..1a75a49 100644 --- a/README.en.md +++ b/README.en.md @@ -79,7 +79,22 @@ winget upgrade HarumiWeb.Eitango If you do not want to use winget, you can still use the GitHub Releases zip described below. -### 2. Use `curl | sh` on macOS / Linux +### 2. Use Homebrew on macOS / Linux + +On macOS / Linux, you can also install `eitango` from the Homebrew tap. The formula points to the darwin / linux `tar.gz` archives published in GitHub Releases. + +```bash +brew tap harumiWeb/eitango +brew install eitango +``` + +To upgrade: + +```bash +brew upgrade eitango +``` + +### 3. Use `curl | sh` on macOS / Linux `install.sh` calls the GitHub Releases API (`/releases/latest`) when `--version` is omitted to resolve the latest version, then downloads the matching GitHub Release archive and `checksums.txt`. It installs into `~/.eitango/` only after the SHA256 check passes, and it never edits shell rc files automatically. @@ -126,13 +141,13 @@ curl -fsSL https://raw.githubusercontent.com/harumiWeb/eitango/main/install.sh | Required tools are `sh`, `curl`, `tar`, `mktemp`, and one of `sha256sum`, `shasum`, or `openssl`. Windows is out of scope for this installer, so use winget or the release zip there. -### 3. Use GitHub Releases +### 4. Use GitHub Releases Published archives include the executable plus `LICENSE`, `THIRD_PARTY_NOTICES.md`, and `third_party/licenses/`. Extract the artifact for your OS and run `eitango`. ※ You need to manually add it to your `PATH`. -### 4. Install with Go +### 5. Install with Go Go 1.26 or newer is required. diff --git a/README.md b/README.md index 3accdbb..c1cec1a 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,22 @@ winget を使わない場合は後述の GitHub Releases の zip からも利用 > [!NOTE] > winget は 都合上、他の配布手段よりリリースからの反映が遅れる可能性があります。最新 release をすぐに使いたい場合は、次のいずれかを選択してください。 -### 2. macOS / Linux は `curl | sh` を使う +### 2. macOS / Linux は Homebrew を使う + +macOS / Linux では Homebrew tap からも install できます。formula は GitHub Releases に公開した darwin / linux の `tar.gz` を参照します。 + +```bash +brew tap harumiWeb/eitango +brew install eitango +``` + +更新は次です。 + +```bash +brew upgrade eitango +``` + +### 3. macOS / Linux は `curl | sh` を使う `install.sh` は `--version` を省略した場合に GitHub Releases API (`/releases/latest`) へアクセスして最新 version を解決し、そのうえで対応する archive と `checksums.txt` を取得します。SHA256 検証が通ったときだけ `~/.eitango/` へ展開し、shell rc は自動変更しません。 @@ -129,13 +144,13 @@ curl -fsSL https://raw.githubusercontent.com/harumiWeb/eitango/main/install.sh | 必要ツールは `sh`, `curl`, `tar`, `mktemp` と、`sha256sum` / `shasum` / `openssl` のいずれか 1 つです。Windows は今回の installer 対象外なので、winget か release zip を使ってください。 -### 3. GitHub Releases から使う +### 4. GitHub Releases から使う 公開アーカイブにはバイナリに加えて `LICENSE`、`THIRD_PARTY_NOTICES.md`、`third_party/licenses/` が同梱されます。自分のOS向けの成果物を展開して `eitango` を実行してください。 ※ `PATH`への追加は手動で行う必要があります。 -### 4. Go からインストールする +### 5. Go からインストールする Go 1.26 以降を前提にしています。 diff --git a/docs/adr/0003-release-and-update-policy.md b/docs/adr/0003-release-and-update-policy.md index c926d31..1ec9766 100644 --- a/docs/adr/0003-release-and-update-policy.md +++ b/docs/adr/0003-release-and-update-policy.md @@ -11,10 +11,12 @@ ## Decision - 正規の配布チャネルは GitHub Releases とし、GoReleaser で darwin / linux / windows 向け archive を生成する。 +- macOS / linux 向けには Homebrew tap `harumiWeb/homebrew-eitango` も配布導線として提供するが、formula の参照先 artifact は GitHub Releases に置いた darwin / linux `tar.gz` のみとし、release の正本は引き続き GitHub Releases に固定する。 - Windows 向けには winget community repository も配布導線として提供するが、参照先 artifact は GitHub Releases に置いた Windows zip のみとし、release の正本は引き続き GitHub Releases に固定する。 - release artifact には実行バイナリに加えて `LICENSE`, `README.md`, `README.en.md`, `THIRD_PARTY_NOTICES.md`, `third_party/licenses/**` を同梱する。 - macOS / linux では `install.sh` を bootstrap 導線として許可するが、installer 自体は GitHub Releases の archive と `checksums.txt` を取得する薄い wrapper に留める。 - `install.sh` は `checksums.txt` で SHA256 を必須検証し、法務ファイルを `~/.eitango/share/` へ保持する。 +- Homebrew formula は GoReleaser で release 時に生成し、`harumiWeb/homebrew-eitango` へ push する。push 用 token は release 用 `GITHUB_TOKEN` と分離した `HOMEBREW_TAP_GITHUB_TOKEN` を使い、prerelease tag は tap へ publish しない。 - winget manifest は GoReleaser で release 時に生成し、`harumiWeb/winget-pkgs` fork へ push する。`microsoft/winget-pkgs` への PR は手動で作成し、fork への push 用 token は release 用 `GITHUB_TOKEN` と分離した `WINGET_GITHUB_TOKEN` を使う。 - 更新は自動適用しない。新しい版の取得は release archive の再取得か `go install ...@latest` の再実行で行う。 - update check は GitHub Releases の latest を参照する補助機能とし、学習フローを止めない best-effort 動作に限定する。 @@ -27,14 +29,15 @@ ## Consequences - release artifact の再配布条件と notice の同梱要件を、ビルド設定と文書で一貫して維持できる。 +- macOS / Linux 利用者には `brew tap harumiWeb/eitango && brew install eitango` の導線を追加できるが、artifact hosting と checksum の正本は増やさずに済む。 - Windows 利用者には `winget install HarumiWeb.Eitango` の導線を追加できるが、artifact hosting と checksum の正本は増やさずに済む。 - `curl | sh` の最短導線を追加しても、配布物の単一ソースは GitHub Releases のまま保てる。 - SHA256 検証で download 途中の破損や取り違えは防ぎやすくなるが、署名付き provenance ではないため `install.sh` 本体の信頼境界は依然として HTTPS / GitHub 側にある。 - 自動更新を持たないため、更新失敗が学習データを壊す経路を増やさずに済む。 - update check は起動ごとに 1 回の best-effort request を行うが、最新 release 反映の遅延は大きく減り、更新作業は引き続き手動のまま保てる。 - 保存済み state があるため、GitHub API が失敗しても直前の latest 情報を fallback として使える。 -- fork 側 PAT の期限切れや権限不足があると winget manifest push が失敗しうるため、release 失敗時は token 権限と fork 状態を先に確認する運用が必要になる。upstream PR は手動運用なので、release 後に compare URL から提出する手順を別途維持する必要がある。 -- GitHub Releases が update metadata と Windows zip の単一ソースになるため、別チャネルを増やす場合は新しい判断が必要になる。 +- tap / fork 側 PAT の期限切れや権限不足があると Homebrew formula push や winget manifest push が失敗しうるため、release 失敗時は token 権限と publish 先 repository 状態を先に確認する運用が必要になる。upstream PR は手動運用なので、release 後に compare URL から提出する手順を別途維持する必要がある。 +- GitHub Releases が update metadata と Homebrew / winget の参照先 artifact の単一ソースになるため、さらに別チャネルを増やす場合は新しい判断が必要になる。 ## Rationale @@ -48,6 +51,8 @@ - `.goreleaser.yaml` - `.github/workflows/release.yml` - `install.sh` + - `README.md` + - `README.en.md` - Related specs: - なし。コード、README、CHANGELOG、tests を正本とする。 From 4733dd9b3ae1fd94fed1097be3195a5553f470c7 Mon Sep 17 00:00:00 2001 From: harumiWeb Date: Sat, 11 Apr 2026 23:26:57 +0900 Subject: [PATCH 2/2] fix: Check for the existence and accessibility of the Homebrew repository. --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++++-------- CHANGELOG.md | 2 ++ tasks/lessons.md | 1 + 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ad2a6f..af18002 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,18 +39,36 @@ jobs: run: | set -euo pipefail - tap_api_url="https://api.github.com/repos/harumiWeb/homebrew-eitango/git/ref/heads/main" - status_code="$(curl -sS -o /dev/null -w '%{http_code}' \ - -H 'Accept: application/vnd.github+json' \ - -H "Authorization: Bearer ${HOMEBREW_TAP_GITHUB_TOKEN}" \ - "${tap_api_url}")" + curl_status_args=( + -sS + --connect-timeout 10 + --max-time 20 + --retry 3 + --retry-delay 2 + --retry-all-errors + -o /dev/null + -w '%{http_code}' + -H 'Accept: application/vnd.github+json' + -H "Authorization: Bearer ${HOMEBREW_TAP_GITHUB_TOKEN}" + ) - if [ "${status_code}" = "200" ]; then + tap_repo_api_url="https://api.github.com/repos/harumiWeb/homebrew-eitango" + repo_status_code="$(curl "${curl_status_args[@]}" "${tap_repo_api_url}")" + + if [ "${repo_status_code}" != "200" ]; then + echo "homebrew tap repository is missing or inaccessible: ${repo_status_code}" >&2 + exit 1 + fi + + tap_branch_api_url="https://api.github.com/repos/harumiWeb/homebrew-eitango/git/ref/heads/main" + branch_status_code="$(curl "${curl_status_args[@]}" "${tap_branch_api_url}")" + + if [ "${branch_status_code}" = "200" ]; then exit 0 fi - if [ "${status_code}" != "404" ] && [ "${status_code}" != "409" ]; then - echo "unexpected tap branch status: ${status_code}" >&2 + if [ "${branch_status_code}" != "404" ] && [ "${branch_status_code}" != "409" ]; then + echo "unexpected tap branch status: ${branch_status_code}" >&2 exit 1 fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b4eb77..f319024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ## [Unreleased] +## [0.7.1] - 2026-04-11 + ### Added - release パイプラインが Homebrew tap `harumiWeb/homebrew-eitango` へ formula を publish できるようになり、macOS / Linux で `brew` install 導線を提供できるようにしました。 diff --git a/tasks/lessons.md b/tasks/lessons.md index f411c83..cd5b3f7 100644 --- a/tasks/lessons.md +++ b/tasks/lessons.md @@ -26,6 +26,7 @@ - 非同期エラーを UI 用 message へ潰すときも root cause を落とさない。`audioErrMsg` のような軽量 message でも trigger source と元 error を保持し、status は短く保ったまま診断と回帰テストに使える形で残す。 - 外部コマンド実行を security lint が見ている箇所は、validated な入力でも `exec.CommandContext(name, ...)` の形を避ける。`LookPath` は存在確認と allowlist 判定に使い、実行は静的 literal のコマンドに正規化して Semgrep/Codacy と実装意図を一致させる。 - `winget-pkgs` のような fork 経由の upstream PR を自動化する前に、PAT の owner 境界を確認する。fine-grained PAT は選択した owner 配下の repo にしか権限を持てないので、fork への push はできても別 owner への cross-repository PR 作成は失敗しうる。 +- cross-repository publish の初期化 step で `git/ref/heads/...` の 404 を branch 不在と決め打ちしない。先に target repo 自体の存在/アクセスを確認し、外部 API probe には timeout と retry を付けて token 不備や一時的な network stall を明確に切り分ける。 - アクセシビリティ向けのテーマ提案では、任意設定だけで完結させず、すぐ選べる高コントラスト preset を別モードで持つかを先に確認する。利用者は `custom` より preset を期待していることがある。 - 既存 UI の見た目調整では、preset を追加するときも従来 preset の色味を不必要に変えない。default は既存の印象を保ち、新規テーマでだけ方向性を変える。 - optional config table を保存するときは、未設定 field を `""` でシリアライズしない。fallback 契約がある設定は key ごと omit して、ユーザー生成物にも契約そのものを反映する。