Release #62
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_run: | |
| workflows: ["CI"] | |
| branches: [main] | |
| types: [completed] | |
| workflow_dispatch: | |
| # Prevent concurrent releases | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| check: | |
| if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| next_version: ${{ steps.version.outputs.next_version }} | |
| changelog: ${{ steps.changelog.outputs.changelog }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if release needed | |
| id: check | |
| run: | | |
| # Skip if the last commit is a release commit (avoid infinite loop) | |
| LAST_MSG=$(git log -1 --pretty=%s) | |
| if [[ "$LAST_MSG" == chore:\ release* ]]; then | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Skip if no meaningful changes since last tag | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -z "$LAST_TAG" ]; then | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Check for feat/fix/refactor commits since last tag | |
| COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=%s) | |
| if echo "$COMMITS" | grep -qE '^(feat|fix|refactor|perf|breaking)'; then | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Determine next version | |
| if: steps.check.outputs.should_release == 'true' | |
| id: version | |
| run: | | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") | |
| echo "Last tag: $LAST_TAG" | |
| # Parse semver | |
| VERSION=${LAST_TAG#v} | |
| MAJOR=$(echo $VERSION | cut -d. -f1) | |
| MINOR=$(echo $VERSION | cut -d. -f2) | |
| PATCH=$(echo $VERSION | cut -d. -f3) | |
| # Determine bump type from commits | |
| COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=%s 2>/dev/null || git log --pretty=%s) | |
| if echo "$COMMITS" | grep -qiE '^breaking|BREAKING CHANGE'; then | |
| MAJOR=$((MAJOR + 1)) | |
| MINOR=0 | |
| PATCH=0 | |
| elif echo "$COMMITS" | grep -qE '^feat'; then | |
| MINOR=$((MINOR + 1)) | |
| PATCH=0 | |
| else | |
| PATCH=$((PATCH + 1)) | |
| fi | |
| NEXT="v${MAJOR}.${MINOR}.${PATCH}" | |
| echo "Next version: $NEXT" | |
| echo "next_version=$NEXT" >> $GITHUB_OUTPUT | |
| - name: Generate changelog | |
| if: steps.check.outputs.should_release == 'true' | |
| id: changelog | |
| run: | | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -z "$LAST_TAG" ]; then | |
| RANGE="HEAD" | |
| else | |
| RANGE="${LAST_TAG}..HEAD" | |
| fi | |
| { | |
| echo 'changelog<<CHANGELOG_EOF' | |
| # Features | |
| FEATS=$(git log $RANGE --pretty="- %s (%h)" --grep="^feat" --extended-regexp 2>/dev/null) | |
| if [ -n "$FEATS" ]; then | |
| echo "### Features" | |
| echo "$FEATS" | |
| echo "" | |
| fi | |
| # Fixes | |
| FIXES=$(git log $RANGE --pretty="- %s (%h)" --grep="^fix" --extended-regexp 2>/dev/null) | |
| if [ -n "$FIXES" ]; then | |
| echo "### Bug Fixes" | |
| echo "$FIXES" | |
| echo "" | |
| fi | |
| # Other changes (refactor, perf, etc.) | |
| OTHERS=$(git log $RANGE --pretty="- %s (%h)" --grep="^refactor|^perf|^chore|^ci|^docs|^test|^build" --extended-regexp 2>/dev/null) | |
| if [ -n "$OTHERS" ]; then | |
| echo "### Other Changes" | |
| echo "$OTHERS" | |
| echo "" | |
| fi | |
| echo "### Platforms" | |
| echo "- Linux (amd64, arm64, arm, 386, mips, mipsle, mips64, mips64le, ppc64le, s390x, riscv64)" | |
| echo "- macOS (amd64, arm64)" | |
| echo "- Windows (amd64, arm64, 386)" | |
| echo "- FreeBSD (amd64, arm64)" | |
| echo "- OpenBSD (amd64)" | |
| echo "- NetBSD (amd64)" | |
| echo 'CHANGELOG_EOF' | |
| } >> $GITHUB_OUTPUT | |
| build-cross: | |
| needs: check | |
| if: needs.check.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.24.x' | |
| cache: false | |
| - name: Install dependencies | |
| run: go mod download | |
| - name: Build all non-darwin targets | |
| env: | |
| CGO_ENABLED: '0' | |
| run: | | |
| VERSION=${{ needs.check.outputs.next_version }} | |
| COMMIT=$(git rev-parse --short HEAD) | |
| DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| LDFLAGS="-s -w -X codes/internal/commands.Version=${VERSION} -X codes/internal/commands.Commit=${COMMIT} -X codes/internal/commands.Date=${DATE}" | |
| # Define targets: GOOS:GOARCH[:extra_env] | |
| TARGETS=( | |
| "linux:amd64" | |
| "linux:arm64" | |
| "linux:arm:GOARM=6" | |
| "linux:386" | |
| "linux:mips:GOMIPS=softfloat" | |
| "linux:mipsle:GOMIPS=softfloat" | |
| "linux:mips64" | |
| "linux:mips64le" | |
| "linux:ppc64le" | |
| "linux:s390x" | |
| "linux:riscv64" | |
| "windows:amd64" | |
| "windows:arm64" | |
| "windows:386" | |
| "freebsd:amd64" | |
| "freebsd:arm64" | |
| "openbsd:amd64" | |
| "netbsd:amd64" | |
| ) | |
| for target in "${TARGETS[@]}"; do | |
| IFS=':' read -r os arch extra <<< "$target" | |
| echo "==> Building ${os}/${arch} ${extra:+($extra)}" | |
| # Set extra env vars if any | |
| EXTRA_ENV="" | |
| if [ -n "$extra" ]; then | |
| export ${extra} | |
| EXTRA_ENV="$extra" | |
| fi | |
| OUTPUT="codes" | |
| if [ "$os" = "windows" ]; then | |
| OUTPUT="codes.exe" | |
| fi | |
| GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o "dist/codes-${os}-${arch}/${OUTPUT}" ./cmd/codes | |
| # Unset extra env vars | |
| if [ -n "$EXTRA_ENV" ]; then | |
| unset ${EXTRA_ENV%%=*} | |
| fi | |
| echo " OK: dist/codes-${os}-${arch}/${OUTPUT}" | |
| done | |
| echo "" | |
| echo "==> All builds completed:" | |
| ls -la dist/*/ | |
| - name: Smoke test linux-amd64 | |
| run: | | |
| chmod +x dist/codes-linux-amd64/codes | |
| dist/codes-linux-amd64/codes version | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cross-binaries | |
| path: dist/ | |
| retention-days: 7 | |
| build-darwin: | |
| needs: check | |
| if: needs.check.outputs.should_release == 'true' | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.24.x' | |
| cache: false | |
| - name: Install dependencies | |
| run: go mod download | |
| - name: Build darwin targets | |
| env: | |
| CGO_ENABLED: '0' | |
| run: | | |
| VERSION=${{ needs.check.outputs.next_version }} | |
| COMMIT=$(git rev-parse --short HEAD) | |
| DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| LDFLAGS="-s -w -X codes/internal/commands.Version=${VERSION} -X codes/internal/commands.Commit=${COMMIT} -X codes/internal/commands.Date=${DATE}" | |
| for arch in amd64 arm64; do | |
| echo "==> Building darwin/${arch}" | |
| GOOS=darwin GOARCH=$arch go build -ldflags "$LDFLAGS" -o "dist/codes-darwin-${arch}/codes" ./cmd/codes | |
| echo " OK" | |
| done | |
| - name: Sign macOS binaries | |
| run: | | |
| for arch in amd64 arm64; do | |
| echo "==> Signing darwin/${arch}" | |
| codesign --force --sign - "dist/codes-darwin-${arch}/codes" | |
| done | |
| - name: Smoke test native binary | |
| run: | | |
| # macos-latest is arm64, test that binary | |
| chmod +x dist/codes-darwin-arm64/codes | |
| dist/codes-darwin-arm64/codes version | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: darwin-binaries | |
| path: dist/ | |
| retention-days: 7 | |
| release: | |
| needs: [check, build-cross, build-darwin] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Download cross-compiled binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cross-binaries | |
| path: dist | |
| - name: Download darwin binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: darwin-binaries | |
| path: dist | |
| - name: Package release archives | |
| run: | | |
| VERSION=${{ needs.check.outputs.next_version }} | |
| mkdir -p release | |
| for dir in dist/codes-*; do | |
| [ -d "$dir" ] || continue | |
| target=$(basename "$dir" | sed 's/codes-//') | |
| os=$(echo "$target" | cut -d- -f1) | |
| arch=$(echo "$target" | cut -d- -f2) | |
| if [ "$os" = "windows" ]; then | |
| # ZIP for Windows | |
| ARCHIVE="codes-${VERSION}-${os}-${arch}.zip" | |
| echo "==> Packaging ${ARCHIVE}" | |
| (cd "$dir" && zip -q "${OLDPWD}/release/${ARCHIVE}" codes.exe) | |
| else | |
| # tar.gz for Unix | |
| ARCHIVE="codes-${VERSION}-${os}-${arch}.tar.gz" | |
| echo "==> Packaging ${ARCHIVE}" | |
| chmod +x "$dir/codes" | |
| tar -czf "release/${ARCHIVE}" -C "$dir" codes | |
| fi | |
| done | |
| echo "" | |
| echo "==> All archives:" | |
| ls -lh release/ | |
| - name: Generate checksums | |
| run: | | |
| VERSION=${{ needs.check.outputs.next_version }} | |
| cd release | |
| sha256sum codes-${VERSION}-* > "codes-${VERSION}-checksums.txt" | |
| echo "==> Checksums:" | |
| cat "codes-${VERSION}-checksums.txt" | |
| - name: Create release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.check.outputs.next_version }} | |
| name: ${{ needs.check.outputs.next_version }} | |
| body: ${{ needs.check.outputs.changelog }} | |
| files: release/* | |
| draft: false | |
| prerelease: false | |
| make_latest: true |