Skip to content

CI Actions

本页描述 GitHub Actions 的职责和信任边界。测试 profile、Python/架构矩阵与条件 smoke 见 测试矩阵,合并规则见 Pull Request 生命周期,远端 Ruleset 与 environment 契约见仓库治理与保护

工作流总览

Workflow 文件 主要职责 触发条件
CI .github/workflows/ci.yml Ruff format/check、类型检查、分发安装矩阵、远程浏览器 smoke、Entari load push main、PR、手动
Coverage .github/workflows/coverage.yml Python 3.10–3.14 × x64/arm64 pytest/coverage push main、PR、手动
Prek .github/workflows/prek.yml 仓库级 prek hooks push main、PR、手动
Docs PR Preview Build .github/workflows/docs-pr-preview.yml 检测文档相关变更,以只读权限严格构建静态站 PR open/sync/reopen
Docs PR Preview Deploy .github/workflows/docs-pr-preview-deploy.yml 校验并发布 build artifact,更新 PR 预览评论 preview build 完成
Docs PR Preview Cleanup .github/workflows/docs-pr-preview-cleanup.yml 删除已关闭 PR 的 Pages 预览 PR closed
Docs .github/workflows/docs.yml 对每个可能成为 release source 的 main SHA 执行严格文档构建,不写 Pages push main、手动
Publish (TestPyPI) .github/workflows/publish-test.yml 构建唯一 dev version 并 trusted publish 到 TestPyPI 仅手动
Auto Tag on Version Change .github/workflows/auto-tag.yml 汇合同一 main SHA 的 CI/Coverage/Docs/Prek,验证版本递增后 tag 并 dispatch Publish;也提供受同等门禁约束的 pre-tag recovery 四条 required workflow 完成、受约束的手动恢复
Publish .github/workflows/publish.yml 校验 tag/source/version,发布并回读 PyPI、创建 GitHub Release,最后部署对应 tag 的版本文档 v* tag、受约束的手动恢复

PR 必需质量层

CI

CI 是静态质量、分发包和跨容器行为的主入口:

  • Ruff:依次运行 ruff format --checkruff check,只验证 checkout,不改写文件;
  • TyBasedpyright:两套类型检查器分别执行;
  • Package Builduv build --no-sources 构建 wheel/sdist,用 twine check 校验 metadata,并在 Python 3.12 隔离安装 bare core、[takumi][playwright,filehost][pillow,skia]
  • Wheel Smoke:复用同一 artifact,在 Python 3.10、3.11、3.13、3.14 隔离安装wheel,断言 core 零 backend,并为受支持的 native extra 执行真实 PNG;
  • Remote Browser Render Smoke (Docker):通过 Docker Compose 顺序验证远程 Playwright WebSocket 的 MEMORY 与 filehost 路径,覆盖 text、Markdown 相对图片、CSS 字体/背景和模板本地资源,并检查 filehost 请求头、CORS、资源后缀与未认证 403;
  • Entari Load:在 Python 3.10–3.14 隔离安装 wheel,分别验证普通 library import 与 Entari loader 注册/卸载。

pytest 不在 CI 中重复执行,由 Coverage 统一覆盖。

Required Checks 是 always-run 汇总 job;上述任一层失败、取消或跳过都会使它失败。Ruleset 绑定这个稳定名称即可覆盖内部 job 和 Entari load 矩阵,而不必在每次扩展 Python 或后端维度时更新 required check 列表。

Coverage

Coverage 对 Python 3.10–3.14 分别在 x64 和 arm64 runner 上执行 CI profile。各矩阵项独立失败,不因一个版本失败而取消其他版本,并产生:

coverage-py<python-version>-<arch>.xml
pytest-py<python-version>-<arch>.log

XML 上传 Codecov,日志与 XML 同时作为 coverage-debug-* artifact 保留。每个矩阵项要求总覆盖率不低于 90%;不应用排除新代码或删除有效测试来规避阈值。

Coverage Matrix 汇总完整矩阵,只在所有版本与架构均成功时通过,并为 Ruleset 提供稳定 check 名称。

Prek

Prek 读取原生 prek.toml,并在 CI 中固定工具版本。workflow 对每次push、PR 和手动运行执行两层检查:

prek run --all-files --show-diff-on-failure --color=always
prek run actionlint --all-files --hook-stage=manual --show-diff-on-failure --color=always

第一条执行默认 stage 的仓库级 hooks;第二条显式执行较慢的 actionlint 及其ShellCheck 集成。CI 始终运行两条,本地至少运行第一条,修改 workflow 时必须同时运行第二条。hook 的自动修复、commit-msg 和增量检查契约见工程协作流程

