From 113631c42450ca569305bf460b36c1160cf213cd Mon Sep 17 00:00:00 2001 From: rn404 Date: Sun, 5 Jul 2026 23:28:01 +0900 Subject: [PATCH 1/4] ci: add release workflow for prebuilt binaries Triggered by v* tag push. Builds darwin/linux (amd64+arm64) and windows (amd64) with version embedded via ldflags, packages tar.gz/zip with sha256 checksums, and creates a GitHub Release. Uses only GitHub-owned actions (checkout, setup-go) so no changes to the Actions allowlist are needed. Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++++ README.md | 4 +++ docs/go-migration-plan.md | 3 ++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4260c57 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + + - name: Test + run: go test ./... + + - name: Build binaries + run: | + mkdir -p dist + for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64; do + export GOOS=${target%/*} GOARCH=${target#*/} + ext="" + [ "$GOOS" = "windows" ] && ext=".exe" + name="sava_${GITHUB_REF_NAME}_${GOOS}_${GOARCH}" + CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X main.version=${GITHUB_REF_NAME}" \ + -o "dist/${name}/sava${ext}" ./cmd/sava + if [ "$GOOS" = "windows" ]; then + (cd dist && zip -q -r "${name}.zip" "$name") + else + tar -czf "dist/${name}.tar.gz" -C dist "$name" + fi + rm -r "dist/${name}" + done + + - name: Checksums + run: (cd dist && shasum -a 256 ./* > checksums.txt) + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes diff --git a/README.md b/README.md index f04cfb1..25560b4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ https://gist.github.com/rn404/decf010fc48d7d8688116af0f4427b44 go install github.com/rn404/nippo-cli/cmd/sava@latest ``` +ビルド済みバイナリ (macOS / Linux / Windows) は +[Releases](https://github.com/rn404/nippo-cli/releases) からも取得できます. +`v*` タグを push すると CI がバイナリをビルドしてリリースを作成します. + ## Usage ``` diff --git a/docs/go-migration-plan.md b/docs/go-migration-plan.md index 335f3cc..ee54156 100644 --- a/docs/go-migration-plan.md +++ b/docs/go-migration-plan.md @@ -238,7 +238,8 @@ type Log struct { - [x] **4.2** Phase 0 のサンプルと比較し、機能面のデグレがないことを確認 - 加えて hash バグ・`clear` バグの修正が意図通り機能することを実機確認 - [x] **4.3** README の Usage を Go 版に更新 -- [x] **4.4** `go install` 手順を README に整備(goreleaser は任意のため今回は見送り、必要になったら導入) +- [x] **4.4** `go install` 手順を README に整備 + - 追記: リリースバイナリ用 CI(`release.yml`)を移行後に追加。goreleaser は Actions 許可リストへの追加が必要になるため採用せず、GitHub 製アクション + `go build` + `gh release` のみで構成(`v*` タグ push で発火) ### Phase 5: Deno 資産の撤去 From 9a36f39f4c17a4da409b042c7d4611a67254288e Mon Sep 17 00:00:00 2001 From: rn404 Date: Sun, 5 Jul 2026 23:30:21 +0900 Subject: [PATCH 2/4] ci: drop windows target, ship macOS/Linux binaries only Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 12 +++--------- README.md | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4260c57..a9369fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,19 +22,13 @@ jobs: - name: Build binaries run: | mkdir -p dist - for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64; do + for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do export GOOS=${target%/*} GOARCH=${target#*/} - ext="" - [ "$GOOS" = "windows" ] && ext=".exe" name="sava_${GITHUB_REF_NAME}_${GOOS}_${GOARCH}" CGO_ENABLED=0 go build -trimpath \ -ldflags "-s -w -X main.version=${GITHUB_REF_NAME}" \ - -o "dist/${name}/sava${ext}" ./cmd/sava - if [ "$GOOS" = "windows" ]; then - (cd dist && zip -q -r "${name}.zip" "$name") - else - tar -czf "dist/${name}.tar.gz" -C dist "$name" - fi + -o "dist/${name}/sava" ./cmd/sava + tar -czf "dist/${name}.tar.gz" -C dist "$name" rm -r "dist/${name}" done diff --git a/README.md b/README.md index 25560b4..c8c5557 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ https://gist.github.com/rn404/decf010fc48d7d8688116af0f4427b44 go install github.com/rn404/nippo-cli/cmd/sava@latest ``` -ビルド済みバイナリ (macOS / Linux / Windows) は +ビルド済みバイナリ (macOS / Linux) は [Releases](https://github.com/rn404/nippo-cli/releases) からも取得できます. `v*` タグを push すると CI がバイナリをビルドしてリリースを作成します. From b26ce6b575defd56de7a1cc043c91e41f0dc40fa Mon Sep 17 00:00:00 2001 From: rn404 Date: Sun, 5 Jul 2026 23:46:14 +0900 Subject: [PATCH 3/4] ci: redesign release lifecycle around release PRs - cmd/sava/version.txt is the single source of truth, embedded via go:embed (go install builds also show the correct version) - release-pr.yml (dispatch, bump: patch/minor/major): creates a PR bumping version.txt with auto-generated notes as the body - release.yml: on merge (version.txt change on main), tests, builds, tags, and publishes a Release with the merged PR body as notes - Release logic lives in scripts/ and runs via Makefile (bash invocation, no exec permission needed); Makefile also provides build/test/fmt/vet/lint/check targets for development Co-Authored-By: Claude Fable 5 --- .github/workflows/release-pr.yml | 39 ++++++++++++++++++++++ .github/workflows/release.yml | 56 ++++++++++++++++++++++---------- .gitignore | 3 ++ Makefile | 29 +++++++++++++++++ README.md | 27 +++++++++++---- cmd/sava/main.go | 12 +++++-- cmd/sava/version.txt | 1 + docs/go-migration-plan.md | 3 +- scripts/build-release.sh | 21 ++++++++++++ scripts/bump-version.sh | 17 ++++++++++ scripts/create-release-pr.sh | 24 ++++++++++++++ 11 files changed, 204 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/release-pr.yml create mode 100644 Makefile create mode 100644 cmd/sava/version.txt create mode 100644 scripts/build-release.sh create mode 100644 scripts/bump-version.sh create mode 100644 scripts/create-release-pr.sh diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000..40fcab6 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,39 @@ +name: Release PR + +# Creates a release pull request that bumps cmd/sava/version.txt. +# Merging that PR is the release approval: release.yml then builds, +# tags, and publishes using the PR body as release notes. +# +# Note: PRs created with GITHUB_TOKEN do not trigger other workflows, +# so no CI runs on the release PR itself. The diff is version.txt +# only, and release.yml runs go test before publishing. + +on: + workflow_dispatch: + inputs: + bump: + description: Version bump level + type: choice + options: + - patch + - minor + - major + default: patch + +permissions: + contents: write + pull-requests: write + +jobs: + release-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Create release PR + env: + GH_TOKEN: ${{ github.token }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + make release-pr BUMP="${{ inputs.bump }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9369fa..5fd33df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,13 @@ name: Release +# Runs when a release PR (created by release-pr.yml) is merged: +# builds binaries, creates the version tag, and publishes a GitHub +# Release using the merged PR body as release notes. + on: push: - tags: ['v*'] + branches: [main] + paths: ['cmd/sava/version.txt'] permissions: contents: write @@ -12,30 +17,45 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Read version + id: version + run: echo "tag=$(cat cmd/sava/version.txt)" >> "$GITHUB_OUTPUT" + + - name: Skip if already released + id: guard + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then + echo "Tag $TAG already exists; skipping release." + echo "exists=true" >> "$GITHUB_OUTPUT" + fi + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + if: steps.guard.outputs.exists != 'true' with: go-version-file: go.mod - name: Test + if: steps.guard.outputs.exists != 'true' run: go test ./... - name: Build binaries - run: | - mkdir -p dist - for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do - export GOOS=${target%/*} GOARCH=${target#*/} - name="sava_${GITHUB_REF_NAME}_${GOOS}_${GOARCH}" - CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X main.version=${GITHUB_REF_NAME}" \ - -o "dist/${name}/sava" ./cmd/sava - tar -czf "dist/${name}.tar.gz" -C dist "$name" - rm -r "dist/${name}" - done - - - name: Checksums - run: (cd dist && shasum -a 256 ./* > checksums.txt) - - - name: Create GitHub Release + if: steps.guard.outputs.exists != 'true' + run: make release-build TAG="${{ steps.version.outputs.tag }}" + + - name: Publish release + if: steps.guard.outputs.exists != 'true' env: GH_TOKEN: ${{ github.token }} - run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes + TAG: ${{ steps.version.outputs.tag }} + run: | + # Release notes = body of the PR that introduced this commit. + gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" \ + --jq '.[0].body // empty' > notes.md + if [ ! -s notes.md ]; then + echo "Release $TAG" > notes.md + fi + gh release create "$TAG" dist/* \ + --title "$TAG" --notes-file notes.md --target "$GITHUB_SHA" diff --git a/.gitignore b/.gitignore index 3d741e0..b6d15a0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ docs/.obsidian/ # Go build artifacts /sava + +# Release build artifacts +/dist/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4e5a2ae --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: build test fmt vet lint check release-pr release-build + +BUMP ?= patch +TAG ?= $(shell cat cmd/sava/version.txt) + +build: + go build -o sava ./cmd/sava + +test: + go test ./... + +fmt: + test -z "$$(gofmt -l .)" + +vet: + go vet ./... + +lint: + golangci-lint run + +check: fmt vet test lint + +# --- release (scripts run via bash: no exec permission needed) --- + +release-pr: + bash scripts/create-release-pr.sh $(BUMP) + +release-build: + bash scripts/build-release.sh $(TAG) diff --git a/README.md b/README.md index c8c5557..6679b94 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,18 @@ go install github.com/rn404/nippo-cli/cmd/sava@latest ビルド済みバイナリ (macOS / Linux) は [Releases](https://github.com/rn404/nippo-cli/releases) からも取得できます. -`v*` タグを push すると CI がバイナリをビルドしてリリースを作成します. + +## Release lifecycle + +バージョンの単一の真実は `cmd/sava/version.txt` (`go:embed` でバイナリに埋め込み). + +1. Actions で「Release PR」workflow を dispatch し, bump レベル (patch / minor / major) を選択 +2. `version.txt` を更新した Release PR が自動で作られる (本文には変更点一覧が自動生成される) +3. PR 本文をリリースノートとして整えて, **マージ = リリース承認** +4. マージを検知して release workflow が起動し, テスト・バイナリビルド・タグ作成・GitHub Release 公開まで自動実行 (マージ時点の PR 本文がそのままリリースノートになる) + +リリース処理の実体は `scripts/` にあり, Makefile 経由でローカルでも実行できます +(例: `make release-build TAG=v0.1.0` で `dist/` にバイナリを生成). ## Usage @@ -68,11 +79,15 @@ sava clear -a # Run from source go run ./cmd/sava -# Test / format / lint -go test ./... -gofmt -l . -go vet ./... -golangci-lint run # version is pinned in .mise.toml (mise install) +# Test / format / vet / lint at once +make check + +# Individual targets +make test +make fmt +make vet +make lint # golangci-lint: version is pinned in .mise.toml (mise install) +make build ``` ## History diff --git a/cmd/sava/main.go b/cmd/sava/main.go index d9e23a3..a41a262 100644 --- a/cmd/sava/main.go +++ b/cmd/sava/main.go @@ -1,17 +1,23 @@ package main import ( + _ "embed" "fmt" "os" + "strings" "github.com/spf13/cobra" ) const appName = "sava" -// version is overridable at build time: -// go build -ldflags "-X main.version=vX.Y.Z" -var version = "v0.1.0" +// versionFile is the single source of truth for the version, bumped +// by the release PR workflow (.github/workflows/release-pr.yml). +// +//go:embed version.txt +var versionFile string + +var version = strings.TrimSpace(versionFile) func main() { root := newRootCommand() diff --git a/cmd/sava/version.txt b/cmd/sava/version.txt new file mode 100644 index 0000000..b82608c --- /dev/null +++ b/cmd/sava/version.txt @@ -0,0 +1 @@ +v0.1.0 diff --git a/docs/go-migration-plan.md b/docs/go-migration-plan.md index ee54156..08c2e8e 100644 --- a/docs/go-migration-plan.md +++ b/docs/go-migration-plan.md @@ -239,7 +239,8 @@ type Log struct { - 加えて hash バグ・`clear` バグの修正が意図通り機能することを実機確認 - [x] **4.3** README の Usage を Go 版に更新 - [x] **4.4** `go install` 手順を README に整備 - - 追記: リリースバイナリ用 CI(`release.yml`)を移行後に追加。goreleaser は Actions 許可リストへの追加が必要になるため採用せず、GitHub 製アクション + `go build` + `gh release` のみで構成(`v*` タグ push で発火) + - 追記: リリース用 CI を移行後に追加。goreleaser は Actions 許可リストへの追加が必要になるため採用せず、GitHub 製アクション + `go build` + `gh` のみで構成 + - ライフサイクル: `release-pr.yml`(dispatch でバージョンバンプ PR を自動作成)→ PR マージ = リリース承認 → `release.yml`(`cmd/sava/version.txt` の変更を検知してテスト・ビルド・タグ・Release 公開、PR 本文がリリースノート) ### Phase 5: Deno 資産の撤去 diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100644 index 0000000..5c61220 --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Cross-compile release binaries into dist/ with sha256 checksums. +# Usage: build-release.sh +set -euo pipefail + +cd "$(dirname "$0")/.." +tag="${1:?usage: build-release.sh }" + +rm -rf dist +mkdir -p dist + +for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do + export GOOS="${target%/*}" GOARCH="${target#*/}" + name="sava_${tag}_${GOOS}_${GOARCH}" + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" \ + -o "dist/${name}/sava" ./cmd/sava + tar -czf "dist/${name}.tar.gz" -C dist "$name" + rm -r "dist/${name:?}" +done + +(cd dist && shasum -a 256 ./* > checksums.txt) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100644 index 0000000..baaf4e0 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Print the next version for the given bump level. +# Usage: bump-version.sh +set -euo pipefail + +VERSION_FILE="$(dirname "$0")/../cmd/sava/version.txt" +bump="${1:?usage: bump-version.sh }" + +current="$(cat "$VERSION_FILE")" +IFS=. read -r major minor patch <<< "${current#v}" + +case "$bump" in + major) echo "v$((major + 1)).0.0" ;; + minor) echo "v${major}.$((minor + 1)).0" ;; + patch) echo "v${major}.${minor}.$((patch + 1))" ;; + *) echo "unknown bump level: $bump" >&2; exit 1 ;; +esac diff --git a/scripts/create-release-pr.sh b/scripts/create-release-pr.sh new file mode 100644 index 0000000..19ea6c0 --- /dev/null +++ b/scripts/create-release-pr.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Create a release PR that bumps cmd/sava/version.txt. +# Merging the PR is the release approval (see release.yml). +# Usage: create-release-pr.sh +# Requires: gh (authenticated), git push permission. +set -euo pipefail + +cd "$(dirname "$0")/.." +bump="${1:?usage: create-release-pr.sh }" + +next="$(bash scripts/bump-version.sh "$bump")" +base_sha="$(git rev-parse HEAD)" +repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner)" + +git switch -c "release/$next" +echo "$next" > cmd/sava/version.txt +git commit -am "release: $next" +git push -u origin "release/$next" + +# Prefill the PR body with auto-generated notes; edit it before +# merging — the merged body becomes the release notes. +notes="$(gh api "repos/$repo/releases/generate-notes" \ + -f tag_name="$next" -f target_commitish="$base_sha" --jq .body)" +gh pr create --title "Release $next" --body "$notes" From 7a745fe67f67751a6d929da99bbe57bb49d14314 Mon Sep 17 00:00:00 2001 From: rn404 Date: Sun, 5 Jul 2026 23:46:43 +0900 Subject: [PATCH 4/4] ci: seed version.txt at v0.0.0 so the first release PR produces v0.1.0 Co-Authored-By: Claude Fable 5 --- cmd/sava/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/sava/version.txt b/cmd/sava/version.txt index b82608c..ae39fab 100644 --- a/cmd/sava/version.txt +++ b/cmd/sava/version.txt @@ -1 +1 @@ -v0.1.0 +v0.0.0