Files
SpineWallpaper/tools/check-syntax.ts
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

74 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFile } from "node:fs/promises";
import { abs, log, walkFiles } from "./lib/fs.ts";
import { VaultError } from "./lib/vault.ts";
// pnpm check:syntax —— 把"源码必须 strip-safe"从约定升级成门禁。
//
// Node 26 能直接跑 .ts,但它只做**类型擦除**:enum、构造函数参数属性、namespace 会以
// ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX 崩掉(已实测)。这类语法在 tsc 下完全合法,所以只有真实加载
// 才发现得了——而那通常是 `pnpm dev` 已经跑起来之后。这里做一次静态扫描,提前挡在门口。
//
// 刻意**不做**动态 import 验证:那些入口脚本一被执行就有副作用(起服务、写 dist)。
const BANNED: { pattern: RegExp; what: string; why: string }[] = [
{
pattern: /^[ \t]*(?:export[ \t]+)?(?:const[ \t]+)?enum[ \t]/m,
what: "enum",
why: "类型擦除模式下没有运行时实现,Node 直接报错",
},
{
pattern: /^[ \t]*(?:export[ \t]+)?(?:declare[ \t]+)?namespace[ \t]/m,
what: "namespace",
why: "同上,且它是过时的模块写法",
},
{
pattern: /constructor[ \t]*\([^)]*\b(?:public|private|protected|readonly)[ \t]+\w+/s,
what: "构造函数参数属性(constructor(public x: T))",
why: "需要编译器注入赋值语句,擦除模式做不到",
},
{ pattern: /\bimport[ \t]+\w+[ \t]*=[ \t]*require[ \t]*\(/, what: "import x = require()", why: "TS 特有语法,非 ESM" },
];
/** 扫描范围:所有会被 Node 直接执行的 .ts(运行时脚本经 tsc 编译,不受此限制)。 */
async function sourceFiles(): Promise<string[]> {
const files: string[] = [];
for (const dir of ["tools", "src/simulator"]) {
for (const file of await walkFiles(abs(dir))) {
if (file.endsWith(".ts")) files.push(`${dir}/${file}`);
}
}
return files.sort();
}
/**
* 先剥掉注释与字符串/模板字面量再匹配。
*
* 这不是洁癖:本文件自己就用正则写着这些被禁语法的样子,若直接扫全文会自我举报。
* 同理,任何文档或消息文本里出现 "enum " 也不该被当成代码。
*/
function stripCommentsAndLiterals(text: string): string {
return text
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/\/\/[^\n]*/g, "")
.replace(/`(?:\\[\s\S]|[^`\\])*`/g, '""')
.replace(/'(?:\\[\s\S]|[^'\\\n])*'/g, '""')
.replace(/"(?:\\[\s\S]|[^"\\\n])*"/g, '""');
}
const problems: string[] = [];
for (const relPath of await sourceFiles()) {
const text = stripCommentsAndLiterals(await readFile(abs(relPath), "utf8"));
for (const { pattern, what, why } of BANNED) {
const match = pattern.exec(text);
if (!match) continue;
problems.push(`${relPath} 用了 ${what} —— ${why}`);
}
}
if (problems.length > 0) {
log("check:syntax ✗");
for (const problem of problems) log(` ✗ ${problem}`);
throw new VaultError(`源码里有 ${problems.length} 处 Node 类型擦除不支持的语法`);
}
log("check:syntax ✓ 所有会被 Node 直接执行的 .ts 都是 strip-safe");