name: CLI Release on: push: tags: - 'cli/v*' # Trigger on tags like cli/v1.2.3 # Add permissions for creating releases permissions: contents: write # pull-requests: write # Not typically needed for a tag-triggered release workflow jobs: build: # No specific if condition needed here based on event, tag push is the trigger strategy: matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu artifact_name: buster-cli-linux-x86_64.tar.gz use_tar: true - os: macos-latest target: x86_64-apple-darwin artifact_name: buster-cli-darwin-x86_64.tar.gz use_tar: true - os: macos-latest target: aarch64-apple-darwin artifact_name: buster-cli-darwin-arm64.tar.gz use_tar: true - os: windows-latest target: x86_64-pc-windows-msvc artifact_name: buster-cli-windows-x86_64.zip use_tar: false runs-on: ${{ matrix.os }} steps: - name: Checkout code at the specific tag uses: actions/checkout@v4 with: ref: ${{ github.ref }} # Checks out the specific tag that triggered the workflow fetch-depth: 0 # Useful for some build processes or if release notes need history - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable target: ${{ matrix.target }} profile: minimal override: true - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 - name: Configure Cargo for optimized build run: | mkdir -p .cargo echo '[profile.release]' > .cargo/config.toml echo 'lto = true' >> .cargo/config.toml echo 'codegen-units = 1' >> .cargo/config.toml echo 'panic = "abort"' >> .cargo/config.toml echo 'opt-level = 3' >> .cargo/config.toml echo 'strip = true' >> .cargo/config.toml - name: Build optimized release working-directory: ./cli # Assuming this is the workspace root for the cli crate # If your CLI project is in cli/cli, adjust working-directory to ./cli/cli run: cargo build --release --target ${{ matrix.target }} --manifest-path ./cli/Cargo.toml - name: Determine Binary Name and Path id: binary_info shell: bash run: | # Ensure cli/target directory exists before find, in case of clean builds or different structures mkdir -p cli/target/${{ matrix.target }}/release CRATE_NAME_OUTPUT=$(basename $(find cli/target/${{ matrix.target }}/release -maxdepth 1 -type f -executable ! -name '*.dSYM' ! -name '*.pdb' 2>/dev/null || echo "buster")) # Default to buster if not found # If find returns nothing (e.g. build failed or path is wrong), CRATE_NAME_OUTPUT could be empty or an error message. # Fallback to a known name or fail if necessary. For now, using "buster" as a placeholder. if [ -z "$CRATE_NAME_OUTPUT" ] || ! [ -f "cli/target/${{ matrix.target }}/release/$CRATE_NAME_OUTPUT" ]; then echo "Warning: Could not automatically determine binary name. Assuming 'buster'." # Attempt to find 'buster' or 'buster.exe' directly if primary find fails if [[ "${{ matrix.os }}" == "windows-latest" ]]; then CRATE_NAME_CANDIDATE="buster.exe" else CRATE_NAME_CANDIDATE="buster" fi if [ -f "cli/target/${{ matrix.target }}/release/$CRATE_NAME_CANDIDATE" ]; then CRATE_NAME_OUTPUT=$CRATE_NAME_CANDIDATE else # If even the fallback isn't found, this will cause issues later. # Consider failing the step: echo "Error: Binary not found."; exit 1 # For now, proceeding with a default name and letting later steps handle missing file echo "Fallback binary '$CRATE_NAME_CANDIDATE' also not found. Proceeding with this name." CRATE_NAME_OUTPUT=${CRATE_NAME_CANDIDATE%.exe} # Store without .exe for consistency if needed elsewhere fi fi echo "CRATE_NAME=$CRATE_NAME_OUTPUT" echo "Binary name: $CRATE_NAME_OUTPUT" echo "binary_name=$CRATE_NAME_OUTPUT" >> $GITHUB_OUTPUT echo "binary_path=cli/target/${{ matrix.target }}/release/$CRATE_NAME_OUTPUT" echo "binary_path_val=cli/target/${{ matrix.target }}/release/$CRATE_NAME_OUTPUT" >> $GITHUB_OUTPUT - name: Compress binary (Unix) if: matrix.use_tar shell: bash run: | cd cli/target/${{ matrix.target }}/release tar czf ${{ matrix.artifact_name }} ${{ steps.binary_info.outputs.binary_name }} if [[ "${{ runner.os }}" == "macOS" ]]; then shasum -a 256 ${{ matrix.artifact_name }} > ${{ matrix.artifact_name }}.sha256 else sha256sum ${{ matrix.artifact_name }} > ${{ matrix.artifact_name }}.sha256 fi - name: Compress binary (Windows) if: matrix.use_tar == false shell: pwsh run: | cd cli/target/${{ matrix.target }}/release Compress-Archive -Path ${{ steps.binary_info.outputs.binary_name }}.exe -DestinationPath ${{ matrix.artifact_name }} Get-FileHash -Algorithm SHA256 ${{ matrix.artifact_name }} | Select-Object -ExpandProperty Hash > ${{ matrix.artifact_name }}.sha256 - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: buster-cli-${{ matrix.target }} path: | cli/target/${{ matrix.target }}/release/${{ matrix.artifact_name }} cli/target/${{ matrix.target }}/release/${{ matrix.artifact_name }}.sha256 retention-days: 1 release: needs: build runs-on: ubuntu-latest # No specific if condition needed here based on event, tag push is the trigger outputs: release_tag: ${{ steps.get_tag_info.outputs.cli_tag_name }} release_version: ${{ steps.get_tag_info.outputs.cli_version }} steps: - name: Checkout code at the specific tag uses: actions/checkout@v4 with: ref: ${{ github.ref }} # Checks out the specific tag that triggered the workflow fetch-depth: 0 - name: Extract CLI Tag and Version from Git Ref id: get_tag_info shell: bash run: | CLI_TAG_NAME="${{ github.ref_name }}" # Validate tag format if necessary (e.g., ensure it starts with cli/v) if [[ ! "$CLI_TAG_NAME" =~ ^cli/v[0-9]+\.[0-9]+\.[0-9]+(.*)$ ]]; then echo "Error: Tag $CLI_TAG_NAME does not match the expected format 'cli/vX.Y.Z'" # exit 1 # Optionally fail the job # For now, we'll proceed and let release creation fail if tag is not suitable fi CLI_VERSION=$(echo "$CLI_TAG_NAME" | sed 's#^cli/v##') echo "cli_tag_name=$CLI_TAG_NAME" >> $GITHUB_OUTPUT echo "cli_version=$CLI_VERSION" >> $GITHUB_OUTPUT echo "Extracted from Git Ref - CLI Tag: $CLI_TAG_NAME, CLI Version: $CLI_VERSION" - name: Download build artifacts uses: actions/download-artifact@v4 # No specific path needed, it downloads all to a directory named after the artifact - name: Create Release # if: steps.get_tag_info.outputs.cli_tag_name != '' # This check is implicitly handled by the tag trigger pattern uses: softprops/action-gh-release@v1 with: tag_name: ${{ steps.get_tag_info.outputs.cli_tag_name }} # Should be same as github.ref_name name: CLI Release v${{ steps.get_tag_info.outputs.cli_version }} files: | **/buster-cli-linux-x86_64.tar.gz **/buster-cli-linux-x86_64.tar.gz.sha256 **/buster-cli-darwin-x86_64.tar.gz **/buster-cli-darwin-x86_64.tar.gz.sha256 **/buster-cli-darwin-arm64.tar.gz **/buster-cli-darwin-arm64.tar.gz.sha256 **/buster-cli-windows-x86_64.zip **/buster-cli-windows-x86_64.zip.sha256 draft: false prerelease: false generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} update-homebrew-tap: name: Update Homebrew Tap needs: release runs-on: ubuntu-latest if: needs.release.outputs.release_tag != '' # Run only if a CLI tag was processed and release was attempted steps: - name: Get release version and tag from previous job id: release_info run: | echo "RELEASE_VERSION=${{ needs.release.outputs.release_version }}" >> $GITHUB_ENV echo "RELEASE_TAG=${{ needs.release.outputs.release_tag }}" >> $GITHUB_ENV echo "Using version: ${{ needs.release.outputs.release_version }} from tag: ${{ needs.release.outputs.release_tag }}" - name: Set up GitHub CLI uses: actions/setup-node@v4 # gh is often bundled, but this ensures it's available or can be installed with: node-version: '20' # Or any version that ensures gh is available - name: Download SHA256 sums from GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN to interact with the current repo's release GH_REPO: ${{ github.repository }} run: | gh release download ${{ env.RELEASE_TAG }} --pattern '*.sha256' -R $GH_REPO echo "Downloaded SHA256 files:" ls -la *.sha256 SHA_ARM64=$(cat buster-cli-darwin-arm64.tar.gz.sha256 | awk '{print $1}') SHA_INTEL=$(cat buster-cli-darwin-x86_64.tar.gz.sha256 | awk '{print $1}') SHA_LINUX=$(cat buster-cli-linux-x86_64.tar.gz.sha256 | awk '{print $1}') echo "SHA_ARM64=$SHA_ARM64" >> $GITHUB_ENV echo "SHA_INTEL=$SHA_INTEL" >> $GITHUB_ENV echo "SHA_LINUX=$SHA_LINUX" >> $GITHUB_ENV echo "ARM64 SHA: $SHA_ARM64" echo "Intel SHA: $SHA_INTEL" echo "Linux SHA: $SHA_LINUX" - name: Checkout Homebrew tap repository uses: actions/checkout@v4 with: repository: buster-so/buster-homebrew token: ${{ secrets.HOMEBREW_TAP_TOKEN }} # PAT with repo scope for buster-so/buster-homebrew path: buster-homebrew # Checkout to a specific path - name: Configure Git working-directory: ./buster-homebrew run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Update Homebrew Formula working-directory: ./buster-homebrew env: VERSION: ${{ env.RELEASE_VERSION }} TAG: ${{ env.RELEASE_TAG }} SHA_ARM64: ${{ env.SHA_ARM64 }} SHA_INTEL: ${{ env.SHA_INTEL }} SHA_LINUX: ${{ env.SHA_LINUX }} run: | FORMULA_FILE="Formula/buster.rb" TEMP_FORMULA_FILE="Formula/buster.rb.tmp" # URLs for artifacts URL_BASE="https://github.com/${{ github.repository_owner }}/buster/releases/download/$TAG" URL_ARM64="$URL_BASE/buster-cli-darwin-arm64.tar.gz" URL_INTEL="$URL_BASE/buster-cli-darwin-x86_64.tar.gz" URL_LINUX="$URL_BASE/buster-cli-linux-x86_64.tar.gz" echo "Updating $FORMULA_FILE with Version: $VERSION" echo "ARM64 URL: $URL_ARM64, SHA: $SHA_ARM64" echo "Intel URL: $URL_INTEL, SHA: $SHA_INTEL" echo "Linux URL: $URL_LINUX, SHA: $SHA_LINUX" # Update version sed "s/^ version .*/ version \\"$VERSION\\"/" "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" # Update top-level (defaults to ARM usually, as per your formula) sed -E "s#^ url .*# url \\"$URL_ARM64\\"#" "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" sed "s/^ sha256 .*/ sha256 \\"$SHA_ARM64\\"/" "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" # Update on_macos -> on_arm # Use a block to target sed within the on_arm block. Delimit with unique markers. awk ' BEGIN { printing = 1; in_arm_block = 0; } /on_macos do/,/end/ { if (/on_arm do/) { in_arm_block = 1; } if (in_arm_block && /url /) { print " url \\"\\"" ENVIRON["URL_ARM64"] "\\"\\"" next } if (in_arm_block && /sha256 /) { print " sha256 \\"\\"" ENVIRON["SHA_ARM64"] "\\"\\"" next } if (in_arm_block && /end/) { in_arm_block = 0; } } { print } ' "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" # Update on_macos -> on_intel awk ' BEGIN { printing = 1; in_intel_block = 0; } /on_macos do/,/end/ { if (/on_intel do/) { in_intel_block = 1; } if (in_intel_block && /url /) { print " url \\"\\"" ENVIRON["URL_INTEL"] "\\"\\"" next } if (in_intel_block && /sha256 /) { print " sha256 \\"\\"" ENVIRON["SHA_INTEL"] "\\"\\"" next } if (in_intel_block && /end/) { in_intel_block = 0; } } { print } ' "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" # Update on_linux awk ' BEGIN { printing = 1; in_linux_block = 0; } /on_linux do/,/end/ { if (/url / && !in_linux_block) { next } # Skip top-level url if not already processed if (/on_linux do/) { in_linux_block = 1; } if (in_linux_block && /url /) { print " url \\"\\"" ENVIRON["URL_LINUX"] "\\"\\"" next } if (in_linux_block && /sha256 /) { print " sha256 \\"\\"" ENVIRON["SHA_LINUX"] "\\"\\"" next } if (in_linux_block && /end/) { in_linux_block = 0; } } { print } ' "$FORMULA_FILE" > "$TEMP_FORMULA_FILE" && mv "$TEMP_FORMULA_FILE" "$FORMULA_FILE" echo "--- Formula file after updates ---" cat "$FORMULA_FILE" echo "--- End of formula file ---" - name: Commit and push changes to Homebrew tap working-directory: ./buster-homebrew run: | git add Formula/buster.rb # Check if there are changes to commit if git diff --staged --quiet; then echo "No changes to commit to Homebrew tap." else git commit -m "Update buster formula to version ${{ env.RELEASE_VERSION }} [skip ci]" git push echo "Pushed updated formula to buster-so/buster-homebrew." fi