chore(release): v2.3.2 #154
Workflow file for this run
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: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: windows-latest | |
| args: '' | |
| label: Windows x64 | |
| - platform: macos-latest | |
| args: '--target aarch64-apple-darwin' | |
| label: macOS Apple Silicon | |
| - platform: ubuntu-22.04 | |
| args: '' | |
| label: Linux x64 | |
| - platform: ubuntu-22.04-arm | |
| args: '' | |
| label: Linux arm64 | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Resolve release tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| # Tag-pushes give refs/tags/v1.2.3; workflow_dispatch falls back to | |
| # the latest tag in the working tree. Bare version (no leading "v") | |
| # is what tauri stamps into bundle filenames; both forms are | |
| # exposed for downstream steps. | |
| TAG="${GITHUB_REF#refs/tags/}" | |
| if [ "$TAG" = "$GITHUB_REF" ]; then | |
| TAG=$(git describe --tags --abbrev=0) | |
| fi | |
| VERSION="${TAG#v}" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Install system dependencies (Linux) | |
| if: startsWith(matrix.platform, 'ubuntu-') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf rpm | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: src-ui/package-lock.json | |
| - name: Install & build frontend | |
| run: | | |
| cd src-ui | |
| npm ci | |
| npm run build | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ contains(matrix.args, 'aarch64-apple-darwin') && 'aarch64-apple-darwin' || '' }} | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.label }} | |
| - name: Install Tauri CLI | |
| run: cargo install tauri-cli --version "^2.0" --locked | |
| # ── macOS: build .app, re-sign without hardened runtime, then bundle DMG ── | |
| # tauri-bundler's default codesign for adhoc identities adds | |
| # --options runtime, which without Apple notarization causes Gatekeeper | |
| # to silently reject the GUI launch (click icon, nothing happens). We | |
| # build the .app first, codesign --deep WITHOUT runtime, then bundle | |
| # the DMG from the re-signed .app. Gatekeeper accepts adhoc-signed | |
| # apps that aren't claiming hardened runtime. | |
| - name: Build .app only (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: cargo tauri build --bundles app ${{ matrix.args }} | |
| - name: Re-sign without hardened runtime (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| APP=$(find target/aarch64-apple-darwin/release/bundle/macos -maxdepth 2 -name "*.app" | head -1) | |
| if [ -z "$APP" ]; then | |
| echo "::error::No .app found" | |
| find target -name "*.app" 2>/dev/null | |
| exit 1 | |
| fi | |
| echo "Re-signing: $APP" | |
| codesign --force --deep --sign - "$APP" | |
| codesign -dv --verbose=4 "$APP" 2>&1 | |
| # Sanity assertion: no 'runtime' flag, sealed resources present. | |
| if codesign -dv --verbose=4 "$APP" 2>&1 | grep -qE "flags=.*runtime"; then | |
| echo "::error::runtime flag still present after re-sign" | |
| exit 1 | |
| fi | |
| if codesign -dv --verbose=4 "$APP" 2>&1 | grep -q "Sealed Resources=none"; then | |
| echo "::error::Sealed Resources=none after re-sign" | |
| exit 1 | |
| fi | |
| - name: Build DMG from re-signed .app (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: cargo tauri bundle --bundles dmg ${{ matrix.args }} | |
| # ── Windows / Linux: standard one-shot bundle build ── | |
| - name: Build all bundles (Windows / Linux) | |
| if: matrix.platform != 'macos-latest' | |
| run: cargo tauri build ${{ matrix.args }} | |
| # ── Stage + rename artifacts to platform-labelled format ── | |
| # Tauri's bundler emits names like `Coffee CLI_1.9.2_amd64.deb` which | |
| # don't tell the end user which OS they're for and use inconsistent | |
| # arch slugs (amd64/x86_64/x64 across formats, plus rpm's `-1` release | |
| # suffix). We rename to the EchoBird-style convention: | |
| # Coffee.CLI_<version>_<OS>_<arch>.<ext> | |
| # so a glance at the GitHub Releases page makes the OS/arch obvious | |
| # and the install scripts have a single uniform pattern to grep. | |
| - name: Stage and rename artifacts | |
| id: stage | |
| shell: bash | |
| env: | |
| VERSION: ${{ steps.tag.outputs.version }} | |
| run: | | |
| set -e | |
| STAGING="$RUNNER_TEMP/coffee-release" | |
| mkdir -p "$STAGING" | |
| rename_one() { | |
| local src="$1" | |
| [ -f "$src" ] || return 0 | |
| local base | |
| base=$(basename "$src") | |
| local out="" | |
| case "$base" in | |
| # Windows | |
| *_x64-setup.exe) out="Coffee.CLI_${VERSION}_Windows_x64-setup.exe" ;; | |
| *_x64_*.msi|*_x64.msi) out="Coffee.CLI_${VERSION}_Windows_x64.msi" ;; | |
| # macOS (Coffee CLI ships arm64 only; Intel runs via Rosetta) | |
| *_aarch64.dmg) out="Coffee.CLI_${VERSION}_macOS_arm64.dmg" ;; | |
| # Linux .deb (Tauri uses Debian "amd64"/"arm64") | |
| *_amd64.deb) out="Coffee.CLI_${VERSION}_Linux_x64.deb" ;; | |
| *_arm64.deb) out="Coffee.CLI_${VERSION}_Linux_arm64.deb" ;; | |
| # Linux .AppImage (upstream AppImage uses "amd64"/"aarch64") | |
| *_amd64.AppImage) out="Coffee.CLI_${VERSION}_Linux_x64.AppImage" ;; | |
| *_aarch64.AppImage) out="Coffee.CLI_${VERSION}_Linux_arm64.AppImage" ;; | |
| # Linux .rpm (uses dot-separated `<name>-<ver>-<release>.<arch>.rpm`) | |
| *.x86_64.rpm) out="Coffee.CLI_${VERSION}_Linux_x64.rpm" ;; | |
| *.aarch64.rpm) out="Coffee.CLI_${VERSION}_Linux_arm64.rpm" ;; | |
| *) echo " skip: $base"; return 0 ;; | |
| esac | |
| cp "$src" "$STAGING/$out" | |
| echo " staged: $base → $out" | |
| } | |
| echo "Bundle scan roots:" | |
| for root in \ | |
| target/release/bundle \ | |
| target/aarch64-apple-darwin/release/bundle \ | |
| ; do | |
| [ -d "$root" ] || continue | |
| echo " $root" | |
| while IFS= read -r f; do | |
| rename_one "$f" | |
| done < <(find "$root" -type f \ | |
| \( -name "*.exe" -o -name "*.msi" \ | |
| -o -name "*.dmg" \ | |
| -o -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) ) | |
| done | |
| echo "" | |
| echo "Final staged assets:" | |
| ls -la "$STAGING" | |
| echo "staging=$STAGING" >> "$GITHUB_OUTPUT" | |
| # ── Create release if missing, then upload all staged artifacts ── | |
| # Each matrix job races to create the release; `gh release create` | |
| # fails idempotently if another job already created it (caught and | |
| # ignored), then `gh release upload --clobber` overwrites any | |
| # earlier-job placeholder of the same filename. | |
| - name: Upload to GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -e | |
| TAG="${{ steps.tag.outputs.tag }}" | |
| STAGING="${{ steps.stage.outputs.staging }}" | |
| # Wait briefly in case another matrix job is mid-create. | |
| for i in 1 2 3 4 5 6; do | |
| if gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| break | |
| fi | |
| echo "Release $TAG not yet present (attempt $i/6); waiting 10s..." | |
| sleep 10 | |
| done | |
| if ! gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| echo "Creating release $TAG" | |
| # Notes body lives in install/release-notes-template.md so the | |
| # YAML block doesn't have to fight bash heredoc indentation | |
| # rules. `gh release create --notes-file` accepts a path; we | |
| # don't need to interpolate the tag because the body is the | |
| # generic install instructions, not version-specific changelog. | |
| NOTES_FILE="$GITHUB_WORKSPACE/install/release-notes-template.md" | |
| if [ -f "$NOTES_FILE" ]; then | |
| gh release create "$TAG" --repo "${{ github.repository }}" \ | |
| --title "Coffee CLI $TAG" \ | |
| --notes-file "$NOTES_FILE" \ | |
| || echo "Release already exists (created by sibling matrix job)" | |
| else | |
| gh release create "$TAG" --repo "${{ github.repository }}" \ | |
| --title "Coffee CLI $TAG" \ | |
| --notes "Coffee CLI $TAG. See README for install instructions." \ | |
| || echo "Release already exists (created by sibling matrix job)" | |
| fi | |
| fi | |
| # Upload everything in staging. --clobber overwrites if a previous | |
| # job already pushed an asset with the same name. | |
| shopt -s nullglob | |
| for f in "$STAGING"/*; do | |
| echo "uploading: $(basename "$f")" | |
| gh release upload "$TAG" "$f" --repo "${{ github.repository }}" --clobber | |
| done |