Skip to content

Declare what each frame projection reads and returns, and fix two cop… #88

Declare what each frame projection reads and returns, and fix two cop…

Declare what each frame projection reads and returns, and fix two cop… #88

Workflow file for this run

# 每次 push / PR 跑一遍仓库自己声明的验证。
#
# 三件事按「快 → 慢」排:格式检查不需要装依赖之外的东西,
# typecheck 和 test 依赖 devDependencies,所以 npm install 只做一次。
#
# **不跑真实后端。** 判定后端和生成后端都需要 key 或本地模型,
# CI 里没有 —— 用 `--rule` + 空 DEEPSEEK_API_KEY 走离线路径,
# 它覆盖的是「loop 能不能跑完」,不是「判定准不准」。
name: CI
on:
push:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# 类型剥离 v22.6 引入、v22.18 才默认开启。
# 命令里显式带 --experimental-strip-types,所以 22.6 起都能跑。
node-version: '22.18'
- run: npm install
- run: npm run check
- run: npm run typecheck
- run: npm test
# `npm test` 里已经有 `tests/conformance.test.ts`(它跑那 15 个突变、4 个阴性
# 对照,并断言锚点恰好命中一次),所以这一条**不是**唯一一道防线。
# 单独跑一次的另一个理由:给人看的那张表在 CI 日志里能直接读,
# 而测试失败信息里只有断言。
- run: npm run conformance
# ★ README 让人跑的第一条适配命令。加这一步之前,**没有任何东西跑过它**:
# 既不在 CI 里,也没有测试调它 —— 它坏掉不会有任何人知道,直到有人照着
# README 敲下那一行。正是 §8.16 记的「声明了却没有消费方」。
# 现在它同时被 `tests/external-host.test.ts` 和这一步覆盖。
- run: npm run external-host
# 为什么需要这一步:`package.json` 的 `exports` 指向 `dist/`,
# 而上面每一步(check / typecheck / test,以及下面的 demo)走的都是 `src/`。
# 也就是说 —— **使用者唯一会拿到的那一面,恰好是仓库里唯一没人验证的一面。**
# `dist/` 在 `.gitignore` 里,所以它过期了不会有任何东西报警。
# 实测:一次源码分层重构之后,`dist/` 少了 7 个模块、多了 1 个孤儿(`types.js`),
# 而当时的 check / typecheck / test / demo **全部照常全绿**。
#
# 比的是**运行时导出面**而不是文件名,所以 `src/` 将来拆成目录也不会误报。
#
# ★ **每一个入口都要比**。加 `jevloop/contract` 时如果只比包根,这个新入口
# 就会原样重演上面那段理由 —— 使用者会拿到的第二个面没人验证。
# 少列一个入口 = 少验证一个面,所以入口清单写在这里,加一个就要加一行。
- name: dist/ 与 src/ 的导出面必须一致(每个入口)
run: |
npm run build
node --input-type=module -e '
const load = async (p) => {
try { return Object.keys(await import(p)).sort() }
catch (e) { console.error(` 无法加载 ${p}:${e.message}`); process.exit(1) }
}
const ENTRIES = [
["./dist/index.js", "./src/index.ts"],
["./dist/contract.js", "./src/contract.ts"],
]
let bad = false
for (const [d, s] of ENTRIES) {
const a = await load(d)
const b = await load(s)
const onlyA = a.filter((x) => !b.includes(x))
const onlyB = b.filter((x) => !a.includes(x))
if (onlyA.length || onlyB.length) {
console.error(` ${d} 与 ${s} 的导出面不一致 —— 构建产物已过期,或有模块没被编译进去`)
if (onlyA.length) console.error(" 仅 dist 有:", onlyA.join(", "))
if (onlyB.length) console.error(" 仅 src 有:", onlyB.join(", "))
bad = true
continue
}
console.log(` ${s} 与 ${d} 一致(${a.length} 个运行时导出)`)
}
if (bad) process.exit(1)
'
- name: 离线冒烟(loop 能跑完)
run: DEEPSEEK_API_KEY= npm run demo -- --rule