Skip to content

feat: update chat surfaces and template workflows (#2787) #271

feat: update chat surfaces and template workflows (#2787)

feat: update chat surfaces and template workflows (#2787) #271

name: Clips Desktop Release
# Release channel for the Clips Tauri menu-bar app, SEPARATE from the main
# Electron desktop app. Uses tag prefix `clips-v*` so the two release trains
# don't collide on GitHub Releases. The in-app updater fetches its manifest
# from the `clips-latest` stable-pointer release (assets overwritten each run),
# which is what `endpoints` in tauri.conf.json points at.
on:
push:
branches: [main]
paths:
- "templates/clips/desktop/**"
- "packages/shared-app-config/**"
workflow_dispatch:
inputs:
version:
description: "Version (e.g. 0.2.0). Leave empty to use package.json version."
required: false
type: string
permissions:
contents: write
env:
TAURI_APP_PATH: templates/clips/desktop
jobs:
build-tauri:
strategy:
fail-fast: false
# Serialize so platform jobs can't race to create the draft release at
# the same time — when both call tauri-action concurrently, one job's
# bundles + latest.json get orphaned (clips-v0.1.74 lost its macOS
# assets this way, leaving the manifest Windows-only and breaking the
# Tauri updater on macOS clients).
max-parallel: 1
matrix:
include:
- os: macos-latest
rust-targets: "aarch64-apple-darwin,x86_64-apple-darwin"
args: "--target universal-apple-darwin"
- os: windows-latest
rust-targets: ""
args: ""
- os: ubuntu-22.04
rust-targets: ""
args: "--bundles appimage,deb,rpm"
runs-on: ${{ matrix.os }}
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.os == 'macos-latest' && '13.0' || '' }}
GGML_METAL_MACOSX_VERSION_MIN: ${{ matrix.os == 'macos-latest' && '13.0' || '' }}
outputs:
app-version: ${{ steps.resolve-version.outputs.version }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libfuse2 \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-libav \
gstreamer1.0-pipewire
- name: Guard — refuse to release with placeholder updater pubkey
shell: bash
run: |
if grep -q 'REPLACE_WITH_TAURI_UPDATER_PUBKEY' "$TAURI_APP_PATH/src-tauri/tauri.conf.json"; then
echo "::error::tauri.conf.json still has the placeholder updater pubkey."
echo "::error::Generate a keypair with 'pnpm exec tauri signer generate' and commit the public key."
echo "::error::See templates/clips/desktop/README.md for the full setup."
exit 1
fi
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.rust-targets }}
- name: Link Apple compiler runtime for Rust
if: runner.os == 'macOS'
shell: bash
run: |
# Xcode emits __isPlatformVersionAtLeast for whisper-rs-sys's
# availability checks. Rust's final link does not add compiler-rt.
CLANG_RUNTIME="$(xcrun --sdk macosx clang -print-libgcc-file-name)"
echo "RUSTFLAGS=${RUSTFLAGS:-} -C link-arg=${CLANG_RUNTIME}" >> "$GITHUB_ENV"
- name: Rust cache
uses: swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: ${{ env.TAURI_APP_PATH }}/src-tauri
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Auto-bump version (push trigger only)
if: github.event_name == 'push'
working-directory: ${{ env.TAURI_APP_PATH }}
shell: bash
run: |
CURRENT=$(node -p "require('./package.json').version")
# Use major.minor from package.json + run_number as patch for
# guaranteed uniqueness without committing version bumps back.
NEXT=$(node -e "const [ma,mi]=process.argv[1].split('.'); console.log(ma+'.'+mi+'.${{ github.run_number }}')" "$CURRENT")
npm version "$NEXT" --no-git-tag-version
node -e "
const fs = require('fs');
const tc = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));
tc.version = '$NEXT';
fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(tc, null, 2) + '\n');
const cargo = fs.readFileSync('src-tauri/Cargo.toml','utf8');
fs.writeFileSync('src-tauri/Cargo.toml', cargo.replace(/^version = \".*\"/m, 'version = \"$NEXT\"'));
"
echo "Auto-bumped version: $CURRENT → $NEXT"
- name: Bump version (manual)
if: inputs.version != ''
working-directory: ${{ env.TAURI_APP_PATH }}
shell: bash
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
npm version "$RELEASE_VERSION" --no-git-tag-version
node -e "
const fs = require('fs');
const tc = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));
tc.version = '$RELEASE_VERSION';
fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(tc, null, 2) + '\n');
const cargo = fs.readFileSync('src-tauri/Cargo.toml','utf8');
fs.writeFileSync('src-tauri/Cargo.toml', cargo.replace(/^version = \".*\"/m, 'version = \"$RELEASE_VERSION\"'));
"
- name: Resolve version
id: resolve-version
shell: bash
run: |
V=$(node -p "require('./${{ env.TAURI_APP_PATH }}/package.json').version")
echo "version=$V" >> "$GITHUB_OUTPUT"
- name: Build and release (tauri-action)
uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Tauri signing — REQUIRED for the updater to accept downloaded
# bundles. See templates/clips/desktop/README.md for key setup.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.CLIPS_TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.CLIPS_TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# macOS codesign + notarize (reuses the Electron app's Apple creds).
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: W3PMF2T3MW
# Sentry error reporting for the Tauri native process and webview.
CLIPS_DESKTOP_SENTRY_DSN: ${{ secrets.CLIPS_DESKTOP_SENTRY_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }}
CLIPS_DESKTOP_SENTRY_CLIENT_KEY: ${{ secrets.CLIPS_DESKTOP_SENTRY_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }}
CLIPS_DESKTOP_SENTRY_PROJECT_ID: ${{ secrets.CLIPS_DESKTOP_SENTRY_PROJECT_ID || secrets.SENTRY_PROJECT_ID }}
CLIPS_DESKTOP_SENTRY_INGEST_HOST: ${{ secrets.CLIPS_DESKTOP_SENTRY_INGEST_HOST || secrets.SENTRY_INGEST_HOST }}
CLIPS_DESKTOP_SENTRY_ENVIRONMENT: production
with:
projectPath: ${{ env.TAURI_APP_PATH }}
tagName: clips-v__VERSION__
releaseName: "Clips Desktop v__VERSION__"
releaseBody: |
Auto-updating build of the Clips menu-bar app.
The in-app updater fetches its manifest from the `clips-latest`
release — this versioned release is the immutable source of the
signed bundles it downloads.
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
- name: Verify macOS permission metadata
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
APP_PATH=$(find "$TAURI_APP_PATH/src-tauri/target" -path '*/bundle/macos/Clips.app' -type d | head -n 1)
if [ -z "$APP_PATH" ]; then
echo "::error::Could not find built Clips.app bundle"
exit 1
fi
INFO="$APP_PATH/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Print :NSCameraUsageDescription" "$INFO" >/dev/null
/usr/libexec/PlistBuddy -c "Print :NSMicrophoneUsageDescription" "$INFO" >/dev/null
/usr/libexec/PlistBuddy -c "Print :NSScreenCaptureUsageDescription" "$INFO" >/dev/null
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO" | grep -qx 'com.clips.tray'
ENTITLEMENTS=$(codesign -d --entitlements :- "$APP_PATH" 2>/dev/null)
grep -q '<key>com.apple.security.device.camera</key>' <<< "$ENTITLEMENTS"
grep -q '<key>com.apple.security.device.audio-input</key>' <<< "$ENTITLEMENTS"
publish-release:
needs: build-tauri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Resolve version
id: v
run: echo "version=${{ needs.build-tauri.outputs.app-version }}" >> "$GITHUB_OUTPUT"
- name: Mark versioned release as published
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
retry() {
local attempts="$1"
shift
local delay=2
local n=1
until "$@"; do
if [ "$n" -ge "$attempts" ]; then
return 1
fi
echo "Attempt $n failed; retrying in ${delay}s..."
sleep "$delay"
n=$((n + 1))
delay=$((delay * 2))
done
}
retry 5 gh release edit "clips-v${{ steps.v.outputs.version }}" \
--draft=false \
--latest=false
- name: Download and publish updater manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
retry() {
local attempts="$1"
shift
local delay=2
local n=1
until "$@"; do
if [ "$n" -ge "$attempts" ]; then
return 1
fi
echo "Attempt $n failed; retrying in ${delay}s..."
sleep "$delay"
n=$((n + 1))
delay=$((delay * 2))
done
}
RELEASE_TAG="clips-v${{ steps.v.outputs.version }}"
mkdir -p manifest
# latest.json is only produced when TAURI_SIGNING_PRIVATE_KEY is set.
# Check explicitly so real API/auth errors aren't silently swallowed.
HAS_MANIFEST=$(gh release view "$RELEASE_TAG" --json assets \
-q '[.assets[].name] | any(. == "latest.json")') || {
echo "❌ Failed to query release assets for $RELEASE_TAG"
exit 1
}
if [ "$HAS_MANIFEST" = "true" ]; then
retry 5 gh release download "$RELEASE_TAG" --pattern 'latest.json' --dir manifest
mv manifest/latest.json manifest/clips-latest.json
MANIFEST_TITLE="Clips Desktop — latest (updater manifest)"
MANIFEST_NOTES="Stable URL for the Clips auto-updater. Overwritten on every release. Versioned bundles live in clips-v* releases."
if gh release view clips-latest --json tagName -q .tagName >/dev/null 2>&1; then
retry 5 gh release delete-asset clips-latest clips-latest.json --yes || true
retry 5 gh release upload clips-latest manifest/clips-latest.json
retry 5 gh release edit clips-latest \
--title "$MANIFEST_TITLE" \
--notes "$MANIFEST_NOTES"
else
retry 5 gh release create clips-latest manifest/clips-latest.json \
--title "$MANIFEST_TITLE" \
--notes "$MANIFEST_NOTES" \
--latest=false
fi
echo "✅ Updater manifest published to clips-latest release"
else
echo "⚠️ No latest.json in $RELEASE_TAG — TAURI_SIGNING_PRIVATE_KEY may not be configured."
echo "⚠️ Release published without auto-update support. Users can still download manually."
fi