chore: unique ports (3900/3901) + broader CI caches (#20)

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>
This commit is contained in:
Palash Debnath
2026-04-23 06:52:30 +05:30
committed by GitHub
co-authored by Claude Opus 4.7
parent e57448f704
commit 90ef02b2e4
8 changed files with 63 additions and 19 deletions
+20 -3
View File
@@ -30,8 +30,13 @@ jobs:
with:
python-version: "3.11"
# enable-cache persists ~/.cache/uv across runs, keyed on uv.lock —
# turns `uv sync` from ~45 s cold to ~5 s warm.
- 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/*.
@@ -43,10 +48,12 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
# apt install ffmpeg is ~30 s every run; cache the resolved .debs.
- name: System deps (ffmpeg)
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: ffmpeg
version: 1.0
- name: Install Python deps
run: uv sync
@@ -54,6 +61,16 @@ jobs:
- name: Run pytest
run: uv run pytest tests/ -q --tb=short
# Cache ~/.bun/install/cache keyed on bun.lock — `bun install` drops
# from ~15 s cold to near-instant on warm cache.
- 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
+26 -6
View File
@@ -50,8 +50,12 @@ jobs:
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/*.
@@ -63,13 +67,13 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
# Backend tests need ffmpeg (subprocess calls in fixtures) + the minimal
# apt deps pydub/imageio pull in. Model weights are mocked so no HF
# downloads happen.
# 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)
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: ffmpeg
version: 1.0
- name: Install Python deps
run: uv sync
@@ -77,6 +81,14 @@ jobs:
- 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
@@ -170,6 +182,14 @@ jobs:
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
+5 -3
View File
@@ -157,7 +157,7 @@ async def global_exception_handler(request: Request, exc: Exception):
_allowed = os.environ.get(
"OMNIVOICE_ALLOWED_ORIGINS",
"http://localhost:5173,http://127.0.0.1:5173,tauri://localhost,http://tauri.localhost",
"http://localhost:3901,http://127.0.0.1:3901,tauri://localhost,http://tauri.localhost",
).split(",")
app.add_middleware(
@@ -191,8 +191,10 @@ if os.path.exists(frontend_path):
else:
@app.get("/")
def _dev_fallback():
return RedirectResponse(url="http://localhost:5173")
return RedirectResponse(url="http://localhost:3901")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
# Port 3900 picked to dodge common 8000 conflicts (Django/Rails/Jupyter).
# Rust sidecar launcher in lib.rs::BACKEND_PORT must stay in sync.
uvicorn.run(app, host="0.0.0.0", port=3900)
+5 -1
View File
@@ -7,7 +7,11 @@ use std::sync::Mutex;
use std::time::Duration;
use tauri::Manager;
const BACKEND_PORT: u16 = 8000;
// Unique port range (3900-3902) chosen to avoid common conflicts:
// 8000 collides with Django/Rails/Jupyter/Airflow on most dev machines.
// 3900 is the backend (FastAPI + uvicorn), 3901 is the Vite dev server,
// 3902 is reserved for future IPC / websocket listeners.
const BACKEND_PORT: u16 = 3900;
// Version of the Astral `uv` binary we download at first run when no system
// uv is on PATH. Pinned for reproducibility — bump alongside the uv.lock
+1 -1
View File
@@ -5,7 +5,7 @@
"identifier": "com.debpalash.omnivoice-studio",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
"devUrl": "http://localhost:3901",
"beforeDevCommand": "bun run dev",
"beforeBuildCommand": "bun run build"
},
+1 -1
View File
@@ -68,7 +68,7 @@ const doubleClickMaximize = () => {
* We upload to the backend's /preview endpoint and serve via HTTP instead.
* Falls back to createObjectURL for regular browsers.
*/
const _PREVIEW_API = import.meta.env.VITE_OMNIVOICE_API || 'http://localhost:8000';
const _PREVIEW_API = import.meta.env.VITE_OMNIVOICE_API || 'http://localhost:3900';
const fileToMediaUrl = async (file, prevUrls) => {
// Revoke previous blob URLs if they exist
if (prevUrls?.videoUrl?.startsWith('blob:')) URL.revokeObjectURL(prevUrls.videoUrl);
+4 -3
View File
@@ -1,8 +1,9 @@
// Backend always listens on localhost:8000 — both in dev (Vite @ 5173 talking
// Backend always listens on localhost:3900 — both in dev (Vite @ 3901 talking
// to a separate uvicorn) and in the built .app (Tauri webview @ tauri://localhost
// talking to the bundled frozen backend sidecar). Relative fetches against
// talking to the venv-bootstrapped sidecar). Relative fetches against
// tauri://localhost don't reach the sidecar, so we hardcode the absolute host.
export const API = 'http://localhost:8000';
// Port 3900 chosen to avoid common 8000 conflicts (Django/Rails/Jupyter).
export const API = 'http://localhost:3900';
export class ApiError extends Error {
status?: number;
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
},
},
server: {
port: 5173,
port: 3901,
strictPort: true,
host: false,
watch: {