Entari load 的边界

Entari load job 验证隔离安装、普通 import 无宿主副作用、plugin metadata/config、add_service 注册与 cleanup。它不能替代 pytest、真实浏览器启动或 Docker smoke。

Fork-safe 文档预览

文档预览被拆成三个 workflow,以同时满足 fork 支持和最小权限:

flowchart LR
    A["pull_request<br/>fork code, contents: read"] --> B["strict build"]
    B --> C["untrusted static artifact"]
    C --> D["workflow_run<br/>trusted default branch"]
    D --> E["validate PR + SHA + artifact"]
    E --> F["deploy pr-preview/pr-N<br/>sticky link comment"]
    G["pull_request_target: closed<br/>trusted base only"] --> H["remove pr-N preview"]

1. Build

Docs PR Preview Build 在普通 pull_request 上运行,因此 fork PR 只有只读 token。它通过 GitHub API 检查整个 PR 的 changed files;以下路径会触发 strict build:

  • docs/**mkdocs.ymlREADME.md
  • pyproject.tomluv.lock.python-versionMakefile
  • Docs 与 preview 相关 workflow 文件。

构建使用 PR checkout,但不接触写权限或环境 secrets,完成后只上传短期 docs-preview-site artifact。

always-run 的 Docs Preview 汇总 job 是该 workflow 的稳定门禁:没有文档输入变化时正常通过,有变化时则要求 strict build 与 artifact 上传成功。

2. Deploy

Docs PR Preview Deployworkflow_run 触发。只读的 resolve job 先检查上游 run 是否产生预览 artifact;普通代码 PR 没有 artifact 时会正常结束,不会把“不需要预览”误报为部署失败。需要预览时,后续 deploy job 才在受信任上下文取得 Pages 写权限。它不会 checkout PR 分支或执行 artifact 中的程序,而是:

  1. 要求上游 build 成功并且恰好产生一个未过期且下载前大小不超过上限的 docs-preview-site artifact;
  2. 要求 workflow run 恰好关联一个仍然 open 的 PR;payload 未携带关联时,用 head commit API 做受约束回查;
  3. 核对 workflow run 的 head SHA 仍是该 PR 当前 head,过期 run 正常跳过;
  4. checkout 受信任的默认分支并下载指定 run 的 artifact;
  5. 拒绝 symlink、特殊文件、异常文件数量或异常体积;
  6. 仅把验证后的静态目录部署到 gh-pages/pr-preview/pr-<NUMBER>/
  7. 创建或更新带固定 marker 的 PR 评论,给出预览链接。

预览 URL 形如:

https://<owner>.github.io/<repo>/pr-preview/pr-<NUMBER>/

文件约束校验不等于内容净化。部署 workflow 不会把 artifact 当作 shell、workflow script 或模板执行,但 reviewer 打开页面时,其中的 HTML/JavaScript 会由浏览器执行。

PR preview 只是同一 GitHub Pages origin 下的路径命名空间,并不是隔离域。公开 fork 可以控制该预览内容,因此必须把它视为不可信站点:不要在该 Pages origin 保存 secret、token 或可信的 localStorage 状态,不要让正式页面依赖同源机密,并建议用不带敏感登录状态的浏览器 profile 评审预览。

3. Cleanup

Docs PR Preview Cleanup 监听 pull_request_target: closed,且**不使用 paths filter**,保证任何曾经部署过预览的 PR 都能在关闭后清理。它只 checkout 默认分支,并把事件中的数字 PR number 传给删除 action,绝不 checkout 或执行 fork code。

不要把三段重新合成一个带 pull_request_target checkout 的 workflow;那会把仓库写 token 暴露给不受信任的 PR 代码。

正式文档部署

Docs 对每个 main push 执行 strict build,不使用 paths filter。它的成功结果作为同一 source SHA 的发布门禁,但不会提前创建正式版本目录或移动 latest;这样即使文档未变化,Auto Tag 也始终能汇合同一 SHA 的四条门禁。

只有 Publish 完成 PyPI hash 回读与 GitHub Release 后,Publish versioned documentation 才重新检出经过验证的 tag、再次 strict build,并调用 mike 写入正式版本目录。这样 /0.1.0/ 表示该版本已经存在可安装产物,而不是“某个版本 PR 已合并”。

正式版本和 PR previews 仍共享生成物分支 gh-pages。不同 PR 的 preview 更新互不取消;正式部署与 preview action 每次从最新远程状态开始,只使用非 force push,并在冲突时有限重试,避免一个 writer 覆盖另一个 writer 已发布的目录。

文档版本语义和独立恢复方式见 文档版本管理发布流程

TestPyPI 与正式发布

Publish (TestPyPI) 仅供维护者手动运行。它不是 PR workflow,不会自动评论安装命令,也不会给 fork 或同仓库 PR 自动发布 dev package。

Auto Tag on Version Change 只在同一 source SHA 的 CICoverageDocsPrek 全部成功后继续。三条通用 required workflow 对 main push 使用 source SHA 作为 concurrency key,不会因为后续 PR 很快合并而取消版本提交的门禁;PR 更新仍会取消自身的过期 run。

版本差异检测、tag/source/version 不变量、trusted publishing 权限拆分及部分失败恢复见 发布流程。普通 pyproject.toml 配置变更在版本未变化时不会发布。发布 workflow 的写权限按 job 收窄,不允许构建步骤同时持有 PyPI OIDC 和仓库写权限。

仓库内 Composite Actions

重复但边界稳定的步骤由 .github/actions/ 统一所有,workflow 只声明调用场景:

Action 唯一职责
setup-project 安装固定版本的 uv/Python,并按 alldocstestnone 同步依赖
build-distributions 构建 wheel/sdist,执行 Twine、隔离安装与 archive 内容验证,并输出 SHA-256
build-docs 执行 Zensical strict build 并保留构建日志
setup-skia-runtime 安装 Skia smoke 所需的 Linux 动态库

Composite action 不负责 checkout、job permissions、environment、artifact 传递或发布。调用 job 必须显式持有最小权限;获得 id-token: writecontents: write 或 Pages 权限的 job 不得借此重新执行未受信任的 PR 代码。PR preview 的受信任部署阶段也不调用 PR checkout 中的本地 action。

Action 供应链

  • 所有本仓库直接使用的第三方与 GitHub actions 固定到完整 commit SHA,行尾注释记录对应 release 版本;
  • CI 与发布 workflow 固定 uv==0.11.28;Docker remote smoke 的 bootstrap 也使用同一版本,升级时应作为工具链变更统一验证;
  • .github/dependabot.yml 定期检查 github-actions 更新;Dependabot PR 仍需 review release notes、action diff 与权限变化;
  • 外部 action 与 reusable workflow 的传递依赖按固定 SHA 规则审计;
  • 不接受只把固定 SHA 改为 @main、版本分支或 moving major tag 的更新。

本地对应命令

CI 层 本地命令
同步锁定开发环境 make sync-all
Prek 默认 hooks prek run --all-files
Prek workflow hooks prek run actionlint --all-files --hook-stage=manual
Ruff 格式化 make ruff-format
Ruff 格式验证 make ruff-format-check
Ruff lint make ruff-check
Basedpyright make typecheck(源码零诊断 + package type completeness 100%)
Ty make ty
Coverage profile tests make test-ci
Local browser tests make install-browser && make test-local
Remote browser smoke make remote-smoke
Strict docs build make docs-build
Build artifacts + metadata check make build-artifacts

make test-ci 与远端 Coverage Matrix 共用 90% 总覆盖率门禁,并包含 documentation contract:检查六章任务导航、canonical 页面唯一性、公共导出、完整配置路径与 Python 示例;make typecheck / make ty 同时覆盖 examples。make docs-build 负责链接、导航和页面渲染,不能替代前两类检查。

排障入口

  • 测试失败:下载对应 coverage-debug-* artifact,先看 pytest log 最后 200 行;
  • 远程浏览器失败:下载 remote-smoke-compose-logs,区分容器启动、WebSocket 和资源加载错误;
  • 文档预览 build 失败:本地设置同样的 DOCS_* preview 环境变量后 strict build;
  • 文档预览 deploy 被拒绝:检查关联 PR 数、PR 当前 head SHA 和 artifact 文件约束;
  • main 文档门禁失败:下载 docs-build-logs,本地执行 make docs-build
  • 发布后的版本文档失败:下载 release-docs-build-log / release-docs-deploy-log;确认 PyPI 与 GitHub Release 已正确后,对同一 tag 重跑 Publish,不得从 release 分支手工复制 site/
  • 打包失败:下载 package dist/build logs,并用 make build-artifacts 本地重现完整构建与 pinned twine 校验;
  • 发布失败:先判断 PyPI 是否已经产生不可逆上传,再核对 PyPI verification 的 filename/hash 结果,并按 部分失败恢复处理;
  • Auto Tag 未创建 tag:先核对同一 workflow_run.head_sha 的 CI/Coverage/Docs/Prek 是否全部 completed/success,再检查第一父提交与当前 project.version、trusted main SHA 和 tag 指向;禁止直接移动已发布 tag。