mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
Chromium checks its Linux sandbox before main.js, while package installation probes run as root and miss AppArmor restrictions on desktop users. Choose the launch flags before exec and always install the SUID alternative for native packages. Test published artifacts as an ordinary user with namespaces both denied and allowed; never repair permissions in the smoke harness.
489 lines
16 KiB
YAML
489 lines
16 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
merge_group:
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
env:
|
|
# CI does not use the CUDA execution provider, and the onnxruntime-node
|
|
# postinstall download from NuGet is large enough to make npm ci flaky.
|
|
ONNXRUNTIME_NODE_INSTALL: skip
|
|
|
|
jobs:
|
|
changes:
|
|
name: changes
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
full: ${{ github.event_name != 'pull_request' || steps.filter.outputs.routing != 'false' || steps.filter.outputs.workspace != 'false' || steps.filter.outputs.ci != 'false' }}
|
|
format: ${{ steps.filter.outputs.format }}
|
|
quality: ${{ steps.filter.outputs.quality }}
|
|
hub: ${{ steps.filter.outputs.hub }}
|
|
server: ${{ steps.filter.outputs.server }}
|
|
desktop: ${{ steps.filter.outputs.desktop }}
|
|
app: ${{ steps.filter.outputs.app }}
|
|
sdk: ${{ steps.filter.outputs.sdk }}
|
|
browser: ${{ steps.filter.outputs.browser }}
|
|
relay: ${{ steps.filter.outputs.relay }}
|
|
cli: ${{ steps.filter.outputs.cli }}
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Detect affected CI contracts
|
|
id: filter
|
|
uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3
|
|
with:
|
|
filters: .github/ci-paths.yml
|
|
|
|
- name: Validate CI contracts
|
|
run: node --test scripts/ci-workflow.test.mjs scripts/daemon-launch-contract.test.mjs scripts/sync-fdroid-changelogs.test.mjs
|
|
|
|
format:
|
|
name: format
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.format != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Check formatting
|
|
run: npm run format:check
|
|
|
|
lint:
|
|
name: lint
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.quality != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Lint lockfile
|
|
run: npx --yes lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https --validate-integrity
|
|
|
|
- name: Verify dependency signatures
|
|
run: npm audit signatures
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
|
|
typecheck:
|
|
name: typecheck
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.quality != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Build server stack
|
|
run: npm run build:server
|
|
|
|
- name: Typecheck all packages
|
|
run: npm run typecheck
|
|
|
|
- name: Verify public package contents
|
|
run: |
|
|
npm pack --dry-run --ignore-scripts --workspace=@getpaseo/protocol
|
|
npm pack --dry-run --ignore-scripts --workspace=@getpaseo/client
|
|
npm pack --dry-run --ignore-scripts --workspace=@getpaseo/server
|
|
|
|
server-tests-ubuntu:
|
|
name: server-tests (ubuntu-latest)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.server != 'false' || needs.changes.outputs.hub != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps: &server_test_steps
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Fetch origin/main (worktree tests)
|
|
if: ${{ needs.changes.outputs.full != 'false' || needs.changes.outputs.server != 'false' }}
|
|
run: git fetch --no-tags origin main:refs/remotes/origin/main
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Install agent CLIs for provider tests
|
|
if: ${{ needs.changes.outputs.full != 'false' || needs.changes.outputs.server != 'false' }}
|
|
run: node scripts/npm-retry.mjs install -g @anthropic-ai/claude-code opencode-ai
|
|
|
|
- name: Build server dependencies
|
|
run: npm run build:server-deps
|
|
|
|
- name: Run server tests
|
|
if: ${{ needs.changes.outputs.full != 'false' || needs.changes.outputs.server != 'false' }}
|
|
run: npm run test --workspace=@getpaseo/server
|
|
env:
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
|
|
- name: Run Hub CLI/daemon contract
|
|
if: ${{ needs.changes.outputs.full == 'false' && needs.changes.outputs.server == 'false' && needs.changes.outputs.hub != 'false' }}
|
|
run: npm run test:hub-cli-contract --workspace=@getpaseo/server
|
|
|
|
server-tests-windows:
|
|
name: server-tests (windows-latest)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.server != 'false' || needs.changes.outputs.hub != 'false') }}
|
|
runs-on: windows-latest
|
|
steps: *server_test_steps
|
|
|
|
desktop-tests-ubuntu:
|
|
name: desktop-tests (ubuntu-latest)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.desktop != 'false') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
steps: &desktop_test_steps
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies with retry
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Build server stack
|
|
run: npm run build:server
|
|
|
|
- name: Run desktop tests
|
|
run: npm run test --workspace=@getpaseo/desktop
|
|
|
|
- name: Install virtual display
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: sudo apt-get update && sudo apt-get install -y xvfb xauth rpm
|
|
|
|
- name: Install Playwright browser
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: npx playwright install chromium
|
|
|
|
- name: Run desktop renderer E2E
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: npm run test:e2e:renderer --workspace=@getpaseo/desktop
|
|
|
|
- name: Run desktop browser E2E
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: npm run test:e2e:browser-tabs --workspace=@getpaseo/desktop
|
|
env:
|
|
PASEO_DESKTOP_BROWSER_E2E_ARTIFACT_DIR: ${{ runner.temp }}/desktop-browser-e2e
|
|
|
|
- name: Upload desktop browser diagnostics
|
|
uses: actions/upload-artifact@v4
|
|
if: ${{ failure() && runner.os == 'Linux' }}
|
|
with:
|
|
name: desktop-browser-e2e
|
|
path: ${{ runner.temp }}/desktop-browser-e2e
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|
|
|
|
- name: Build Linux desktop artifacts
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: npm run build:desktop -- --publish never --linux --x64
|
|
env:
|
|
EP_GH_IGNORE_TIME: true
|
|
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke
|
|
|
|
- name: Smoke installed artifacts with restricted user namespaces
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: |
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
|
|
if unshare --user --map-root-user --net true; then
|
|
echo "Expected the launching user's namespace probe to be denied"
|
|
exit 1
|
|
fi
|
|
sudo dpkg -i packages/desktop/release/*.deb
|
|
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release disabled
|
|
env:
|
|
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/restricted
|
|
|
|
- name: Smoke sandbox-preserving portable launches
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: |
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
unshare --user --map-root-user --net true
|
|
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release enabled
|
|
env:
|
|
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/userns
|
|
|
|
- name: Upload packaged smoke diagnostics
|
|
uses: actions/upload-artifact@v4
|
|
if: ${{ always() && runner.os == 'Linux' }}
|
|
with:
|
|
name: desktop-packaged-smoke-linux-x64
|
|
path: ${{ runner.temp }}/desktop-smoke
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|
|
|
|
desktop-tests-windows:
|
|
name: desktop-tests (windows-latest)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.desktop != 'false') }}
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
steps: *desktop_test_steps
|
|
|
|
app-tests:
|
|
name: app-tests
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.app != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies with retry
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Install Playwright browsers
|
|
timeout-minutes: 10
|
|
run: npx playwright install chromium
|
|
|
|
- name: Build app dependencies
|
|
run: npm run build:app-deps
|
|
|
|
- name: Run app unit tests
|
|
run: npm run test --workspace=@getpaseo/app
|
|
|
|
sdk-tests:
|
|
name: sdk-tests
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.sdk != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Build client dependencies
|
|
run: npm run build:client
|
|
|
|
- name: Run protocol tests
|
|
run: npm run test --workspace=@getpaseo/protocol
|
|
|
|
- name: Run client tests
|
|
run: npm run test --workspace=@getpaseo/client
|
|
|
|
- name: Run highlight tests
|
|
run: npm run test --workspace=@getpaseo/highlight
|
|
|
|
- name: Typecheck client examples
|
|
run: npm run typecheck:examples --workspace=@getpaseo/client
|
|
|
|
playwright-1:
|
|
name: playwright (shard 1/4)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PLAYWRIGHT_SHARD: "1/4"
|
|
PLAYWRIGHT_ARTIFACT: "1"
|
|
steps: &playwright_test_steps
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies with retry
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Install Playwright browsers
|
|
timeout-minutes: 10
|
|
run: npx playwright install chromium
|
|
|
|
- name: Build server stack
|
|
run: npm run build:server
|
|
|
|
- name: Build native app dependency
|
|
run: npm run build --workspace=@getpaseo/expo-two-way-audio
|
|
|
|
- name: Install agent CLIs for provider tests
|
|
run: node scripts/npm-retry.mjs install -g @anthropic-ai/claude-code @openai/codex@0.105.0 opencode-ai
|
|
|
|
- name: Run Playwright E2E tests
|
|
run: npm run test:e2e --workspace=@getpaseo/app -- --shard=${{ env.PLAYWRIGHT_SHARD }}
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
|
|
- name: Upload test artifacts
|
|
uses: actions/upload-artifact@v4
|
|
if: failure()
|
|
with:
|
|
name: playwright-results-${{ env.PLAYWRIGHT_ARTIFACT }}
|
|
path: |
|
|
packages/app/test-results/
|
|
packages/app/playwright-report/
|
|
retention-days: 7
|
|
|
|
playwright-2:
|
|
name: playwright (shard 2/4)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PLAYWRIGHT_SHARD: "2/4"
|
|
PLAYWRIGHT_ARTIFACT: "2"
|
|
steps: *playwright_test_steps
|
|
|
|
playwright-3:
|
|
name: playwright (shard 3/4)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PLAYWRIGHT_SHARD: "3/4"
|
|
PLAYWRIGHT_ARTIFACT: "3"
|
|
steps: *playwright_test_steps
|
|
|
|
playwright-4:
|
|
name: playwright (shard 4/4)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PLAYWRIGHT_SHARD: "4/4"
|
|
PLAYWRIGHT_ARTIFACT: "4"
|
|
steps: *playwright_test_steps
|
|
|
|
relay-tests:
|
|
name: relay-tests
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.relay != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Build relay
|
|
run: npm run build:relay
|
|
|
|
- name: Run relay tests
|
|
run: npm run test --workspace=@getpaseo/relay
|
|
|
|
cli-tests-1:
|
|
name: cli-tests (shard 1/3)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.cli != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0"
|
|
PASEO_DICTATION_ENABLED: "0"
|
|
PASEO_VOICE_MODE_ENABLED: "0"
|
|
PASEO_CLI_TEST_SHARD: "1"
|
|
PASEO_CLI_TEST_SHARD_TOTAL: "3"
|
|
steps: &cli_test_steps
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: node scripts/npm-retry.mjs ci
|
|
|
|
- name: Build server stack
|
|
run: npm run build:server
|
|
|
|
- name: Install agent CLIs for provider tests
|
|
run: node scripts/npm-retry.mjs install -g @anthropic-ai/claude-code @openai/codex@0.105.0 opencode-ai
|
|
|
|
- name: Run CLI tests
|
|
run: npm run test --workspace=@getpaseo/cli
|
|
|
|
cli-tests-2:
|
|
name: cli-tests (shard 2/3)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.cli != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0"
|
|
PASEO_DICTATION_ENABLED: "0"
|
|
PASEO_VOICE_MODE_ENABLED: "0"
|
|
PASEO_CLI_TEST_SHARD: "2"
|
|
PASEO_CLI_TEST_SHARD_TOTAL: "3"
|
|
steps: *cli_test_steps
|
|
|
|
cli-tests-3:
|
|
name: cli-tests (shard 3/3)
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.cli != 'false') }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0"
|
|
PASEO_DICTATION_ENABLED: "0"
|
|
PASEO_VOICE_MODE_ENABLED: "0"
|
|
PASEO_CLI_TEST_SHARD: "3"
|
|
PASEO_CLI_TEST_SHARD_TOTAL: "3"
|
|
steps: *cli_test_steps
|