aps-agent/.github/workflows/ci.yml

226 lines
9.0 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# APS 构建矩阵 CI(矩阵 118 · CI 构建矩阵门禁)
# push / PR 触发:lint(ruff,基线有存量告警,informational)
# × Windows + Ubuntu 后端黄金测试(隔离 APS_KNOWLEDGE_PATH)
# × Windows + Ubuntu 前端 type+build
# × E2E(preview 生产构建 + Chromium,JMS 登录依赖外网)
# × SBOM(npm run sbom,CycloneDX 1.6)
# × node-tests(Ubuntu + Windows):updater/sidecar/packaging-smoke(node --test,npm run test:node)
# Windows 额外:sidecar 本地冒烟 + offline install verify(产物存在时)+ Defender best-effort
# 签名等外部项不在本工作流(需签名证书,见 docs/development/e2e.md);Defender 无 MpCmdRun 时跳过(退出码 2)
# ============================================================
name: CI
on:
push:
pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
PYTHONUTF8: '1'
NODE_VERSION: '22'
PYTHON_VERSION: '3.13'
jobs:
lint:
name: lint (ruff)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: install ruff
run: python -m pip install --upgrade pip ruff
# 基线:server/tests/scripts 存量 55 项 ruff 告警(round-41 记录)。
# 本步 informational(continue-on-error),不阻断构建;清零后改回硬门禁。
- name: ruff check(informational,不阻断)
continue-on-error: true
run: ruff check --output-format=github server/ tests/ scripts/ || true
frontend-build:
name: frontend build (tsc + vite) · ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: npm ci
working-directory: apps/web
run: npm ci
- name: build(tsc -b && vite build)
working-directory: apps/web
run: npm run build
backend-tests:
name: backend golden tests · ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
# 隔离运行数据:绝不触碰仓库 server/data 或开发者 ~/.aps
APS_HOME: ${{ runner.temp }}/aps-ci/home
APS_DB_PATH: ${{ runner.temp }}/aps-ci/master.db
APS_KNOWLEDGE_PATH: ${{ runner.temp }}/aps-ci/knowledge
APS_APPROVAL_PATH: ${{ runner.temp }}/aps-ci/approval
APS_WORLD_PATH: ${{ runner.temp }}/aps-ci/world.json
APS_AUTH_PROVIDER: unconfigured
APS_LICENSE_PROVIDER: unconfigured
LLM_PROVIDER: ''
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: install deps
run: python -m pip install -r requirements.txt pytest pytest-asyncio
- name: golden tests(隔离 APS_KNOWLEDGE_PATH)
run: python -m pytest tests/golden -q -p no:cacheprovider
e2e:
name: frontend E2E smoke (preview + Chromium)
runs-on: ubuntu-latest
env:
E2E_MODE: preview
E2E_FRONTEND_PORT: '43117'
E2E_BASE_URL: http://localhost:43117
E2E_API_TARGET: http://127.0.0.1:8100
E2E_OUTPUT_DIR: ${{ runner.temp }}/aps-e2e-artifacts
APS_HOME: ${{ runner.temp }}/aps-e2e/home
APS_DB_PATH: ${{ runner.temp }}/aps-e2e/master.db
APS_KNOWLEDGE_PATH: ${{ runner.temp }}/aps-e2e/knowledge
APS_APPROVAL_PATH: ${{ runner.temp }}/aps-e2e/approval
APS_WORLD_PATH: ${{ runner.temp }}/aps-e2e/world.json
APS_AUTH_PROVIDER: jms
APS_AUTH_COOKIE_SECURE: '0'
APS_AUTH_TTL_SECONDS: '28800'
APS_APPROVAL_BACKEND: file
APS_CORS_ORIGINS: http://localhost:43117,http://127.0.0.1:43117
JMS_AUTH_BASE_URL: ${{ vars.APS_E2E_JMS_BASE_URL }}
JMS_AUTH_SESSION_SECRET: ${{ secrets.APS_JMS_SESSION_SECRET }}
JMS_AUTH_TIMEOUT_SECONDS: '15'
LLM_PROVIDER: ''
# 若 GitHub 运行器无法访问 JMS(外网受限),设置仓库变量 APS_E2E_SKIP_JMS=1 只跑离线访客冒烟
APS_E2E_SKIP_JMS: ${{ vars.APS_E2E_SKIP_JMS }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: install backend deps
run: python -m pip install -r requirements.txt
- name: frontend deps
working-directory: apps/web
run: npm ci
- name: build frontend(preview 模式依赖 dist)
working-directory: apps/web
run: npm run build
- name: install chromium
working-directory: apps/web
run: npx playwright install --with-deps chromium
- name: start backend(独立端口 8100,隔离数据)
run: |
python -m uvicorn server.main:app --host 127.0.0.1 --port 8100 > /tmp/aps-e2e-backend.log 2>&1 &
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:8100/api/health > /dev/null; then break; fi
sleep 2
done
curl -sf http://127.0.0.1:8100/api/health || (echo 'backend failed to start'; tail -200 /tmp/aps-e2e-backend.log; exit 1)
- name: run E2E smoke
working-directory: apps/web
run: npx playwright test --project=chromium
- name: upload playwright artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-e2e-artifacts
path: ${{ runner.temp }}/aps-e2e-artifacts
if-no-files-found: ignore
sbom:
name: SBOM(CycloneDX 1.6 + SHA-256)
runs-on: ubuntu-latest
env:
PYTHONUTF8: '1'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: install uv(提供 uvx 供 sidecar-python BOM)
run: python -m pip install uv
- name: npm run sbom
run: npm run sbom
- name: verify SBOM outputs
run: |
ls -la dist/sbom/
test -s dist/sbom/web-node.cdx.json
test -s dist/sbom/desktop-node.cdx.json
test -s dist/sbom/sidecar-python.cdx.json
test -s dist/sbom/release-manifest.json
node-tests:
name: node tests(updater/sidecar/packaging-smoke + Win 冒烟)· ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
# 根目录 npm run test:node = node --test apps/desktop/test/*.test.cjs + tests/node/packaging-smoke.test.mjs
# (updater 22 项 + sidecar 7 项 + packaging-smoke 12 项),纯 node 内置模块,无需 npm install
- name: node --test(updater + sidecar + packaging-smoke)
run: npm run test:node
# Windows 额外门禁:sidecar 本地冒烟(server/sidecar.py 纯 stdlib,~72s)
- name: setup python(sidecar 冒烟用)
if: runner.os == 'Windows'
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: sidecar 本地冒烟(20 冷启动/端口冲突/崩溃重启/中文路径)
if: runner.os == 'Windows'
run: node scripts/smoke-sidecar-local.mjs
# offline install verify:本 job 不构建安装包,产物存在时才校验;缺失则跳过
- name: offline install verify(产物存在时)
if: runner.os == 'Windows'
shell: pwsh
run: |
if (Test-Path "build/offline-check/offline-install-manifest.json") {
node scripts/offline-install-check.mjs --verify
} else {
Write-Host "offline manifest 不存在(本 job 不构建安装包)— 跳过 verify"
}
# Defender 扫描 best-effort:无 MpCmdRun(退出码 2)或无可扫描产物(退出码 3)视为跳过;
# 检出威胁/扫描失败(退出码 1)才阻断。产物由桌面构建流程生成,本 job 通常跳过。
- name: Defender 扫描(best-effort,退出码 2/3 跳过)
if: runner.os == 'Windows'
shell: pwsh
run: |
node scripts/defender-scan.mjs
$code = $LASTEXITCODE
if ($code -eq 0) { Write-Host "Defender: PASS"; exit 0 }
if ($code -eq 2) { Write-Host "Defender: SKIP(MpCmdRun 不可用)"; exit 0 }
if ($code -eq 3) { Write-Host "Defender: SKIP(无可扫描产物)"; exit 0 }
Write-Host "Defender: FAIL(exit $code)"
exit $code