portfolio-site/build.mjs(132 行)+ verify.mjs(234 行)+ contracts.test.mjs(178 行)原子发布(build.mjs 第 79-110 行):
const stage = path.join(..., `.${path.basename(output)}.staging-${randomUUID()}`);
...
await mkdir(stage);
for (const file of [...staticFiles, ...images]) { /* 复制到 staging */ }
await writeFile(path.join(stage, "index.html"), html);
// ...写 projects.js / hanzi-data.js / portfolio.v1.json,算 sha256 写 manifest
if (await exists(output)) {
backup = path.join(..., `.${path.basename(output)}.previous-${randomUUID()}`);
await rename(output, backup); // 旧版先改名为备份
}
try { await rename(stage, output); } // staging 原子上位
catch (error) { if (backup) await rename(backup, output); throw error; } // 失败回滚
构建锁 + 边界校验(第 22-28、75-78 行):
export async function validateOutput(source, output) {
// outputs 必须在工作区内;输出必须是 outputs 内的独立子目录;
// 输出不能与源码重叠;输出路径不能含符号链接/目录联接
}
const lockPath = path.join(..., `.${path.basename(output)}.build.lock`);
try { lock = await open(lockPath, "wx"); } // wx = 排他创建
catch (error) { if (error.code === "EEXIST") throw new Error("构建锁已存在……"); }
跨项目契约测试(contracts.test.mjs 第 90-106 行):验证 core.adaptCourseIndex
能正确读取 course-publisher 生成的 index.json——兼容旧格式、拒绝未知版本、
"没生成 HTML 就不伪造入口"。
真浏览器验收(verify.mjs):校验 manifest 的 sha256 → 起本地 HTTP 并注入不同数据夹具
→ spawn headless Edge(--remote-debugging-port=0)→ 用内置 WebSocket 走 CDP 执行断言,
最后断言未捕获异常数为 0;覆盖 file://、缓存自愈、响应式、XSS 等约 40 个检查点。
open(..., "wx") 防并发构建realpath + inside() 防止覆盖源码/输出到系统目录verify.mjs 先核对再验收contracts.test.mjs(约 60 用例)+ verify.mjs(约 40 检查点)safe-publish(npm):publish({ source, output, validate }) 原子发布cdp-check:一组基于 CDP 的页面验收断言工具rsync --delay-updates 也是思路第 19 课(零依赖构建与交付验收)