ci: port the release workflow, add app CI

Release workflow adapted to the root layout (no monorepo paths, no .pth venv wiring).
New CI runs the coworker pytest suite and GUI unit tests; e2e joins once the inherited failures are fixed.
This commit is contained in:
Rohit C Prasad
2026-07-21 12:19:56 -07:00
parent 52afd9c694
commit d7af8af9c6
2 changed files with 233 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# App CI — the coworker Python suite + the GUI unit tests.
#
# The Playwright e2e suite (surfaces/gui, `npm run e2e`) is NOT wired in yet: it has
# known pre-existing failures (automations/standing-approvals specs) inherited from the
# source repo. Add it here once those are fixed so CI starts green and stays green.
name: CI
on: [push, pull_request]
jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[messaging,dev]"
- name: Test
run: pytest tests -q
gui-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: surfaces/gui/package-lock.json
- name: npm ci
working-directory: surfaces/gui
run: npm ci
- name: Unit tests
working-directory: surfaces/gui
run: npm test
+193
View File
@@ -0,0 +1,193 @@
# Desktop release builds — macOS (.dmg, arm64 + Intel) and Windows (.msi + NSIS .exe).
#
# CI calls the SAME scripts developers run locally (packaging/build_dmg.sh and
# build_windows.ps1); this file only provisions the toolchain (Node, Rust, a Python venv at
# .venv with PyInstaller) and publishes the results.
#
# Triggers:
# - tag push `v*` → builds all targets and attaches them to a DRAFT GitHub Release
# (review, then publish by hand).
# - manual run → builds and uploads workflow artifacts only (no release).
#
# Each installer is uploaded twice: once with its versioned name (archive) and once with a
# stable name (OpenWorker-macos-arm64.dmg, …) so the website can link to
# github.com/<repo>/releases/latest/download/<stable-name>
# and never need updating.
#
# macOS signing + notarization: Tauri's bundler handles both during `tauri build` when the
# APPLE_* env vars are present (import cert → sign app + sidecar with hardened runtime →
# notarize via notarytool → staple). Driven by repo secrets:
# APPLE_CERTIFICATE base64 .p12 (Developer ID Application cert + key)
# APPLE_CERTIFICATE_PASSWORD the .p12 export password
# APPLE_SIGNING_IDENTITY e.g. "Developer ID Application: Name (TEAMID)"
# APPLE_API_KEY_CONTENT base64 App Store Connect API .p8 (notarytool)
# APPLE_API_KEY the API key id
# APPLE_API_ISSUER the API issuer id
# When the secrets are absent (forks, scratch runs) the build degrades to unsigned —
# installable via `xattr -cr`. Windows remains unsigned (Authenticode is a later step).
name: Release
on:
push:
tags: ["v*", "app-v*"]
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# No Intel macOS target: macos-13 (the last Intel runner image) is deprecated and
# its queue waits run to hours, which blocks the release job. Intel Macs are
# 2020-and-earlier hardware — revisit only if beta users actually ask.
- os: macos-latest # Apple Silicon
slug: macos-arm64
- os: windows-latest
slug: windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: surfaces/gui/package-lock.json
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: surfaces/gui/src-tauri
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Set up the sidecar venv (.venv)
# The build scripts expect a venv at .venv with the package + PyInstaller.
# typer/tzdata are build-time-only (PyInstaller walks mcp.cli, which needs typer;
# tzdata ships zoneinfo for Windows). aisuite installs like any other dependency
# (git-pinned in pyproject.toml).
run: |
python -m venv .venv
if [ "$RUNNER_OS" = "Windows" ]; then VPY=.venv/Scripts/python; else VPY=.venv/bin/python; fi
"$VPY" -m pip install --upgrade pip
"$VPY" -m pip install -e . pyinstaller typer tzdata
"$VPY" -c "import aisuite, coworker" # fail fast if either import breaks
- name: npm ci
working-directory: surfaces/gui
run: npm ci
- name: Build .dmg (macOS)
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
# Auto-update artifact signing (minisign, separate from Apple signing). Absent →
# the build script skips updater artifacts with a warning (fork/scratch runs).
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
# Unset empty APPLE_* vars so runs without secrets stay cleanly unsigned
# (Tauri treats a present-but-empty var as a config error).
for v in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY APPLE_API_KEY APPLE_API_ISSUER; do
[ -n "$(eval echo "\${$v:-}")" ] || unset "$v"
done
if [ -n "${APPLE_API_KEY_CONTENT:-}" ]; then
echo "$APPLE_API_KEY_CONTENT" | base64 -d > "$RUNNER_TEMP/AuthKey.p8"
export APPLE_API_KEY_PATH="$RUNNER_TEMP/AuthKey.p8"
fi
unset APPLE_API_KEY_CONTENT
bash packaging/build_dmg.sh
- name: Build .msi + NSIS .exe (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: ./packaging/build_windows.ps1
- name: Stage artifacts (versioned + stable names)
run: |
mkdir -p out
BUNDLE=surfaces/gui/src-tauri/target/release/bundle
if [ "$RUNNER_OS" = "Windows" ]; then
cp "$BUNDLE"/nsis/*.exe out/
cp "$BUNDLE"/nsis/*.exe out/OpenWorker-windows-setup.exe
cp "$BUNDLE"/msi/*.msi out/
cp "$BUNDLE"/msi/*.msi out/OpenWorker-windows.msi
# Updater signature for the NSIS installer (present only when the updater key
# secret is configured). The .sig signs CONTENT, so the stable rename is safe.
SIG=$(ls "$BUNDLE"/nsis/*.exe.sig 2>/dev/null | head -1 || true)
[ -n "$SIG" ] && cp "$SIG" out/OpenWorker-windows-setup.exe.sig
else
cp "$BUNDLE"/dmg/*.dmg out/
cp "$BUNDLE"/dmg/*.dmg out/OpenWorker-${{ matrix.slug }}.dmg
# macOS updater artifact: the signed .app tarball the installed app swaps in.
if [ -f "$BUNDLE"/macos/OpenWorker.app.tar.gz ]; then
cp "$BUNDLE"/macos/OpenWorker.app.tar.gz out/OpenWorker-${{ matrix.slug }}.app.tar.gz
cp "$BUNDLE"/macos/OpenWorker.app.tar.gz.sig out/OpenWorker-${{ matrix.slug }}.app.tar.gz.sig
fi
fi
ls -la out
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.slug }}
path: out/*
if-no-files-found: error
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Compose the auto-update manifest (latest.json)
# Shipped apps poll releases/latest/download/latest.json (via the branded
# download.openworker.com redirect) — publishing this release IS pushing the
# update. The tag must match tauri.conf.json's version or installed apps would
# see a permanent phantom update; fail loudly on drift. Runs only when signed
# updater artifacts exist (i.e. the TAURI_SIGNING_PRIVATE_KEY secret is set).
run: |
TAG="${GITHUB_REF_NAME}"
CONF_VERSION=$(python3 -c "import json; print(json.load(open('surfaces/gui/src-tauri/tauri.conf.json'))['version'])")
if [ "${TAG#v}" != "$CONF_VERSION" ]; then
echo "::error::tag $TAG != tauri.conf.json version $CONF_VERSION — bump the config before tagging"
exit 1
fi
if ls dist/*.sig >/dev/null 2>&1; then
python3 packaging/make_update_manifest.py \
--version "${TAG#v}" --tag "$TAG" --repo "$GITHUB_REPOSITORY" \
--dist dist --out dist/latest.json \
--notes "OpenWorker ${TAG#v}"
else
echo "::warning::no updater signatures in dist/ — release ships WITHOUT auto-update manifest"
fi
- uses: softprops/action-gh-release@v2
with:
draft: true
files: dist/*
generate_release_notes: true