Two unrelated tweaks grouped into one PR to keep churn low. ## Ports Backend 8000 → 3900, Vite dev 5173 → 3901, 3902 reserved for future IPC. Port 8000 conflicts with Django/Rails/Jupyter/Airflow on most dev machines; the uncommon 3900 range dodges that. Touched: - frontend/src-tauri/src/lib.rs (BACKEND_PORT) - frontend/src-tauri/tauri.conf.json (devUrl) - frontend/vite.config.js (server.port) - frontend/src/api/client.ts (hardcoded API base) - frontend/src/App.jsx (PREVIEW_API fallback) - backend/main.py (CORS allowlist + uvicorn.run default) Rust sidecar launcher and FastAPI uvicorn port stay in sync via the `BACKEND_PORT` constant + explicit port=3900. ## CI caches Build time shaves across ci.yml and release.yml: - `astral-sh/setup-uv@v3` → `enable-cache: true` keyed on uv.lock (~45 s saved per run after uv.lock stabilises) - `awalsh128/cache-apt-pkgs-action` for ffmpeg (~25 s saved) - `actions/cache@v4` on `~/.bun/install/cache` keyed on bun.lock (~15 s saved; applied to both test gate and build matrix) Expected warm test job: ~45-60 s (was ~2-3 min). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
220 lines
7.9 KiB
YAML
220 lines
7.9 KiB
YAML
# Desktop release pipeline — self-updating binaries for mac/linux/windows.
|
||
#
|
||
# Triggers:
|
||
# - push of a tag matching `v*` (e.g. `v0.2.0`) → full release, publishes
|
||
# artifacts + signed updater manifest (`latest.json`) to GH Releases.
|
||
# - workflow_dispatch → on-demand build, uploads artifacts as workflow
|
||
# artifacts only (no release, no updater manifest).
|
||
#
|
||
# Strategy: matrix builds per target. Each runner produces a PyInstaller
|
||
# frozen backend + Tauri bundle. `tauri-apps/tauri-action` signs the updater
|
||
# payloads with TAURI_SIGNING_PRIVATE_KEY and uploads to the GH Release for
|
||
# the tag. The built-in updater plugin polls the release's `latest.json` on
|
||
# client boot.
|
||
#
|
||
# Windows/Linux support: first-pass enabled. Expect the first few runs on
|
||
# each to surface PyInstaller/Tauri issues that never showed up locally on
|
||
# macOS — iterate on CI.
|
||
|
||
name: Desktop Release
|
||
|
||
on:
|
||
push:
|
||
tags: ['v*']
|
||
workflow_dispatch:
|
||
inputs:
|
||
draft:
|
||
description: "Create as draft release (tag push only)"
|
||
required: false
|
||
default: "true"
|
||
|
||
permissions:
|
||
contents: write # needed to attach artifacts + updater manifest to GH Release
|
||
|
||
env:
|
||
# Run all JavaScript actions on Node 24 (GH deprecates Node 20 in Sep 2026).
|
||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||
|
||
jobs:
|
||
# Fast gating job — runs backend pytest + frontend node:test + tsc on a
|
||
# single Linux runner. The matrix build below waits on this via `needs:`
|
||
# so we don't burn 4× platform-matrix minutes on a broken commit.
|
||
test:
|
||
name: Tests (backend + frontend)
|
||
runs-on: ubuntu-22.04
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup Python 3.11
|
||
uses: actions/setup-python@v5
|
||
with:
|
||
python-version: "3.11"
|
||
|
||
# enable-cache persists ~/.cache/uv keyed on uv.lock.
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v3
|
||
with:
|
||
enable-cache: true
|
||
cache-dependency-glob: "uv.lock"
|
||
|
||
# Node 22 is needed for --experimental-strip-types so node:test can
|
||
# import .ts files directly from frontend/src/api/*.
|
||
- name: Setup Node 22
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '22'
|
||
|
||
- name: Setup Bun
|
||
uses: oven-sh/setup-bun@v1
|
||
|
||
# Backend tests need ffmpeg (subprocess calls in fixtures). Cache the
|
||
# resolved .debs so warm runs skip the apt-get update + install.
|
||
- name: System deps (ffmpeg)
|
||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||
with:
|
||
packages: ffmpeg
|
||
version: 1.0
|
||
|
||
- name: Install Python deps
|
||
run: uv sync
|
||
|
||
- name: Run pytest
|
||
run: uv run pytest tests/ -q --tb=short
|
||
|
||
- name: Cache bun deps
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ~/.bun/install/cache
|
||
key: ${{ runner.os }}-bun-${{ hashFiles('frontend/bun.lock', 'bun.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-bun-
|
||
|
||
- name: Install frontend deps
|
||
working-directory: frontend
|
||
run: bun install
|
||
|
||
- name: Frontend typecheck
|
||
working-directory: frontend
|
||
run: bunx tsc --noEmit
|
||
|
||
# Invoke node directly (not `bun run test`) because `bun run` auto-aliases
|
||
# `node` to `bun` in script bodies, and bun doesn't support
|
||
# --experimental-strip-types.
|
||
- name: Run frontend node:test
|
||
working-directory: frontend
|
||
run: node --experimental-strip-types --no-warnings --test ../tests/frontend/*.test.mjs
|
||
|
||
build:
|
||
needs: test
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- os: macos-14
|
||
arch: aarch64-apple-darwin
|
||
label: "macOS Apple Silicon"
|
||
rust_target: aarch64-apple-darwin
|
||
bundles: "app,dmg,updater"
|
||
|
||
- os: macos-13
|
||
arch: x86_64-apple-darwin
|
||
label: "macOS Intel"
|
||
rust_target: x86_64-apple-darwin
|
||
bundles: "app,dmg,updater"
|
||
|
||
# Windows: force MSI bundling via --bundles. NSIS fails at makensis
|
||
# because our PyInstaller payload approaches its ~2 GB stub limit.
|
||
- os: windows-2022
|
||
arch: x86_64-pc-windows-msvc
|
||
label: "Windows x64"
|
||
rust_target: x86_64-pc-windows-msvc
|
||
bundles: "msi,updater"
|
||
|
||
# Linux: ship .deb only. AppImage bundling (linuxdeploy) is
|
||
# unreliable on GH Actions runners even with APPIMAGE_EXTRACT_AND_RUN.
|
||
- os: ubuntu-22.04
|
||
arch: x86_64-unknown-linux-gnu
|
||
label: "Linux x64"
|
||
rust_target: x86_64-unknown-linux-gnu
|
||
bundles: "deb,updater"
|
||
|
||
runs-on: ${{ matrix.os }}
|
||
name: ${{ matrix.label }}
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
# ── Language runtimes ──────────────────────────────────────────────
|
||
- name: Setup Rust (stable)
|
||
uses: dtolnay/rust-toolchain@stable
|
||
with:
|
||
targets: ${{ matrix.rust_target }}
|
||
|
||
# Cache ~/.cargo/registry + {target}/ per rust_target. Cargo dep
|
||
# compile is the long pole of the build — cold is ~5-7 min, warm
|
||
# drops to ~1-2 min.
|
||
- name: Rust cache
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: frontend/src-tauri -> target
|
||
key: ${{ matrix.rust_target }}
|
||
|
||
- name: Setup Bun
|
||
uses: oven-sh/setup-bun@v1
|
||
|
||
# ── Platform deps (Tauri host requirements only — no Python here) ─
|
||
# The runtime Python/uv bootstrap happens on the user's machine at
|
||
# first launch, not in CI. CI only packages the source (pyproject.toml,
|
||
# uv.lock, backend/*.py) into the Tauri installer as resources.
|
||
- name: macOS system deps
|
||
if: runner.os == 'macOS'
|
||
run: |
|
||
brew install ffmpeg || true
|
||
|
||
- name: Linux system deps
|
||
if: runner.os == 'Linux'
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y \
|
||
libwebkit2gtk-4.1-dev \
|
||
build-essential curl wget file libxdo-dev libssl-dev \
|
||
libayatana-appindicator3-dev librsvg2-dev \
|
||
libasound2-dev ffmpeg
|
||
|
||
# ── Frontend build ─────────────────────────────────────────────────
|
||
- name: Cache bun deps
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ~/.bun/install/cache
|
||
key: ${{ runner.os }}-bun-${{ hashFiles('frontend/bun.lock', 'bun.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-bun-
|
||
|
||
- name: Install frontend deps
|
||
working-directory: frontend
|
||
run: bun install
|
||
|
||
# ── Tauri build + sign + publish ───────────────────────────────────
|
||
# tauri-action handles: bundle, sign updater payload with the
|
||
# TAURI_SIGNING_PRIVATE_KEY secret, attach to release, update
|
||
# latest.json with per-platform download URLs & signatures. The
|
||
# installer ships the repo's pyproject.toml + uv.lock + backend/
|
||
# tree as Tauri resources; lib.rs::ensure_venv_ready recreates the
|
||
# venv on first launch via `uv sync --frozen --no-dev`.
|
||
- name: Build + release (Tauri)
|
||
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 }}
|
||
with:
|
||
projectPath: frontend
|
||
args: --target ${{ matrix.rust_target }} --bundles ${{ matrix.bundles }}
|
||
tagName: ${{ github.ref_name }}
|
||
releaseName: "OmniVoice Studio ${{ github.ref_name }}"
|
||
releaseBody: "Auto-generated release. See commit log for changes."
|
||
releaseDraft: ${{ inputs.draft || 'true' }}
|
||
prerelease: false
|
||
updaterJsonPreferNsis: false
|
||
includeUpdaterJson: true
|