chore: reset versions to v0.0.12 for clean v0.1.0 release #128
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: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # ───────────────────────────────────────────────────────────── | |
| # Release Please: Create/update release PR with version bump | |
| # | |
| # When a release PR is merged, release-please creates a published | |
| # release (required for git tag creation — draft releases don't | |
| # create tags, which breaks version tracking). We immediately | |
| # convert it to draft so users never see an empty release. | |
| # | |
| # This is safe because: | |
| # - /releases/latest API returns the PREVIOUS release while draft | |
| # - Tauri updater checks /releases/latest/download/latest.json | |
| # → still resolves to old release → no broken updates | |
| # - discover.ui fetches /releases/latest → still gets old release | |
| # - Once publish-release un-drafts, /latest atomically switches | |
| # to the new release with all artifacts already attached | |
| # ───────────────────────────────────────────────────────────── | |
| release-please: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| outputs: | |
| release_created: ${{ steps.release.outputs.release_created }} | |
| release_id: ${{ steps.release.outputs.id }} | |
| tag_name: ${{ steps.release.outputs.tag_name }} | |
| version: ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} | |
| steps: | |
| - uses: googleapis/release-please-action@v4 | |
| id: release | |
| with: | |
| manifest-file: .release-please-manifest.json | |
| config-file: release-please-config.json | |
| # release-please creates a published release (so the git tag is created | |
| # and version tracking works). Immediately convert to draft so users | |
| # don't see an empty release while artifacts are being built. | |
| # Uses github-script (not gh CLI) for minimal latency — the octokit | |
| # client is pre-authenticated, no process spawn needed. | |
| - name: Convert release to draft | |
| if: steps.release.outputs.release_created == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.repos.updateRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: ${{ steps.release.outputs.id }}, | |
| draft: true, | |
| }); | |
| # ───────────────────────────────────────────────────────────── | |
| # Build Release: Build Tauri app when release is created | |
| # ───────────────────────────────────────────────────────────── | |
| build-release: | |
| needs: release-please | |
| if: needs.release-please.outputs.release_created == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| artifact: linux | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| artifact: windows | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| artifact: macos-arm | |
| - os: macos-latest | |
| target: x86_64-apple-darwin | |
| artifact: macos-intel | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Linux deps | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: ./.github/actions/install-linux-deps | |
| with: | |
| verify_glib: 'false' | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| env: | |
| PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.target }}-release | |
| env: | |
| PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'pnpm' | |
| - run: pnpm install --frozen-lockfile | |
| # Import Apple certificate ourselves, then DON'T pass APPLE_CERTIFICATE | |
| # to tauri-action. Tauri's bundler uses var_os() which treats empty | |
| # strings as present (Some("")), so we must completely omit the env var. | |
| # Instead we import the cert here and only pass APPLE_SIGNING_IDENTITY. | |
| - name: Import Apple certificate | |
| if: matrix.os == 'macos-latest' || matrix.os == 'macos-15' | |
| id: apple-cert | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| if [ -z "$APPLE_CERTIFICATE" ] || [ -z "$KEYCHAIN_PASSWORD" ]; then | |
| echo "No Apple certificate configured — using ad-hoc signing" | |
| echo "identity=-" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| if [ ! -s certificate.p12 ]; then | |
| echo "Certificate decode produced empty file — using ad-hoc signing" | |
| rm -f certificate.p12 | |
| echo "identity=-" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| if ! security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign; then | |
| echo "Certificate import failed — using ad-hoc signing" | |
| rm -f certificate.p12 | |
| echo "identity=-" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain | |
| rm certificate.p12 | |
| echo "identity=${{ secrets.APPLE_SIGNING_IDENTITY }}" >> "$GITHUB_OUTPUT" | |
| echo "cert_ok=true" >> "$GITHUB_OUTPUT" | |
| # IMPORTANT: Do NOT pass APPLE_CERTIFICATE to tauri-action. | |
| # We handle certificate import ourselves above. Tauri's bundler | |
| # treats empty strings as "certificate present" and tries to import, | |
| # which fails. By omitting the var entirely, the bundler skips import | |
| # and uses the identity from APPLE_SIGNING_IDENTITY (or ad-hoc). | |
| - name: Build Tauri app | |
| uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig | |
| APPLE_SIGNING_IDENTITY: ${{ steps.apple-cert.outputs.identity }} | |
| # Notarization only when cert import succeeded | |
| APPLE_ID: ${{ steps.apple-cert.outputs.cert_ok == 'true' && secrets.APPLE_ID || '' }} | |
| APPLE_PASSWORD: ${{ steps.apple-cert.outputs.cert_ok == 'true' && secrets.APPLE_PASSWORD || '' }} | |
| APPLE_TEAM_ID: ${{ steps.apple-cert.outputs.cert_ok == 'true' && secrets.APPLE_TEAM_ID || '' }} | |
| with: | |
| projectPath: apps/desktop | |
| # Upload to the existing draft release | |
| releaseId: ${{ needs.release-please.outputs.release_id }} | |
| updaterJsonKeepUniversal: true | |
| # ───────────────────────────────────────────────────────────── | |
| # Publish Release: Flip draft → published after all artifacts | |
| # are attached, so /releases/latest always has all assets | |
| # ───────────────────────────────────────────────────────────── | |
| publish-release: | |
| needs: [release-please, build-release] | |
| if: needs.release-please.outputs.release_created == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release edit "${{ needs.release-please.outputs.tag_name }}" \ | |
| --draft=false \ | |
| --repo "${{ github.repository }}" | |
| # ───────────────────────────────────────────────────────────── | |
| # Update Homebrew Tap: Push new version to homebrew-mcpmux | |
| # ───────────────────────────────────────────────────────────── | |
| update-homebrew: | |
| needs: [release-please, publish-release] | |
| if: needs.release-please.outputs.release_created == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Compute SHA256 and update cask | |
| env: | |
| GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release-please.outputs.version }}" | |
| BASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}" | |
| # Download DMGs and compute SHA256 | |
| echo "Downloading macOS DMGs..." | |
| curl -fSL "${BASE_URL}/McpMux_${VERSION}_aarch64.dmg" -o arm64.dmg || { echo "ARM64 DMG not found — skipping Homebrew update"; exit 0; } | |
| curl -fSL "${BASE_URL}/McpMux_${VERSION}_x64.dmg" -o x64.dmg || { echo "x64 DMG not found — skipping Homebrew update"; exit 0; } | |
| SHA_ARM64=$(shasum -a 256 arm64.dmg | cut -d' ' -f1) | |
| SHA_X64=$(shasum -a 256 x64.dmg | cut -d' ' -f1) | |
| echo "ARM64 SHA256: $SHA_ARM64" | |
| echo "x64 SHA256: $SHA_X64" | |
| # Clone the tap repo and update the cask | |
| git clone https://x-access-token:${GH_TOKEN}@github.com/mcpmux/homebrew-mcpmux.git tap | |
| # Generate cask file (Ruby heredoc content with shell variable expansion) | |
| CASK_FILE="tap/Casks/mcpmux.rb" | |
| { | |
| echo 'cask "mcpmux" do' | |
| echo ' arch arm: "aarch64", intel: "x64"' | |
| echo '' | |
| echo " version \"${VERSION}\"" | |
| echo " sha256 arm: \"${SHA_ARM64}\"," | |
| echo " intel: \"${SHA_X64}\"" | |
| echo '' | |
| echo ' url "https://github.com/mcpmux/mcp-mux/releases/download/v#{version}/McpMux_#{version}_#{arch}.dmg",' | |
| echo ' verified: "github.com/mcpmux/mcp-mux/"' | |
| echo '' | |
| echo ' name "McpMux"' | |
| echo ' desc "Unified MCP gateway and manager for AI clients"' | |
| echo ' homepage "https://mcpmux.com"' | |
| echo '' | |
| echo ' depends_on macos: ">= :high_sierra"' | |
| echo '' | |
| echo ' livecheck do' | |
| echo ' url "https://github.com/mcpmux/mcp-mux/releases/latest"' | |
| echo ' strategy :github_latest' | |
| echo ' end' | |
| echo '' | |
| echo ' app "McpMux.app"' | |
| echo '' | |
| echo ' # Remove quarantine for ad-hoc signed app (no Apple Developer ID)' | |
| echo ' postflight do' | |
| echo ' system_command "/usr/bin/xattr",' | |
| echo ' args: ["-cr", "#{appdir}/McpMux.app"]' | |
| echo ' end' | |
| echo '' | |
| echo ' zap trash: [' | |
| echo ' "~/Library/Application Support/com.mcpmux.desktop",' | |
| echo ' "~/Library/Preferences/com.mcpmux.desktop.plist",' | |
| echo ' "~/Library/Caches/com.mcpmux.desktop",' | |
| echo ' "~/Library/Saved Application State/com.mcpmux.desktop.savedState",' | |
| echo ' ]' | |
| echo 'end' | |
| } > "$CASK_FILE" | |
| cd tap | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Casks/mcpmux.rb | |
| git commit -m "Update mcpmux to ${VERSION}" | |
| git push | |
| # ───────────────────────────────────────────────────────────── | |
| # Update APT Repository: Add .deb to self-hosted APT repo on R2 | |
| # ───────────────────────────────────────────────────────────── | |
| update-apt-repo: | |
| needs: [release-please, publish-release] | |
| if: needs.release-please.outputs.release_created == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: sudo apt-get update && sudo apt-get install -y reprepro | |
| - name: Configure AWS CLI for R2 | |
| run: | | |
| aws configure set aws_access_key_id "${{ secrets.R2_ACCESS_KEY_ID }}" | |
| aws configure set aws_secret_access_key "${{ secrets.R2_SECRET_ACCESS_KEY }}" | |
| aws configure set default.region auto | |
| env: | |
| AWS_DEFAULT_OUTPUT: json | |
| - name: Import GPG signing key | |
| run: echo "${{ secrets.APT_GPG_PRIVATE_KEY }}" | gpg --batch --import | |
| - name: Download .deb from GitHub Release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release-please.outputs.version }}" | |
| mkdir -p artifacts | |
| # Download all .deb files from the release | |
| gh release download "v${VERSION}" --pattern "*.deb" --dir artifacts | |
| - name: Sync existing APT repo from R2 | |
| run: | | |
| mkdir -p repo | |
| aws s3 sync "s3://mcpmux-apt/" repo/ \ | |
| --endpoint-url "${{ secrets.R2_ENDPOINT }}" \ | |
| || echo "No existing repo (first run)" | |
| - name: Update APT repo with new packages | |
| run: | | |
| # Copy reprepro config | |
| cp -r scripts/apt-repo/conf repo/ | |
| # Add each .deb package | |
| for deb in artifacts/*.deb; do | |
| echo "Adding: $deb" | |
| reprepro -b repo includedeb stable "$deb" | |
| done | |
| # Export public key | |
| gpg --armor --export hello@mcpmux.com > repo/key.gpg | |
| - name: Sync APT repo back to R2 | |
| run: | | |
| aws s3 sync repo/ "s3://mcpmux-apt/" \ | |
| --endpoint-url "${{ secrets.R2_ENDPOINT }}" \ | |
| --delete |