持续集成(CI)说明
本页说明 GitHub Actions 流水线(.github/workflows/ci.yml)里每个任务(job)和每个步骤(step):做什么、跑哪些测试和为什么、大概多久、怎么在本机复现、失败了怎么读。面向贡献者和维护者。
本页与流水线绑定。 pnpm ci:docs-check(包含在 scripts/validate.sh 里,也是流水线自己的 ci-docs 任务)会在下列情况下失败:workflow 里的某个任务或步骤在本页没有对应小节;本页写了已经不存在的任务或步骤;ci.md(英文主版)和本页列出的任务、步骤不一致。改流水线,就要在同一个 PR 里同时改 docs/ci.md 和本页。标题的写法见末尾“让本页保持准确”。
耗时是 GitHub 托管的 ubuntu-24.04 runner(2 核)上的典型值,每个 PR 的运行摘要里有实际数字。测试数量是约数,会随项目增长变化。
flowchart LR PR([pull request / 推送到 main / 手动运行]) --> changes PR --> ci-docs changes -- 代码有改动 --> static changes -- 代码有改动 --> unit changes -- 代码有改动 --> integration changes -- 代码有改动 --> e2e changes -- 镜像输入有改动 --> images changes -- 镜像输入有改动 --> linux-smoke changes --> ci-ok ci-docs --> ci-ok static --> ci-ok unit --> ci-ok integration --> ci-ok e2e --> ci-ok images --> ci-ok linux-smoke --> ci-ok ci-ok --> merge([唯一需要的必过检查])所有任务并行,总时长取决于最慢的那个(端到端任务,约 10 到 12 分钟),而不是相加。ci-ok 是分支保护应当要求的唯一检查:只有分类器选中的所有任务都成功,它才通过。
跑哪些任务由 changes 任务按改动内容决定:
| 改动 | 运行的任务 |
|---|---|
只改文档(*.md、*.html、图片) |
changes(含文档链接检查)、ci-docs、ci-ok |
| 其他(代码、脚本、配置、fixture、workflow、Dockerfile) | 上面的再加 static、unit、integration、e2e |
同时改了镜像输入(Dockerfile、.dockerignore、package.json、lockfile、workspace 文件、next.config.ts、tsconfig*.json、compose.yaml、jobspy/Dockerfile、jobspy/requirements.txt、.github/ 下任何文件) |
再加 images 和 linux-smoke |
推送到 main |
同 PR;integration 还会按默认文件顺序再跑一遍 |
手动运行(workflow_dispatch) |
全部 |
没被选中的任务显示为 skipped,ci-ok 只接受这种情况;被选中却被跳过、取消或失败的任务会让 ci-ok 失败。
workflow 级设置:permissions: contents: read(任何任务都不能写仓库;来自 fork 的 PR 没有密钥);每个 ref 一个 concurrency 组(新的推送会取消 PR 上较早的运行,但不会取消 main 上的运行);每个任务和步骤都有 timeout-minutes,卡住的步骤会失败,而不是挂几个小时。
怎么读一次失败的运行
Section titled “怎么读一次失败的运行”- 打开 PR 的检查列表:只要有任务是红的,
ci-ok就是红的。要打开的是红色的那个任务。 - 任务里红色的步骤默认展开。PR 注解(
::error::行、Playwright 的githubreporter)也出现在 “Files changed” 页和运行摘要里。 e2e失败时,产物playwright-report(保留 7 天)里有 HTML 报告和失败测试的 trace:pnpm exec playwright show-report playwright-report,单个 trace 用pnpm exec playwright show-trace <trace.zip>。- 只有重试才通过的测试会让任务失败(
--fail-on-flaky-tests),这是有意的:修掉不稳定的原因,不要重跑到变绿。 - 只有原因明显出在 runner 上(网络、缓存服务)时才点 “Re-run failed jobs”,不要用它绕过失败的测试。
各步骤常见原因见下面各节。
Job changes
Section titled “Job changes”决定其他任务要不要跑,并检查文档里的相对链接。每次改动都会运行。上限 5 分钟,通常不到 20 秒。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4,fetch-depth: 0(完整历史,因为分类器要对比两个提交)。只会因 GitHub 故障失败。
Classify the change
Section titled “Classify the change”算出改动文件列表,写出其他任务读取的三个输出:
code:只要有一个改动文件不是文档(*.md、*.html、png、jpg、svg、gif)就是true。测试会读 fixture、YAML 和 Dockerfile,所以它们算代码。images:改了镜像输入(见上表)时为true,选中images和linux-smoke。full:推送到main和手动运行时为true,会多跑一遍默认顺序的集成测试。
PR 的对比范围是 base...head,推送是 before..sha。新分支、首次推送、手动运行都选中全部。算不出差异时这一步会失败(ci-ok 随之失败),不会悄悄当作“没改动”。手动复现:git diff --name-only origin/main...HEAD,再套用上面两条规则。
Documentation links
Section titled “Documentation links”node scripts/check-doc-links.mjs(即 pnpm docs:check,也在 scripts/validate.sh 里)。检查 Markdown 里的每个相对链接和 HTML 指南里的每个 href / src 是否指向存在的文件;不跟踪锚点和绝对 URL。约 5 秒。失败时列出 文件 -> 目标。常见原因:文档被改名或删除、相对路径写错。
Job ci-docs
Section titled “Job ci-docs”运行 pnpm ci:docs-check,让本页和流水线保持同步。它是单独的任务,因此只改文档的 PR 也会跑(删掉本页的一个标题就是只改文档的改动)。约 1 分钟,大部分是依赖安装(有缓存)。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up pnpm
Section titled “Set up pnpm”pnpm/action-setup@v4:安装 package.json 的 packageManager 固定的 pnpm 版本。
Set up Node.js
Section titled “Set up Node.js”actions/setup-node@v4,Node 22(engines 里的版本),pnpm store 按 pnpm-lock.yaml 的哈希缓存。
Install dependencies
Section titled “Install dependencies”pnpm install --frozen-lockfile。package.json 和 pnpm-lock.yaml 不一致时失败:本地 pnpm install 后提交 lockfile。所有跑项目代码的任务都有这一步,下面不再重复解释。
CI documentation matches the workflows
Section titled “CI documentation matches the workflows”pnpm ci:docs-check(scripts/check-ci-docs.ts)。解析 .github/workflows/ 下每个文件,要求每个步骤都有 name:,并把任务和步骤与 docs/ci.md 的标题对比;还会对比 docs/ci.zh.md 和 docs/ci.md 的任务、步骤标题,并检查每个 Dependabot ecosystem 都在 Dependabot 一节里出现。每条报错都会说该添加、改名或删除哪个标题。它自己的测试是 tests/ops/ci-docs-check.test.ts。本地运行 pnpm ci:docs-check(一两秒)。
Job static
Section titled “Job static”不需要数据库和浏览器的检查:生成文件、lint、类型、配置、shell 语法、runner 打包、迁移漂移、Python sidecar 测试。只在代码改动时运行。上限 15 分钟,通常 4 到 6 分钟(安装、lint、typecheck 占大头)。除 runner 打包、迁移漂移和 sidecar 测试外,其余都在 scripts/validate.sh 里。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up pnpm
Section titled “Set up pnpm”pnpm/action-setup@v4。
Set up Node.js
Section titled “Set up Node.js”actions/setup-node@v4,Node 22,pnpm store 缓存。
Install dependencies
Section titled “Install dependencies”pnpm install --frozen-lockfile(见 ci-docs)。
I18n catalog (messages valid, generated files current)
Section titled “I18n catalog (messages valid, generated files current)”pnpm i18n:check(tsx scripts/i18n-catalog.ts --check)。校验每个 messages/<id>.json(核心语言 en、zh 必须有全部 key,其他语言不能有英文没有的 key,每条消息的占位符必须与英文一致),并确认生成文件与生成器现在会写出的内容一致。报错会点出 key 或文件。修复:改 messages 后运行 pnpm i18n:catalog,提交重新生成的文件。几秒钟。
pnpm lint(eslint .),覆盖整个仓库,包括测试和脚本。失败时打印 文件:行 规则。规则允许时本地用 pnpm lint --fix。约 1 分钟。
Typecheck
Section titled “Typecheck”pnpm typecheck(tsc --noEmit,严格模式,覆盖 src、tests、scripts、e2e)。抓类型错误以及背后的坏 import。约 1 分钟。
Config check
Section titled “Config check”pnpm config:check(tsx scripts/config-check.ts)。按 schema 和 config_version 校验 config/ 里每个随包默认值、每个 market pack,以及跨文件引用(例如 preferences.yaml 的市场能解析)。几秒钟。常见原因:配置改动违反了 zod schema。见 configuration.zh.md。
Shell script syntax
Section titled “Shell script syntax”for f in scripts/*.sh scripts/ops/*.sh; do bash -n "$f"; done。只做语法解析,覆盖运维脚本(部署、备份、恢复、worker 安装、validate)。脚本的行为由 tests/ops/ 测试。瞬间完成。
Runner bundle
Section titled “Runner bundle”pnpm build:runner:用 esbuild 把采集器(src/runner/index.ts、src/runner/healthcheck.ts)打包到 dist/,和 Dockerfile 的 runner 目标一致。e2e 用的是替身 runner,所以真实 runner 入口的坏 import 只会在这里(早于镜像构建)被发现。约 20 秒。
Migration drift
Section titled “Migration drift”运行 pnpm exec drizzle-kit generate,如果 drizzle/ 下有任何变化就失败:src/lib/db/schema.ts 必须与已提交的迁移一致。失败时打印改动文件和 ::error::src/lib/db/schema.ts and drizzle/ disagree。修复:pnpm db:generate --name <slug>,提交生成的 SQL 和 drizzle/meta 里的 journal。不要手改生成的 SQL 来消除报错。
Set up Python
Section titled “Set up Python”actions/setup-python@v5,Python 3.12,pip 缓存以 jobspy/requirements*.txt 为键。
Install jobspy sidecar dependencies
Section titled “Install jobspy sidecar dependencies”在 jobspy/ 里 python -m pip install -r requirements-dev.txt,安装 sidecar 的运行依赖和测试依赖。本地请用虚拟环境:python3.12 -m venv .venv && .venv/bin/pip install -r jobspy/requirements-dev.txt。
Jobspy sidecar tests
Section titled “Jobspy sidecar tests”在 jobspy/ 里 python -m pytest -q(jobspy/tests/ 里约 20 个测试,抓取库被 mock:请求校验、结果映射、jobspy/app.py 这个小 HTTP 服务的错误处理)。几秒钟。常见原因:改了 jobspy/app.py 没改测试,或依赖升级。
Job unit
Section titled “Job unit”Vitest 单元测试:tests/**(除 tests/integration/)下约 330 个文件、约 4300 个测试,组件用 jsdom,服务端代码用 node 环境。整套单跑约 4 分钟,所以用 Vitest 的 --shard 拆成两个并行任务 unit (1/2) 和 unit (2/2)(各约 2 到 3 分钟)。fail-fast 关闭,两个分片都会报告。只在代码改动时运行,上限 15 分钟。
这些测试不需要数据库、网络和真实时钟:时间是注入或伪造的,配置在每个测试里用临时目录构造,冻结的 fixture tests/fixtures/config/defaults/ 代替随包的 config/(测试从不断言随包默认值)。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up pnpm
Section titled “Set up pnpm”pnpm/action-setup@v4。
Set up Node.js
Section titled “Set up Node.js”actions/setup-node@v4,Node 22,pnpm store 缓存。
Install dependencies
Section titled “Install dependencies”pnpm install --frozen-lockfile。
Unit tests
Section titled “Unit tests”pnpm test --shard=<n>/2,即对一半文件执行 vitest run。本地全跑用 pnpm test,单个文件 pnpm test tests/ops/backup.test.ts,单个测试加 -t "<名称>"。文件落在哪个分片由 Vitest 按文件列表决定,增删文件后失败的文件可能换分片。失败输出是 Vitest 的:测试名、期望与实际的差异、行号。常见原因:没 mock 时钟;依赖时区或其他测试遗留状态(单独重跑该文件就能分辨);断言了随 messages 变化的文案。
Job integration
Section titled “Job integration”需要真实 PostgreSQL 17 的测试:仓库层与迁移、周期与队列、控制请求、ai-worker、仪表盘查询、用真实 pg_dump / pg_restore 的备份。tests/integration/ 下约 60 个文件、约 600 个测试,通过 vitest.integration.config.ts 运行(node 环境,文件依次运行,因为共用一个在每个文件前后都被清空的数据库)。上限 20 分钟,通常 3 到 4 分钟。
任务带一个 postgres:17 service 容器,环境变量 DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres。该角色可以建库:每次运行创建一个唯一命名的数据库(tests/integration/global-setup.ts),结束时删除,服务器上其他东西不受影响。REQUIRE_PG_TOOLS=1 让备份测试在缺客户端工具时失败而不是跳过。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up pnpm
Section titled “Set up pnpm”pnpm/action-setup@v4。
Set up Node.js
Section titled “Set up Node.js”actions/setup-node@v4,Node 22,pnpm store 缓存。
Install dependencies
Section titled “Install dependencies”pnpm install --frozen-lockfile。
Provide PostgreSQL 17 client tools
Section titled “Provide PostgreSQL 17 client tools”把 pg_dump 和 pg_restore 建成指向 scripts/ops/pg-docker.sh 的符号链接,它在 --network host 容器里用 postgres:17 镜像里的工具(所以 localhost:5432 能连上)。客户端和服务端必须同一大版本;工具取自 service 已经拉取的镜像,避免用 apt.postgresql.org(它曾让这一步卡了一小时)。最后打印两个版本作为冒烟检查。本地 Linux 上做法相同(ln -sf "$PWD/scripts/ops/pg-docker.sh" /tmp/pgbin/pg_dump,pg_restore 同理),并导出 PG_DUMP_PATH、PG_RESTORE_PATH;macOS 的 Docker Desktop 没有 host 网络,装 PostgreSQL 17 客户端工具(如 brew install postgresql@17)并把这两个变量指过去。
Integration tests (default order)
Section titled “Integration tests (default order)”pnpm test:integration --reporter=verbose,只在 full 为 true 时运行(推送到 main、手动运行)。verbose reporter 会在每个文件开始时打印它,卡住的文件因此能被点名。上限 15 分钟。本地怎么跑见 本地跑完整流水线。
Integration tests (shuffled file order)
Section titled “Integration tests (shuffled file order)”用 --sequence.shuffle.files --sequence.seed=<seed> 跑同一套测试;seed 以 ::notice:: 注解打印(integration file order seed: N)。PR 只跑这种顺序:它能发现依赖其他文件遗留状态的文件,固定顺序会掩盖这类问题。复现失败:用注解里的 seed 运行 pnpm test:integration --sequence.shuffle.files --sequence.seed=<seed>。上限 15 分钟。
Job e2e
Section titled “Job e2e”Playwright 浏览器测试,对生产构建(pnpm build 再 pnpm start)、一次性的 PostgreSQL 17 service 和替身 runner(scripts/e2e-runner.ts:心跳、配置确认、控制请求轮询,使“立即运行一轮”和“配置已保存”无需真实采集器即可工作)运行。没有 AI 提供方,没有职位通道,除 localhost 外没有网络。e2e/ 下约 240 个测试、35 个 spec 文件,另有 e2e-setup/ 的 12 个测试。上限 20 分钟,总共通常 10 到 12 分钟。任务跑在 runner 主机上,不在 Playwright 容器镜像里(实测镜像更慢)。
环境:DATABASE_URL 指向 service;scripts/e2e-prepare.ts 创建数据库 jobradar_e2e_3100(名字随 E2E_PORT),应用迁移,加载仪表盘种子(约 20 条虚构职位),并用 tests/fixtures/config/defaults/ 的冻结 fixture 填充全新的配置和 profile 目录。E2E_PORT=3100 是服务端口;E2E_SKIP_BUILD=1 告诉 web server 命令不要再构建。
主测试集的 project(playwright.config.ts)。 spec 并不都以同一种方式运行:
project 的规划在 e2e/projects.ts(由 tests/e2e-projects.test.ts 做单元测试,它还用 playwright test --list 核对点名的 spec 只跑它自己)。
chromium让大部分 spec 完全并行(2 核 runner 上 2 个 worker),各用自己的浏览器 context。它忽略所有会写共享配置的 spec,以及所有带@exclusive标签的测试。chromium-exclusive在chromium全部结束后,一次一个(workers: 1)运行带@exclusive标签的测试。所有 spec 共用一个预置数据的数据库,并行的 spec 一边跑一边改它:打开详情页会把岗位标为已看,访问“今日”会移动“上次访问”标记,有的测试会忽略、关闭、收藏岗位(再改回去),scoring spec 会临时插入一个岗位。改动每个页面都会读的状态(访问标记、所有岗位的已看记录:list-today.spec.ts),或比较整个列表计数的测试(dashboard.spec.ts里/jobs筛选的计数、filter.spec.ts的已过滤开关、insights.spec.ts的头部数字)要加这个标签。只改一个岗位的测试用一个别的 spec 不碰的岗位、事后改回,并只对这个岗位断言,它留在并行 project 里。- 会改写同一个共享配置目录里文件的 spec(settings、language、breakdown、resume、interests、sources、market)会互相破坏,或破坏并行 spec 里读取这些设置的断言(Sources 页的 AI 卡片、界面语言)。所以它们作为单 spec 的 project,用
dependencies串成链,一个接一个,且在chromium-exclusive全部结束之后才开始。按顺序如下:- 配置链:
chromium-config-settings、-settings-invalid、-language、-breakdown、-resume。 - interests 链:
chromium-interests、chromium-interests-suggestions、chromium-multi-filters(都会把视图或接受的建议存到唯一的interests.yaml)。 - sources 链:
chromium-sources-registry和chromium-sources-discovery(都改写sources.yaml)。 - settings 链:
chromium-settings-advanced和chromium-settings-workflow(高级编辑器和工作流标签页改写 ui、notify、ai、sources、pipeline 配置)。 - 最后是
chromium-market(切换市场会改变每个页面和preferences.yaml)。
- 配置链:
所以总时长不是“最慢 spec 除以 2”:这条链是有意串行的。
- 新 spec 只要写配置目录里的任何文件,就必须加进
e2e/projects.ts的SERIAL_CHAIN(拥有自己的串行 project),否则会与其他 spec 并行而随机失败。新测试如果改共享的数据库状态或计数整个列表,加{ tag: EXCLUSIVE_TAG }。 - 命令行上点名的 spec 文件(
pnpm test:e2e e2e/settings.spec.ts)只跑它们自己:只有含匹配 spec 的 project 参与,每个都收窄到点名的 spec,顺序仍按上面。(Playwright 会完整运行依赖 project,没有这一步时,链上的 spec 会先把整个并行套件跑一遍。)带file:line时,选中的 project 不再串起来,只跑那几行的测试。--project <名称>仍会先跑它的依赖;要单独跑加--no-deps。 - CI 里
retries是 1,reporter 是github、list、html;forbidOnly拒绝提交test.only。
设置向导测试集(playwright.setup.config.ts) 单独运行,因为它需要另一个世界:全新安装。它有自己的端口(E2E_SETUP_PORT,默认 3105)、自己的数据库(jobradar_e2e_setup_<port>,由 scripts/e2e-prepare.ts --fresh 删除、重建并只迁移不灌种子)、空的配置和 profile 目录,且没有替身 runner(请求的周期保持排队)。1 个 worker、按顺序执行,因为这些 spec 在同一个安装上依次走完向导。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up pnpm
Section titled “Set up pnpm”pnpm/action-setup@v4。
Set up Node.js
Section titled “Set up Node.js”actions/setup-node@v4,Node 22,pnpm store 缓存。
Install dependencies
Section titled “Install dependencies”pnpm install --frozen-lockfile。
Cache Playwright browsers
Section titled “Cache Playwright browsers”actions/cache@v4,缓存 ~/.cache/ms-playwright,键为 runner 系统加 pnpm-lock.yaml(浏览器版本跟随锁定的 @playwright/test)。设置 steps.playwright-cache.outputs.cache-hit。
Install Playwright browser and system libraries
Section titled “Install Playwright browser and system libraries”pnpm exec playwright install --with-deps chromium,只在缓存未命中时运行。下载 Chromium 并(用 apt)安装它需要的系统库。最多一分钟左右。
Install Playwright system libraries
Section titled “Install Playwright system libraries”pnpm exec playwright install-deps chromium,只在缓存命中时运行:缓存里只有浏览器,没有系统库,所以每次都要装系统库。
Cache the Next build cache
Section titled “Cache the Next build cache”actions/cache@v4,缓存 .next/cache(只是 Next 的增量构建缓存;应用始终从当前源码构建)。键包含 lockfile 以及 src/、messages/、next.config.ts 的哈希,回退键只有 lockfile。
pnpm build(先生成 i18n catalog,再 next build)。产出服务启动所用的生产构建。允许最多 8 分钟。这里失败说明应用有 static 任务没发现的编译或类型错误(例如服务端 / 客户端边界违规)。
End-to-end tests
Section titled “End-to-end tests”pnpm test:e2e --workers=2 --fail-on-flaky-tests:如上所述的主测试集所有 project。web server 命令先运行 scripts/e2e-prepare.ts,再 pnpm start -p 3100 -H 127.0.0.1。只有重试才通过的测试会让任务失败;重试仍会为报告记录 trace。上限 12 分钟。本地:先起数据库(docker compose up -d db,或下文的 postgres 容器),pnpm build,然后 E2E_PORT=3100 E2E_SKIP_BUILD=1 pnpm test:e2e;E2E_PORT=<端口> 让一次运行有自己的数据库和目录,多个检出可以并排运行,但同一台机器同一时间只跑一套完整 e2e:并行的完整运行会互相抢资源,产生并非真实失败的超时。开发时只跑受影响的 spec(pnpm test:e2e e2e/pipeline.spec.ts、几个文件,或 e2e/dashboard.spec.ts:206 只跑一个测试;只跑这些,web server 和替身 runner 照常启动),完整套件留给 CI。常见失败原因:定位器随界面文案变了(spec 以英文运行);写共享配置的 spec 没加进 SERIAL_CHAIN;计数整个列表或改共享数据的测试没加 @exclusive 标签;只有 2 个 worker 才暴露的竞态(trace 里能看到)。
Setup wizard end-to-end tests
Section titled “Setup wizard end-to-end tests”pnpm test:e2e:setup --fail-on-flaky-tests,在主测试集之后、同一份构建上、用自己的服务、数据库和目录运行(见上)。上限 6 分钟,通常 1 到 2 分钟。报告目录:playwright-report-setup/。
Upload the Playwright report and traces
Section titled “Upload the Playwright report and traces”只在本任务有步骤失败时运行(if: failure()):把 playwright-report/ 和 test-results/ 上传为产物 playwright-report,保留 7 天。从运行页下载,用 pnpm exec playwright show-report 打开。
Job images
Section titled “Job images”构建三个生产镜像但不推送:web 和 runner(根目录 Dockerfile 的目标)以及 jobspy sidecar(jobspy/Dockerfile),以三个并行任务的矩阵运行,名为 image (web)、image (runner)、image (jobspy)。它在部署之前发现 apt / CLI 安装失败、COPY 路径错误、构建阶段损坏。只在镜像输入有改动时运行(见开头的表)。上限 30 分钟;层缓存热时几分钟,冷构建更久。各镜像的层缓存在 Actions 缓存里。本地:docker build --target web .、docker build --target runner .、docker build jobspy。
Check out the repository
Section titled “Check out the repository”actions/checkout@v4。
Set up Docker Buildx
Section titled “Set up Docker Buildx”docker/setup-buildx-action@v3:支持 Actions 层缓存的构建器。
docker/build-push-action@v6,push: false,cache-from / cache-to 为按镜像区分作用域的 gha 类型。日志会显示失败的 Dockerfile 行。常见原因:基础镜像仓库里的包移动了;COPY 的路径被 .dockerignore 排除;lockfile 变化后构建阶段 --frozen-lockfile 装不上。
Job linux-smoke
Section titled “Job linux-smoke”Linux 从零首次运行:在 Ubuntu runner 上用一份空的检出,按随附默认值 docker compose up --build,面板必须能应答。它能发现 macOS 上 Docker Desktop 掩盖的问题:bind mount 的文件属主(PUID / PGID)、以 0600 写入的配置文件、容器写不了的目录。和 images 一样,只在镜像输入有改动时运行。上限 45 分钟;冷构建约 10 到 15 分钟(四个镜像都在没有层缓存的情况下构建)。本地(Linux):在一份新克隆里执行下面各步骤的命令(不要在你想保留其服务栈和数据库的检出里做:最后一步会删除服务栈和它的卷)。
Check out the repository
Section titled “Check out the repository”actions/checkout@v7。
Prepare .env and the data folders as the host user
Section titled “Prepare .env and the data folders as the host user”写一个只设 PUID / PGID(runner 自己的用户,id -u、id -g)的 .env,正如 host-setup.zh.md 要求 Linux 用户做的那样;并以该用户创建 data/ 下的目录和 .auth/,避免 Docker 以 root 身份创建 bind mount 目录。
Start the stack from scratch
Section titled “Start the stack from scratch”docker compose up -d --build --wait --wait-timeout 600:构建四个镜像,等到每个服务都报告健康(runner 会执行迁移并开始心跳)。这里失败要么是构建错误(见 images 任务),要么是某个服务一直没有变健康:“Container logs on failure”一步会打印原因。
Dashboard health check
Section titled “Dashboard health check”GET /api/health 必须返回 200,GET /(页面导航)返回 200 或重定向到首次运行向导。
The web can write its config folder
Section titled “The web can write its config folder”在 web 容器里的 data/config 中创建并删除一个文件(设置页和向导会写这里),并检查 runner 能写 data/backups。失败说明容器用户不拥有 bind mount 的目录:检查 compose.yaml 的 user: 和 PUID / PGID 两行。
Container logs on failure
Section titled “Container logs on failure”仅当前面的步骤失败时运行:docker compose ps -a 和每个服务最后 200 行日志。
Remove the stack and its volume
Section titled “Remove the stack and its volume”总是运行:在一次性的 runner 上 docker compose down -v --remove-orphans。
Job ci-ok
Section titled “Job ci-ok”唯一需要要求的检查。needs 列出所有其他任务,if: always() 让它即使其他任务失败或被跳过也会运行。上限 5 分钟,几秒钟。
All selected checks passed
Section titled “All selected checks passed”读取 needs 的结果(jq):changes 和 ci-docs 必须成功;static、unit、integration、e2e 在 changes 选中代码时必须成功,否则必须是 skipped;images 和 linux-smoke 同理,由 images 输出决定。其他任何组合(包括被选中却被跳过或取消的任务)都会打印各任务结果和 ::error::a selected check did not succeed。它变红时,先看它打印的各任务结果,再打开对应任务。
Dependabot
Section titled “Dependabot”.github/dependabot.yml 每周为三个生态系统开 PR:
| 生态系统 | 目录 | 说明 |
|---|---|---|
npm |
/ |
最多 5 个未关闭 PR;minor 和 patch 更新合并成一个 PR;忽略 @types/node 的 major 更新(类型跟随 engines 里的 Node 大版本) |
pip |
/jobspy |
最多 3 个未关闭 PR;sidecar 的 Python 依赖 |
github-actions |
/ |
最多 3 个未关闭 PR;workflow 里固定的 action 版本 |
Dependabot 的 PR 和其他 PR 跑同一条流水线。action 升级会改 .github/,因此算代码也算镜像输入,会跑全部任务;如果它改了步骤名,ci-docs 会失败,直到本页更新。
scripts/release-check.sh 不是流水线的一部分。维护者在仓库公开或发版之前手动运行,提交可疑时也可运行:对 git 会跟踪的每个文件和整个提交历史跑 gitleaks(通过 Docker,镜像版本固定)、扫描个人字符串和本机路径、检查 .gitignore 覆盖 .env、data/、.auth/,并检查仓库里没有私有规划编号(维护者跟踪系统里的编号:F-、B-、ADR- 加三位数字,M-、P- 加两位数字;ADR 不分大小写)既不在任何文件内容里也不在文件名里,仓库根目录的 drizzle/ 和 pnpm-lock.yaml,以及 scripts/planning-ids.allow 里精确列出的 路径 或 路径:行号(编号是数据而不是引用)除外。这一项也可单独运行:scripts/release-check.sh --planning-ids-only(不需要 Docker,约一秒);它是 scripts/validate.sh 的一部分,也由单元测试 tests/ops/release-check-script.test.ts 覆盖,所以编号回来时 CI 会失败。退出码 0 表示干净,1 列出问题,2 表示没能运行。它单独成关,因为需要 Docker 跑 gitleaks,还需要 CI 里没有的、未跟踪的本地模式文件。详见 RELEASING.md。
本地跑完整流水线
Section titled “本地跑完整流水线”上面每个任务都只是几条命令。下面是通用做法(有 Docker、Node 22 加 pnpm、Python 3.12 就够),使用一个端口与你真实数据库不同的一次性 PostgreSQL。
# 一次性数据库(角色可建库,与 CI 的 service 一样)docker run -d --name jr-ci-pg -p 127.0.0.1:55432:5432 \ -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres:17export DATABASE_URL=postgres://postgres:postgres@127.0.0.1:55432/postgres
pnpm install --frozen-lockfile
# 任务:changes(链接)、ci-docs、static、unit(第一条就是 scripts/validate.sh 做的事)scripts/validate.sh # shell 语法、i18n、lint、typecheck、单元测试、配置、文档链接、CI 文档pnpm build:runnerpnpm exec drizzle-kit generate < /dev/null && git status --porcelain -- drizzle # 必须没有输出(cd jobspy && python3.12 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt && .venv/bin/python -m pytest -q)
# 任务:integration(需要大版本 17 的 pg_dump / pg_restore,见该任务的步骤)export PG_DUMP_PATH=/path/to/pg_dump PG_RESTORE_PATH=/path/to/pg_restore REQUIRE_PG_TOOLS=1pnpm test:integration --reporter=verbosepnpm test:integration --sequence.shuffle.files --sequence.seed=$RANDOM
# 任务:e2e(本机同一时间只跑一套完整 e2e)pnpm exec playwright install chromiumpnpm buildE2E_PORT=3100 E2E_SKIP_BUILD=1 pnpm test:e2e --workers=2 --fail-on-flaky-testsE2E_PORT=3100 E2E_SKIP_BUILD=1 pnpm test:e2e:setup --fail-on-flaky-tests
# 任务:imagesdocker build --target web . && docker build --target runner . && docker build jobspy
# 任务:linux-smoke(Linux,只在新克隆里做:最后会 `docker compose down -v`)printf 'PUID=%s\nPGID=%s\n' "$(id -u)" "$(id -g)" > .env && mkdir -p data/{config,profile,backups,exports,logs} .authdocker compose up -d --build --wait --wait-timeout 600 && curl -fsS http://127.0.0.1:3000/api/healthdocker compose down -v --remove-orphans
docker rm -f -v jr-ci-pg本地运行与托管流水线有三点不同:测的是你的工作区,而不是 PR 与 main 合并后的结果;用的是你自己的 Docker 和 Node 版本;不会自己判断跳过什么(只改文档时只需 pnpm docs:check 和 pnpm ci:docs-check)。
让本页保持准确
Section titled “让本页保持准确”- workflow 的每个任务需要一个标题
### Job `<任务 id>`(jobs:下的 key;任务 id 在所有 workflow 里唯一),它的每个步骤在其下面需要一个标题#### <步骤名>,拼写与步骤的name:完全一致。每个步骤都必须有name:,只运行 action 的步骤(checkout、setup)也一样。 - “任务”部分里,
###/####标题只用于任务和步骤;额外说明写在步骤下面、任务的介绍里或加粗段落里。 .github/dependabot.yml的每个 ecosystem 都要在“Dependabot”一节里用反引号提到。docs/ci.zh.md的任务和步骤标题与docs/ci.md相同(保持英文,因为它们是标识符),正文翻译。- 运行
pnpm ci:docs-check;它会打印该添加、改名或删除哪个标题。
本页同步自产品库 Joevonlong/job-radar@2947f47 的 docs/。Job Radar 与任何招聘网站均无关联。代码采用 AGPL-3.0-only 许可。