Files
SpineWallpaper/tools/checks/smoke-dev.mts
T
Shuery 3f11426964 feat: TS 构建管线、Spine 抓取器与 wallpapers/ 唯一真相来源
把项目从「手写 dist/」改成「wallpapers/ 是唯一真相来源,dist/ 由 pnpm build 生成」,
并补上配套的类型、门禁与抓取器。一次提交落地整条管线,因为拆开会留下不能构建的中间态。

- src/:运行时与模拟器源码(TS,strict),编译到 build/ 再拷进各分发
- tools/:build / dev / check-{syntax,paths,dist},以及抓取器与回归门禁 tools/checks/
  (.scratch/ 下那批一次性脚本移入 tools/checks/ 并入库为长期门禁)
- wallpapers/:七档壁纸的源数据 + README.md(id/音频/预设的完整规范)
- docs/adr/0005-0008:构建管线与分发拓扑、模拟器契约、自包含 sim、每骨架资源布局
- .gitignore:排除 .scratch/ 的参考资料副本(上游 spine 整仓克隆 ~1.2 GB、
  抓取侦查数据 ~680 MB)与调试转储;这些是本地调查材料,补偿会让仓库无法克隆
- 归一化 .gitignore/CONTEXT.md 行尾(工作区 CRLF、索引 LF 造成的整文件假 diff)

同时修掉三档卡住构建的未完工壁纸:
- kv45 的 meta.json 里 id 还是抓取期场景名 scene_main,经 downloader promote 正名为 kv45
- shajin / zhigengniao_juheye 的 meta.json 误用了骨架描述文件(name/spine/animations/pages)、
  且都缺 preset.template.json;现按规范重建:骨架沉到 spines/<名>/(spine-ts 按 atlas 所在
  目录解析贴图页)、补上元数据与单骨架预设,并清掉 zhigengniao 骨架里指向作者机的绝对路径
- 顺带 promote 已在 sources.yml 里的 kv46(月升之前,与兽共舞)

pnpm check 五道门全绿:10 个分发 / 7 档壁纸 / 141 处引用自包含。
2026-10-02 01:27:02 +08:00

73 lines
3.6 KiB
TypeScript

// 调试服冒烟:验证路由、注入、以及"所有分发内引用都能取到 200"。
// 用法:node tools/checks/smoke-dev.mts [port]
//
// 这里有一条**回归断言**:模拟器本体必须能从调试服取到 200。
// 它曾经是 404 —— 驱动脚本从 `/release/<dir>/scripts/wallpaper-engine.js` 取模拟器,
// 而该文件只有 `pnpm build --with-sim` 才会放进分发,默认构建下必然缺失。
// 症状是浏览器里"WE 模拟面板没出来",而 404 只留在控制台里。
// 所以断言必须打在**模拟器本体的可获取性**上,不能只断言"HTML 里提到了这个字符串"。
const port = Number(process.argv[2] ?? 5173);
const base = `http://127.0.0.1:${port}`;
const SIMULATOR_URL = "/simulator/wallpaper-engine.js";
let failures = 0;
function ok(condition: boolean, label: string, detail = ""): void {
if (condition) {
console.log(` ✓ ${label}`);
} else {
failures += 1;
console.log(` ✗ ${label}${detail ? ` —— ${detail}` : ""}`);
}
}
const indexRes = await fetch(`${base}/`);
const indexHtml = await indexRes.text();
console.log(`索引页 ${indexRes.status}`);
ok(indexRes.status === 200, "GET / 返回 200");
ok(indexHtml.includes("single-hsr-xilian") && indexHtml.includes("collection-all"), "索引页列出全部分发");
const map = await (await fetch(`${base}/dist-map.json`).catch(() => ({ status: 0, text: async () => "" }))).text?.();
void map;
for (const dir of ["single-hsr-xilian", "single-hsr-kv37", "collection-hsr", "collection-all"]) {
console.log(`\n${dir}`);
const res = await fetch(`${base}/release/${dir}/`);
const html = await res.text();
ok(res.status === 200, "GET /release/<dir>/ 返回 200");
ok(html.includes("wallpaper-engine.js"), "已注入模拟器脚本"); ok(html.includes("__weProperties"), "已注入 project.json 属性表");
ok(html.includes("mountWallpaperEngineSimulator"), "已注入模拟器驱动");
ok(html.indexOf("wallpaper-engine.js") < html.indexOf("scripts/index.js"), "模拟器在壁纸模块之前");
// 模拟器脚本本体可访问(**回归断言**:曾经 404,导致面板静默消失)
const simRes = await fetch(`${base}${SIMULATOR_URL}`);
ok(simRes.status === 200, `模拟器本体 ${SIMULATOR_URL} 返回 200`, `status=${simRes.status}`);
const simText = simRes.status === 200 ? await simRes.text() : "";
ok(
simText.includes("mountWallpaperEngineSimulator"),
"模拟器本体确实导出了 mountWallpaperEngineSimulator",
`${(simText.length / 1024).toFixed(0)} KB`,
);
// ?nojs=1 剥脚本
const nojs = await (await fetch(`${base}/release/${dir}/?nojs=1`)).text();
ok(!nojs.includes("scripts/index.js") && nojs.includes("TESTSTATE"), "?nojs=1 剥掉脚本并留状态标记", nojs.slice(0, 80));
// ?sim=0 不注入模拟器
const nosim = await (await fetch(`${base}/release/${dir}/?sim=0`)).text();
ok(!nosim.includes('type="module" src="/release/'), "?sim=0 不注入模拟器 script 标签");
// 越界引用必须 404(目录自包含性)
const escape = await fetch(`${base}/release/${dir}/../${dir}/index.html`);
ok(escape.status === 200 || escape.status === 404, "越界路径被规范化(不崩溃)");
}
console.log("\n越权路由");
const other = await fetch(`${base}/release/not-a-release/`);
ok(other.status === 404, "未知分发返回 404");
const stray = await fetch(`${base}/whatever.txt`);
ok(stray.status === 404, "非 /release 路由返回 404");
console.log(failures === 0 ? "\n调试服冒烟通过" : `\n调试服冒烟失败:${failures} 处`);
process.exit(failures === 0 ? 0 : 1);