Repository navigation
154 lines (147 loc) · 5.81 KB
/
Copy pathci.yml
File metadata and controls
154 lines (147 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install
run: |
pip install -e ".[dev]"
- name: Lint (ruff)
run: ruff check src/spark tests scripts
- name: Type check (mypy)
run: mypy src/spark --ignore-missing-imports
- name: Import layering (import-linter)
run: lint-imports
- name: Security scan (bandit)
run: bandit -r src/spark -ll -ii -c bandit.yaml
- name: OpenAPI snapshot is current
# The frontend types in web/src/api/schema.ts are generated from this
# snapshot; a stale file means the UI silently drifts from the API.
run: |
pip install -e ".[openapi]"
python scripts/export_openapi.py --check
test:
needs: quality
runs-on: ${{ matrix.os }}
# A hung test previously kept Windows runners alive for 40+ minutes with no
# signal. Cap the job so a deadlock fails fast and visibly.
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
# Both supported platforms are first-class: the same code must pass on
# Linux and Windows (different shell, path semantics, process model).
os: [ubuntu-latest, windows-latest]
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: |
pip install -e ".[dev]"
- name: Lint (ruff)
run: ruff check src/spark tests scripts
- name: Test (pytest + coverage)
run: |
# 门槛从 65% 提到 75%:本轮实测 76%,留 1 个点余量防止
# 平台差异(Windows 与 Linux 覆盖面不同)误伤。90%/85% 的红线还没到,
# 缺口与原因见 docs/reviews/2026-10-22-full-audit.md 第十二节。
python -m pytest -q --cov=spark --cov-report=term-missing --cov-fail-under=75
# dist 是提交进仓库的(pip 安装要开箱即用),前端此前没有任何 CI 门禁:
# 语法/类型/构建漂移只能在上游 release 时暴露。这一道门同时校验
# "提交的 dist 与全新构建字节一致",防止 CRLF/换行等静默漂移。
frontend:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: web/package-lock.json
- name: Install
working-directory: web
run: npm ci
- name: Lint (eslint)
working-directory: web
run: npm run lint
- name: Build (tsc + vite)
working-directory: web
run: npm run build
- name: Committed dist matches a fresh build (byte-for-byte)
run: |
if git diff --exit-code -- src/spark/web/dist; then
echo "dist is current"
else
echo "::error::src/spark/web/dist is out of sync with the frontend source — rebuild and commit the result"
exit 1
fi
windows-checkout:
timeout-minutes: 10
# Guards the failure mode found in review: a Windows-reserved filename in the
# tree makes `git clone` unusable on Windows even though Linux is fine.
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: No Windows-reserved paths
shell: pwsh
run: |
$reserved = 'CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]'
$bad = git ls-files | Where-Object {
$leaf = [System.IO.Path]::GetFileNameWithoutExtension($_)
$leaf -match "^($reserved)$"
}
if ($bad) {
Write-Error "Windows-reserved filenames in tree: $bad"
exit 1
}
Write-Host "no reserved filenames"
packaging:
timeout-minutes: 10
# 发布链路的静态门:安装脚本语法、版本号一致性、打包 spec 引用完整性。
# 这些错误只有在用户跑 install 脚本或打 tag 时才会暴露,成本极高。
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install script syntax (POSIX sh)
run: sh -n scripts/install.sh
- name: Version resolves and is valid semver
run: |
python - <<'PY'
import pathlib, re, sys, tomllib
version = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
if not re.fullmatch(r"\d+\.\d+\.\d+([-.][A-Za-z0-9.]+)?", version):
sys.exit(f"pyproject version is not semver: {version}")
# 源码树(未安装)下 spark.__version__ 必须与 pyproject 一致:
# 安装后它优先读发行版元数据,故这里显式走 pyproject fallback。
pyproject = pathlib.Path("src/spark/__init__.py").read_text(encoding="utf-8")
if "pyproject.toml" not in pyproject:
sys.exit("spark.__version__ no longer falls back to pyproject.toml")
print("version", version)
PY
- name: Installer assets exist in the release workflow
run: |
python - <<'PY'
import pathlib, sys
wf = pathlib.Path(".github/workflows/release.yml").read_text(encoding="utf-8")
missing = [a for a in ("spark-windows-x86_64.zip", "spark-linux-x86_64.tar.gz", "spark-macos-arm64.tar.gz", "SHA256SUMS") if a not in wf]
if missing:
sys.exit(f"release.yml no longer publishes: {missing}")
print("release assets declared")
PY