diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml new file mode 100644 index 000000000..5570af372 --- /dev/null +++ b/.github/workflows/release-cli.yml @@ -0,0 +1,167 @@ +name: 'Release CLI' + +on: + push: + tags: + - 'cli/*' + pull_request: + paths: + - '.github/workflows/release-cli.yml' + - 'cli/Makefile' + workflow_dispatch: + +# Note: tag pushes made with GITHUB_TOKEN do not trigger workflows. If CLI tagging is ever +# automated the way post-release.yml tags the server, invoke this via workflow_call instead +# of relying on the tag push. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Build and Package + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: cli + outputs: + version: ${{ steps.resolve.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - name: Resolve version + id: resolve + env: + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ "$REF_TYPE" = tag ]; then + V="${REF_NAME#cli/}" + V="${V#v}" + else + V="0.0.0-dev" + fi + if [[ ! "$V" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then + echo "::error::resolved version '$V' is not semver (tag must be cli/X.Y.Z)" + exit 1 + fi + echo "version=$V" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache-dependency-path: cli/go.sum + + - name: Build and package + run: make package VERSION="${{ steps.resolve.outputs.version }}" + + - name: Verify artifacts + env: + VERSION: ${{ steps.resolve.outputs.version }} + run: | + set -euo pipefail + out=$("./build/actionbase_${VERSION}_linux_amd64/actionbase" --version) + [ "$out" = "v${VERSION}" ] \ + || { echo "::error::--version printed '$out', want 'v${VERSION}'"; exit 1; } + for p in linux_amd64 darwin_amd64 darwin_arm64 windows_amd64; do + bin="./build/actionbase_${VERSION}_${p}/actionbase" + if [ "$p" = windows_amd64 ]; then bin="${bin}.exe"; fi + goos="${p%_*}" + goarch="${p#*_}" + info=$(go version -m "$bin") + echo "$info" | grep -qF -- "-X main.Version=${VERSION}" \ + || { echo "::error::version not linked into $p"; exit 1; } + echo "$info" | grep -qF -- "GOOS=${goos}" \ + || { echo "::error::$p was not built for GOOS=${goos}"; exit 1; } + echo "$info" | grep -qF -- "GOARCH=${goarch}" \ + || { echo "::error::$p was not built for GOARCH=${goarch}"; exit 1; } + done + for p in linux_amd64 darwin_amd64 darwin_arm64; do + d="actionbase_${VERSION}_${p}" + tar -tzf "./build/${d}.tar.gz" | grep -qxF "${d}/actionbase" \ + || { echo "::error::${d}.tar.gz layout is wrong"; exit 1; } + done + w="actionbase_${VERSION}_windows_amd64" + unzip -l "./build/${w}.zip" | grep -qF "${w}/actionbase.exe" \ + || { echo "::error::${w}.zip layout is wrong"; exit 1; } + ( cd build && sha256sum -c checksums.txt ) \ + || { echo "::error::checksums.txt does not verify"; exit 1; } + + - uses: actions/upload-artifact@v4 + with: + name: cli-dist + path: | + cli/build/actionbase_${{ steps.resolve.outputs.version }}_*.tar.gz + cli/build/actionbase_${{ steps.resolve.outputs.version }}_*.zip + cli/build/checksums.txt + overwrite: true + retention-days: 7 + + publish: + name: Publish Release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cli/') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + VERSION: ${{ needs.build.outputs.version }} + steps: + - uses: actions/download-artifact@v4 + with: + name: cli-dist + path: dist + + - name: Check artifacts are present + run: | + set -euo pipefail + ls dist/*.tar.gz dist/*.zip dist/checksums.txt >/dev/null \ + || { echo "::error::dist/ is empty: build artifacts may have expired"; exit 1; } + + - name: Create draft release if absent + run: | + set -euo pipefail + if out=$(gh release view "$TAG" --json isDraft -q .isDraft 2>&1); then + [ "$out" = true ] \ + || { echo "::error::$TAG is already published; refusing to overwrite its assets"; exit 1; } + else + case "$out" in + *"release not found"*) + { + echo "Actionbase CLI \`$VERSION\`" + echo + for f in dist/*; do echo "- ${f#dist/}"; done + echo + echo 'Verify with `sha256sum -c checksums.txt`.' + } > notes.md + gh release create "$TAG" --draft --title "$TAG" --notes-file notes.md + ;; + *) + echo "$out" >&2 + exit 1 + ;; + esac + fi + + - name: Upload assets + run: gh release upload "$TAG" dist/* --clobber + + - name: Summarize + run: | + url=$(gh release view "$TAG" --json url -q .url) + { + echo "### Actionbase CLI $VERSION" + echo "$url" + for f in dist/*; do echo "- ${f#dist/}"; done + echo + echo '**This release is a DRAFT.** Review the notes and click Publish.' + echo 'Leave "Set as the latest release" unchecked.' + echo 'Then update `Formula/actionbase.rb` (`url` and `sha256`).' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/cli/Makefile b/cli/Makefile index 6708a36dd..38d156cc9 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -1,6 +1,6 @@ export CGO_ENABLED := 0 -.PHONY: build clean run test install fmt vet +.PHONY: build clean run test install fmt vet package BINARY_NAME=actionbase MAIN_PATH=./cmd/actionbase @@ -14,6 +14,10 @@ GOFILES=$(wildcard *.go) LD_FLAGS := -s -w -X main.Version=$(VERSION) +PLATFORMS := linux_amd64 darwin_amd64 darwin_arm64 +WIN_PLATFORM := windows_amd64 +SHASUM := $(shell command -v sha256sum >/dev/null 2>&1 && echo "sha256sum" || echo "shasum -a 256") + all: fmt vet build build: @@ -84,6 +88,20 @@ build-windows: build-all: build-linux build-macos build-windows @echo "All builds complete" +package: build-all + @echo "Packaging..." + @cd $(BUILD_DIR) && \ + rm -f checksums.txt && \ + for p in $(PLATFORMS); do \ + d="$(BINARY_NAME)_$(VERSION)_$$p"; \ + tar --owner=0 --group=0 --numeric-owner -czf "$$d.tar.gz" "$$d" || exit 1; \ + done && \ + w="$(BINARY_NAME)_$(VERSION)_$(WIN_PLATFORM)" && \ + rm -f "$$w.zip" && \ + zip -qrX "$$w.zip" "$$w" && \ + $(SHASUM) $(BINARY_NAME)_$(VERSION)_*.tar.gz $(BINARY_NAME)_$(VERSION)_*.zip > checksums.txt + @echo "Package complete: $(BUILD_DIR)" + help: @echo "Actionbase CLI Makefile" @echo "" @@ -105,4 +123,5 @@ help: @echo " build-macos - Build for macOS (AMD64 and ARM64)" @echo " build-windows- Build for Windows AMD64" @echo " build-all - Build for all platforms" + @echo " package - Build all platforms and create release archives" @echo " help - Show this help message" diff --git a/cli/README.md b/cli/README.md index bc18ec6bc..e87fcbdbc 100644 --- a/cli/README.md +++ b/cli/README.md @@ -31,41 +31,51 @@ go build -o build/actionbase ./cmd/actionbase ## Release Guide -### 1. Prepare Release +Releases are automated by the [Release CLI workflow](../.github/workflows/release-cli.yml). -#### Create Version Tag +### 1. Push a Version Tag -Create a version tag before creating a release on GitHub: +```bash +git tag cli/1.0.0 +git push origin cli/1.0.0 +``` + +Use the `cli/X.Y.Z` form with no `v` prefix. The CLI prepends `v` itself when reporting +`--version`, and this matches the existing `cli/0.0.1` tag. + +### 2. Publish the Draft Release + +The workflow cross-compiles every platform, packages the archives, and attaches them to a +**draft** release: + +- `actionbase_{VERSION}_darwin_amd64.tar.gz` +- `actionbase_{VERSION}_darwin_arm64.tar.gz` +- `actionbase_{VERSION}_linux_amd64.tar.gz` +- `actionbase_{VERSION}_windows_amd64.zip` +- `checksums.txt` + +Each archive holds a single directory named after the archive, with the binary inside. + +Review the draft, write the release notes, and click **Publish**. Leave *Set as the latest +release* unchecked, so the badge stays on the server release. + +Afterwards, update `Formula/actionbase.rb` to point at the new tag. Its `sha256` is the hash +of GitHub's generated **source** tarball, not of any archive listed in `checksums.txt`: ```bash -git tag -a v1.0.0 -m "Release version 1.0.0" -git push origin v1.0.0 +curl -sL https://github.com/kakao/actionbase/archive/refs/tags/cli/1.0.0.tar.gz | shasum -a 256 ``` -#### Build Binaries for All Platforms +To reproduce the archives locally: ```bash -make build-all +cd cli +make package VERSION=1.0.0 ``` -This command generates binaries for the following platforms: -- `build/actionbase-linux-amd64` - Linux AMD64 -- `build/actionbase-darwin-amd64` - macOS Intel -- `build/actionbase-darwin-arm64` - macOS Apple Silicon -- `build/actionbase-windows-amd64.exe` - Windows AMD64 - -### 2. Create GitHub Release - -1. Go to the [GitHub Releases page](https://github.com/kakao/actionbase/releases) -2. Click "Draft a new release" -3. Select the tag (e.g., `v1.0.0`) -4. Write the release title and description -5. Upload the built binary files: - - `actionbase-linux-amd64` - - `actionbase-darwin-amd64` - - `actionbase-darwin-arm64` - - `actionbase-windows-amd64.exe` -6. Click "Publish release" +> The two sections below describe filenames for an `install.sh` helper that is not present +> in this repository. They do not describe what the release workflow publishes; see +> section 2 above for that. ### 3. Binary Filename Convention