feat: Homebrew bottle生成とFormula更新 #41
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_dispatch: | ||
| inputs: | ||
| dry_run: | ||
| description: 'Dry run (skip actual release)' | ||
| type: boolean | ||
| default: false | ||
| permissions: | ||
| contents: write | ||
| env: | ||
| CARGO_TERM_COLOR: always | ||
| BINARY_NAME: git-sc | ||
| jobs: | ||
| prepare-release: | ||
| name: Prepare Release | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| version: ${{ steps.bump.outputs.version }} | ||
| tag: ${{ steps.bump.outputs.tag }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Configure Git | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| - name: Bump version | ||
| id: bump | ||
| run: | | ||
| # Use 2-digit year (YY) for Windows MSI compatibility (major version must be <= 255) | ||
| YEAR=$(date +%y) | ||
| MONTH=$(date +%-m) | ||
| CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') | ||
| echo "Current version: ${CURRENT}" | ||
| if echo "${CURRENT}" | grep -qE '^[0-9]{2,4}\.[0-9]{1,2}\.[0-9]+$'; then | ||
| OLD_YEAR=$(echo "${CURRENT}" | cut -d. -f1) | ||
| OLD_MONTH=$(echo "${CURRENT}" | cut -d. -f2) | ||
| OLD_COUNTER=$(echo "${CURRENT}" | cut -d. -f3) | ||
| # Handle both 2-digit and 4-digit year formats | ||
| OLD_YEAR_2DIGIT=$(echo "${OLD_YEAR}" | sed 's/^20//') | ||
| if [ "${OLD_YEAR_2DIGIT}" = "${YEAR}" ] && [ "${OLD_MONTH}" = "${MONTH}" ]; then | ||
| NEW_COUNTER=$((OLD_COUNTER + 1)) | ||
| else | ||
| NEW_COUNTER=100 | ||
| fi | ||
| else | ||
| NEW_COUNTER=100 | ||
| fi | ||
| NEW_VERSION="${YEAR}.${MONTH}.${NEW_COUNTER}" | ||
| echo "New version: ${NEW_VERSION}" | ||
| # Only replace the first occurrence (package version, not dependency versions) | ||
| sed -i "0,/^version = \"[^\"]*\"/s//version = \"${NEW_VERSION}\"/" Cargo.toml | ||
| if [ -f "Cargo.lock" ]; then | ||
| cargo generate-lockfile 2>/dev/null || true | ||
| fi | ||
| echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT | ||
| echo "tag=v${NEW_VERSION}" >> $GITHUB_OUTPUT | ||
| - name: Commit and tag | ||
| if: ${{ !inputs.dry_run }} | ||
| run: | | ||
| git add Cargo.toml | ||
| [ -f "Cargo.lock" ] && git add Cargo.lock || true | ||
| git commit -m "Release ${{ steps.bump.outputs.tag }}" | ||
| git tag -a "${{ steps.bump.outputs.tag }}" -m "Release ${{ steps.bump.outputs.tag }}" | ||
| git push origin HEAD "${{ steps.bump.outputs.tag }}" | ||
| - name: Dry run info | ||
| if: ${{ inputs.dry_run }} | ||
| run: | | ||
| echo "🧪 Dry run mode - skipping commit and tag" | ||
| echo "Would release: ${{ steps.bump.outputs.tag }}" | ||
| build: | ||
| name: Build (${{ matrix.target }}) | ||
| needs: prepare-release | ||
| if: ${{ !inputs.dry_run }} | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - os: ubuntu-latest | ||
| target: x86_64-unknown-linux-gnu | ||
| artifact_name: git-sc | ||
| asset_name: git-sc-x86_64-unknown-linux-gnu.tar.gz | ||
| cross: false | ||
| - os: ubuntu-latest | ||
| target: aarch64-unknown-linux-gnu | ||
| artifact_name: git-sc | ||
| asset_name: git-sc-aarch64-unknown-linux-gnu.tar.gz | ||
| cross: true | ||
| - os: macos-latest | ||
| target: x86_64-apple-darwin | ||
| artifact_name: git-sc | ||
| asset_name: git-sc-x86_64-apple-darwin.tar.gz | ||
| cross: false | ||
| - os: macos-latest | ||
| target: aarch64-apple-darwin | ||
| artifact_name: git-sc | ||
| asset_name: git-sc-aarch64-apple-darwin.tar.gz | ||
| cross: false | ||
| - os: windows-latest | ||
| target: x86_64-pc-windows-msvc | ||
| artifact_name: git-sc.exe | ||
| asset_name: git-sc-x86_64-pc-windows-msvc.zip | ||
| cross: false | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.prepare-release.outputs.tag }} | ||
| - name: Install Rust toolchain | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| targets: ${{ matrix.target }} | ||
| - name: Install cross-compilation tools | ||
| if: matrix.cross | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y gcc-aarch64-linux-gnu | ||
| - name: Build with cross | ||
| if: matrix.cross | ||
| run: | | ||
| cargo install cross --git https://github.com/cross-rs/cross | ||
| cross build --release --target ${{ matrix.target }} | ||
| - name: Build | ||
| if: "!matrix.cross" | ||
| run: cargo build --release --target ${{ matrix.target }} | ||
| - name: Package (Unix) | ||
| if: runner.os != 'Windows' | ||
| run: | | ||
| cd target/${{ matrix.target }}/release | ||
| tar czvf ../../../${{ matrix.asset_name }} ${{ matrix.artifact_name }} | ||
| cd ../../.. | ||
| - name: Package (Windows) | ||
| if: runner.os == 'Windows' | ||
| run: | | ||
| cd target/${{ matrix.target }}/release | ||
| 7z a ../../../${{ matrix.asset_name }} ${{ matrix.artifact_name }} | ||
| cd ../../.. | ||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: ${{ matrix.asset_name }} | ||
| path: ${{ matrix.asset_name }} | ||
| release: | ||
| name: Create Release | ||
| needs: [prepare-release, build] | ||
| if: ${{ !inputs.dry_run }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Download all artifacts | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| path: artifacts | ||
| - name: Prepare release files | ||
| run: | | ||
| mkdir -p release | ||
| find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec mv {} release/ \; | ||
| ls -la release/ | ||
| - name: Create Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ needs.prepare-release.outputs.tag }} | ||
| name: ${{ needs.prepare-release.outputs.tag }} | ||
| body: | | ||
| ## git-sc ${{ needs.prepare-release.outputs.version }} | ||
| AI-powered smart commit message generator for coding agents | ||
| ### Installation | ||
| #### Homebrew (macOS/Linux) | ||
| ```bash | ||
| brew install owayo/git-sc/git-sc | ||
| ``` | ||
| #### Manual Download | ||
| Download the appropriate binary for your platform and add it to your PATH. | ||
| #### macOS (Apple Silicon) | ||
| ```bash | ||
| curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/git-sc-aarch64-apple-darwin.tar.gz | tar xz | ||
| sudo mv git-sc /usr/local/bin/ | ||
| ``` | ||
| #### macOS (Intel) | ||
| ```bash | ||
| curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/git-sc-x86_64-apple-darwin.tar.gz | tar xz | ||
| sudo mv git-sc /usr/local/bin/ | ||
| ``` | ||
| #### Linux (x86_64) | ||
| ```bash | ||
| curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/git-sc-x86_64-unknown-linux-gnu.tar.gz | tar xz | ||
| sudo mv git-sc /usr/local/bin/ | ||
| ``` | ||
| #### Linux (ARM64) | ||
| ```bash | ||
| curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/git-sc-aarch64-unknown-linux-gnu.tar.gz | tar xz | ||
| sudo mv git-sc /usr/local/bin/ | ||
| ``` | ||
| #### Windows | ||
| Download `git-sc-x86_64-pc-windows-msvc.zip`, extract, and add to PATH. | ||
| files: release/* | ||
| draft: false | ||
| prerelease: false | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| update-homebrew: | ||
| name: Update Homebrew Tap | ||
| needs: [prepare-release, release] | ||
| if: ${{ !inputs.dry_run }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Generate GitHub App Token | ||
| id: app-token | ||
| uses: tibdex/github-app-token@v2 | ||
| with: | ||
| app_id: ${{ secrets.APP_ID }} | ||
| private_key: ${{ secrets.PRIVATE_KEY }} | ||
| repositories: '["homebrew-git-sc"]' | ||
| - name: Create Homebrew bottles and calculate SHA256 | ||
| id: bottles | ||
| run: | | ||
| VERSION="${{ needs.prepare-release.outputs.version }}" | ||
| TAG="${{ needs.prepare-release.outputs.tag }}" | ||
| # Download source archive for formula URL | ||
| curl -sL "https://github.com/${{ github.repository }}/archive/refs/tags/${TAG}.tar.gz" -o source.tar.gz | ||
| SHA256_SOURCE=$(sha256sum source.tar.gz | cut -d' ' -f1) | ||
| echo "sha256_source=${SHA256_SOURCE}" >> $GITHUB_OUTPUT | ||
| # Download release binaries | ||
| curl -sL "https://github.com/${{ github.repository }}/releases/download/${TAG}/git-sc-aarch64-apple-darwin.tar.gz" -o mac_arm64.tar.gz | ||
| curl -sL "https://github.com/${{ github.repository }}/releases/download/${TAG}/git-sc-x86_64-apple-darwin.tar.gz" -o mac_x86_64.tar.gz | ||
| curl -sL "https://github.com/${{ github.repository }}/releases/download/${TAG}/git-sc-x86_64-unknown-linux-gnu.tar.gz" -o linux_x86_64.tar.gz | ||
| # Create Homebrew bottle tarball from release binary | ||
| create_bottle() { | ||
| local src_tarball=$1 | ||
| local bottle_tag=$2 | ||
| rm -rf bottle_staging | ||
| mkdir -p "bottle_staging/git-sc/${VERSION}/bin" | ||
| # Extract binary from release tarball into bottle structure | ||
| tar xzf "${src_tarball}" -C "bottle_staging/git-sc/${VERSION}/bin/" | ||
| chmod +x "bottle_staging/git-sc/${VERSION}/bin/git-sc" | ||
| # Create bottle tarball | ||
| local bottle_name="git-sc--${VERSION}.${bottle_tag}.bottle.tar.gz" | ||
| tar czvf "${bottle_name}" -C bottle_staging git-sc | ||
| # Return SHA256 | ||
| sha256sum "${bottle_name}" | cut -d' ' -f1 | ||
| } | ||
| SHA256_BOTTLE_ARM64_SONOMA=$(create_bottle mac_arm64.tar.gz arm64_sonoma) | ||
| SHA256_BOTTLE_SONOMA=$(create_bottle mac_x86_64.tar.gz sonoma) | ||
| SHA256_BOTTLE_X86_64_LINUX=$(create_bottle linux_x86_64.tar.gz x86_64_linux) | ||
| echo "sha256_bottle_arm64_sonoma=${SHA256_BOTTLE_ARM64_SONOMA}" >> $GITHUB_OUTPUT | ||
| echo "sha256_bottle_sonoma=${SHA256_BOTTLE_SONOMA}" >> $GITHUB_OUTPUT | ||
| echo "sha256_bottle_x86_64_linux=${SHA256_BOTTLE_X86_64_LINUX}" >> $GITHUB_OUTPUT | ||
| - name: Upload bottles to release | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| VERSION="${{ needs.prepare-release.outputs.version }}" | ||
| TAG="${{ needs.prepare-release.outputs.tag }}" | ||
| gh release upload "${TAG}" \ | ||
| "git-sc--${VERSION}.arm64_sonoma.bottle.tar.gz" \ | ||
| "git-sc--${VERSION}.sonoma.bottle.tar.gz" \ | ||
| "git-sc--${VERSION}.x86_64_linux.bottle.tar.gz" \ | ||
| --clobber \ | ||
| --repo "${{ github.repository }}" | ||
| - name: Update Homebrew Formula | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| run: | | ||
| VERSION="${{ needs.prepare-release.outputs.version }}" | ||
| TAG="${{ needs.prepare-release.outputs.tag }}" | ||
| SHA256_SOURCE="${{ steps.bottles.outputs.sha256_source }}" | ||
| SHA256_BOTTLE_ARM64_SONOMA="${{ steps.bottles.outputs.sha256_bottle_arm64_sonoma }}" | ||
| SHA256_BOTTLE_SONOMA="${{ steps.bottles.outputs.sha256_bottle_sonoma }}" | ||
| SHA256_BOTTLE_X86_64_LINUX="${{ steps.bottles.outputs.sha256_bottle_x86_64_linux }}" | ||
| # Clone homebrew tap | ||
| git clone "https://x-access-token:${GH_TOKEN}@github.com/owayo/homebrew-git-sc.git" | ||
| cd homebrew-git-sc | ||
| # Create Formula directory if not exists | ||
| mkdir -p Formula | ||
| # Create/update formula with bottle support | ||
| cat > Formula/git-sc.rb << 'FORMULA' | ||
| class GitSc < Formula | ||
| desc "AI-powered smart commit message generator for coding agents" | ||
| homepage "https://github.com/owayo/git-smart-commit" | ||
| url "URL_SOURCE_PLACEHOLDER" | ||
| sha256 "SHA256_SOURCE_PLACEHOLDER" | ||
| license "MIT" | ||
| bottle do | ||
| root_url "ROOT_URL_PLACEHOLDER" | ||
| sha256 cellar: :any_skip_relocation, arm64_sonoma: "SHA256_BOTTLE_ARM64_SONOMA_PLACEHOLDER" | ||
| sha256 cellar: :any_skip_relocation, sonoma: "SHA256_BOTTLE_SONOMA_PLACEHOLDER" | ||
| sha256 cellar: :any_skip_relocation, x86_64_linux: "SHA256_BOTTLE_X86_64_LINUX_PLACEHOLDER" | ||
| end | ||
| depends_on "rust" => :build | ||
| def install | ||
| system "cargo", "install", *std_cargo_args | ||
| end | ||
| test do | ||
| system "#{bin}/git-sc", "--version" | ||
| end | ||
| end | ||
| FORMULA | ||
| # Replace placeholders with actual values | ||
| sed -i "s|URL_SOURCE_PLACEHOLDER|https://github.com/owayo/git-smart-commit/archive/refs/tags/${TAG}.tar.gz|g" Formula/git-sc.rb | ||
| sed -i "s|SHA256_SOURCE_PLACEHOLDER|${SHA256_SOURCE}|g" Formula/git-sc.rb | ||
| sed -i "s|ROOT_URL_PLACEHOLDER|https://github.com/owayo/git-smart-commit/releases/download/${TAG}|g" Formula/git-sc.rb | ||
| sed -i "s|SHA256_BOTTLE_ARM64_SONOMA_PLACEHOLDER|${SHA256_BOTTLE_ARM64_SONOMA}|g" Formula/git-sc.rb | ||
| sed -i "s|SHA256_BOTTLE_SONOMA_PLACEHOLDER|${SHA256_BOTTLE_SONOMA}|g" Formula/git-sc.rb | ||
| sed -i "s|SHA256_BOTTLE_X86_64_LINUX_PLACEHOLDER|${SHA256_BOTTLE_X86_64_LINUX}|g" Formula/git-sc.rb | ||
| # Commit and push | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add Formula/git-sc.rb | ||
| git commit -m "Update git-sc to ${TAG}" | ||
| git push | ||
| release-summary: | ||
| name: Release Summary | ||
| needs: [prepare-release, release, update-homebrew] | ||
| if: ${{ !inputs.dry_run }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Summary | ||
| run: | | ||
| echo "🚀 Released ${{ needs.prepare-release.outputs.tag }}" | ||
| echo "" | ||
| echo "Assets uploaded:" | ||
| echo " - git-sc-x86_64-unknown-linux-gnu.tar.gz" | ||
| echo " - git-sc-aarch64-unknown-linux-gnu.tar.gz" | ||
| echo " - git-sc-x86_64-apple-darwin.tar.gz" | ||
| echo " - git-sc-aarch64-apple-darwin.tar.gz" | ||
| echo " - git-sc-x86_64-pc-windows-msvc.zip" | ||
| echo "" | ||
| echo "🍺 Homebrew tap updated: owayo/homebrew-git-sc" | ||