Skip to content

feat: add OrcaRouter provider and OpenCode wrappers (#4521) #12129

feat: add OrcaRouter provider and OpenCode wrappers (#4521)

feat: add OrcaRouter provider and OpenCode wrappers (#4521) #12129

Workflow file for this run

name: CI
on:
pull_request:
branches: [main, release]
push:
branches: [main]
schedule:
- cron: '17 9 * * *'
workflow_dispatch:
workflow_call:
inputs:
full:
description: Run the complete server, client, DB, lint, build, and smoke suite
required: false
type: boolean
default: true
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
impact:
name: Plan test impact
runs-on: ubuntu-latest
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
outputs:
full: ${{ steps.plan.outputs.full }}
reason: ${{ steps.plan.outputs.reason }}
server_mode: ${{ steps.plan.outputs.server_mode }}
server_files: ${{ steps.plan.outputs.server_files }}
client_mode: ${{ steps.plan.outputs.client_mode }}
client_files: ${{ steps.plan.outputs.client_files }}
db: ${{ steps.plan.outputs.db }}
lint_mode: ${{ steps.plan.outputs.lint_mode }}
lint_files: ${{ steps.plan.outputs.lint_files }}
build: ${{ steps.plan.outputs.build }}
smoke: ${{ steps.plan.outputs.smoke }}
windows: ${{ steps.plan.outputs.windows }}
windows_mode: ${{ steps.plan.outputs.windows_mode }}
windows_files: ${{ steps.plan.outputs.windows_files }}
server_native: ${{ steps.plan.outputs.server_native }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Select affected test surfaces
id: plan
env:
CI_FORCE_FULL: ${{ github.event_name != 'pull_request' || inputs.full }}
CI_BASE_SHA: ${{ github.event.pull_request.base.sha }}
CI_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: node scripts/ci-test-plan.js
- name: Publish plan summary
env:
PLAN_REASON: ${{ steps.plan.outputs.reason }}
SERVER_MODE: ${{ steps.plan.outputs.server_mode }}
CLIENT_MODE: ${{ steps.plan.outputs.client_mode }}
DB_MODE: ${{ steps.plan.outputs.db }}
LINT_MODE: ${{ steps.plan.outputs.lint_mode }}
BUILD_MODE: ${{ steps.plan.outputs.build }}
SMOKE_MODE: ${{ steps.plan.outputs.smoke }}
WINDOWS_MODE: ${{ steps.plan.outputs.windows }}
WINDOWS_TEST_MODE: ${{ steps.plan.outputs.windows_mode }}
SERVER_NATIVE: ${{ steps.plan.outputs.server_native }}
run: |
{
echo "### CI impact plan"
echo
echo "- Reason: \`${PLAN_REASON}\`"
echo "- Server tests: \`${SERVER_MODE}\`"
echo "- Client tests: \`${CLIENT_MODE}\`"
echo "- DB tests: \`${DB_MODE}\`"
echo "- Client lint: \`${LINT_MODE}\`"
echo "- Client build: \`${BUILD_MODE}\`"
echo "- Server smoke: \`${SMOKE_MODE}\`"
echo "- Windows server tests: \`${WINDOWS_MODE}\` (\`${WINDOWS_TEST_MODE}\`)"
echo "- Server native rebuild: \`${SERVER_NATIVE}\`"
} >> "$GITHUB_STEP_SUMMARY"
server:
# Preserve the historical required-check context while branch protection
# transitions to the stable aggregate "CI Gate" check.
name: test (24.x)
needs: impact
if: needs.impact.outputs.server_mode != 'skip'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Use Node.js 24.x
uses: actions/setup-node@v7
with:
node-version: 24.x
cache: npm
cache-dependency-path: server/package-lock.json
- name: Install server dependencies
run: npm ci --prefix server
# After npm ci: it wipes node_modules, so a pre-install restore is lost.
# Vite/Vitest write transform artifacts under node_modules/.vite (and
# historically .vitest). Reusing them skips re-transforming unchanged files.
- name: Cache Vitest transform artifacts
uses: actions/cache@v4
with:
path: |
server/node_modules/.vite
server/node_modules/.vitest
key: vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json', 'server/vitest.config.js', 'scripts/vitestCiPool.js') }}
restore-keys: |
vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json') }}-
vitest-server-${{ runner.os }}-
# server/.npmrc pins ignore-scripts=true, so the native deps npm skipped
# need the explicit allowlist rebuild to be usable (node-pty et al).
# Always-run-only plans (prompt-upgrade + changelog fragment guards)
# import no native addons, so skip the rebuild there.
- name: Rebuild trusted native dependencies
if: needs.impact.outputs.server_native == 'true'
run: node scripts/trusted-rebuilds.js server
- name: Check server entry-point syntax
run: node --check server/index.js
- name: Run server tests
if: needs.impact.outputs.server_mode != 'skip'
env:
CI_TEST_MODE: ${{ needs.impact.outputs.server_mode }}
CI_TEST_FILES: ${{ needs.impact.outputs.server_files }}
CI_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: node scripts/run-ci-tests.js server
# Smoke-boot does not need Postgres: NODE_ENV=test selects the file
# backend. It does need the native rebuild (server boot loads node-pty),
# which is why it lives on this job rather than a third installer.
- name: Smoke-boot server
if: needs.impact.outputs.smoke == 'true'
run: npm run smoke
client:
name: Client tests and build
needs: impact
if: needs.impact.outputs.client_mode != 'skip' || needs.impact.outputs.build == 'true' || needs.impact.outputs.lint_mode != 'skip'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Use Node.js 24.x
uses: actions/setup-node@v7
with:
node-version: 24.x
cache: npm
cache-dependency-path: client/package-lock.json
- name: Install client dependencies
run: npm ci --prefix client
- name: Cache Vitest transform artifacts
uses: actions/cache@v4
with:
path: |
client/node_modules/.vite
client/node_modules/.vitest
key: vitest-client-${{ runner.os }}-${{ hashFiles('client/package-lock.json', 'client/vitest.config.js', 'scripts/vitestCiPool.js') }}
restore-keys: |
vitest-client-${{ runner.os }}-${{ hashFiles('client/package-lock.json') }}-
vitest-client-${{ runner.os }}-
- name: Lint client
if: needs.impact.outputs.lint_mode != 'skip'
env:
CI_LINT_MODE: ${{ needs.impact.outputs.lint_mode }}
CI_LINT_FILES: ${{ needs.impact.outputs.lint_files }}
run: node scripts/run-ci-lint.js
- name: Run client tests
if: needs.impact.outputs.client_mode != 'skip'
env:
CI_TEST_MODE: ${{ needs.impact.outputs.client_mode }}
CI_TEST_FILES: ${{ needs.impact.outputs.client_files }}
CI_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: node scripts/run-ci-tests.js client
- name: Build client
if: needs.impact.outputs.build == 'true'
run: npm run build --prefix client
database:
name: DB tests
needs: impact
if: needs.impact.outputs.db == 'true'
runs-on: ubuntu-latest
services:
postgres:
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: portos
POSTGRES_PASSWORD: portos
POSTGRES_DB: portos
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U portos"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
PGHOST: localhost
PGPORT: 5432
PGUSER: portos
PGPASSWORD: portos
steps:
- uses: actions/checkout@v7
- name: Use Node.js 24.x
uses: actions/setup-node@v7
with:
node-version: 24.x
cache: npm
cache-dependency-path: server/package-lock.json
- name: Install server dependencies
run: npm ci --prefix server
- name: Cache Vitest transform artifacts
uses: actions/cache@v4
with:
path: |
server/node_modules/.vite
server/node_modules/.vitest
key: vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json', 'server/vitest.config.js', 'server/vitest.config.db.js', 'scripts/vitestCiPool.js') }}
restore-keys: |
vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json') }}-
vitest-server-${{ runner.os }}-
# server/.npmrc pins ignore-scripts=true — DB suites that touch sharp /
# node-pty still need the explicit allowlist rebuild.
- name: Rebuild trusted native dependencies
run: node scripts/trusted-rebuilds.js server
- name: Provision test database (portos_test)
run: npm run setup:db:test --prefix server
- name: Run DB-backed tests (portos_test)
run: |
set +e
start=$(date +%s)
npm run test:db:ci --prefix server
status=$?
elapsed=$(( $(date +%s) - start ))
echo "⏱ server db suite: ${elapsed}s" | tee -a "$GITHUB_STEP_SUMMARY"
exit $status
env:
PGDATABASE: portos_test
lint:
# Historical required-check name. Lint itself now runs inside the client
# job so we do not pay a second `npm ci --prefix client`. This job only
# mirrors that result so branch protection can keep requiring "lint".
name: lint
needs: [impact, client]
if: always() && needs.impact.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Require client-job lint when it was selected
if: needs.impact.outputs.lint_mode != 'skip'
env:
CLIENT_RESULT: ${{ needs.client.result }}
run: |
if [ "$CLIENT_RESULT" != "success" ]; then
echo "Lint ran inside the client job, which did not succeed ($CLIENT_RESULT)."
exit 1
fi
- name: Lint not selected
if: needs.impact.outputs.lint_mode == 'skip'
run: echo "Lint skipped by impact plan"
windows-server:
name: Windows server unit tests
needs: impact
if: needs.impact.outputs.windows == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Use Node.js 24.x
uses: actions/setup-node@v7
with:
node-version: 24.x
cache: npm
cache-dependency-path: server/package-lock.json
- name: Install server dependencies
run: npm ci --prefix server
- name: Cache Vitest transform artifacts
uses: actions/cache@v4
with:
path: |
server/node_modules/.vite
server/node_modules/.vitest
key: vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json', 'server/vitest.config.js', 'scripts/vitestCiPool.js') }}
restore-keys: |
vitest-server-${{ runner.os }}-${{ hashFiles('server/package-lock.json') }}-
vitest-server-${{ runner.os }}-
- name: Rebuild trusted native dependencies
run: node scripts/trusted-rebuilds.js server
- name: Check server entry-point syntax
run: node --check server/index.js
- name: Run server tests on Windows
env:
CI_TEST_MODE: ${{ needs.impact.outputs.windows_mode }}
CI_TEST_FILES: ${{ needs.impact.outputs.windows_files }}
CI_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: node scripts/run-ci-tests.js server
gate:
name: CI Gate
if: always()
needs: [impact, server, client, database, lint, windows-server]
runs-on: ubuntu-latest
steps:
- name: Require every selected job to pass
env:
IMPACT_RESULT: ${{ needs.impact.result }}
SERVER_RESULT: ${{ needs.server.result }}
CLIENT_RESULT: ${{ needs.client.result }}
DATABASE_RESULT: ${{ needs.database.result }}
LINT_RESULT: ${{ needs.lint.result }}
WINDOWS_SERVER_RESULT: ${{ needs.windows-server.result }}
run: |
node -e '
const results = {
impact: process.env.IMPACT_RESULT,
server: process.env.SERVER_RESULT,
client: process.env.CLIENT_RESULT,
database: process.env.DATABASE_RESULT,
lint: process.env.LINT_RESULT,
windows_server: process.env.WINDOWS_SERVER_RESULT,
};
const failed = Object.entries(results)
.filter(([, result]) => !["success", "skipped"].includes(result));
if (failed.length) {
console.error("Selected CI jobs did not pass:", failed);
process.exit(1);
}
console.log("CI gate passed:", results);
'