226 lines
9.0 KiB
YAML
226 lines
9.0 KiB
YAML
# ============================================================
|
||
# 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
|