fix(skin): 修复最终评审发现的 6 处缺陷(junction 排除失效/meta 静默丢失等)

跨任务视角发现的问题:Critical1 materialize.mjs 的 junction 排除比较在
Windows 上因长路径前缀恒假、从未生效,且测试夹具的 junction 指向真源自身,
掩盖了此问题;Critical2 composeSkin 例外通道在 uuid 缺失/框架侧无 meta 时
静默失败,违反第二准则;Important3 可覆盖范围误把 theme/ 下的框架 TS 代码
也纳入;Important5 cli-entry 测试漏注册 check-skin/build-game;Important6
composeSkin 与 check-skin 各自拼路径,未共用 paths.mjs 权威推导;Important7
--platform 缺值时静默落到默认平台。逐条修复并补测试,Critical1 用诱饵框架
目录验证过能真正杀掉该 bug(变异推演见 fix report)。

spec §5 step5 的 dist/<name>/ 拷贝按要求标注为本期未实现,不实现。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 23:15:28 +08:00
co-authored by Claude Opus 5
parent 88deab3533
commit 5708926711
12 changed files with 209 additions and 24 deletions
@@ -54,13 +54,20 @@ function scaffold(gameVersion = '3.8.8') {
writeFileSync(join(hostProject, 'package.json'), JSON.stringify({ name: 'YouleNexus', creator: { version: '3.8.8' } }));
put(frameworkSrc, 'ui/atlas-hall/btn.png', DEFAULT_BTN);
// junction 故意指向一个诱饵目录而非 frameworkSrc 自身,与 materialize.test.mjs 同理:
// 否则"经 junction 拷来的字节"与"从真源拷来的字节"完全相同,测不出 Critical 1 那类 bug。
const frameworkDecoy = join(root, 'decoy', 'assets', 'framework');
mkdirSync(frameworkDecoy, { recursive: true });
put(frameworkDecoy, 'ui/atlas-hall/btn.png', DEFAULT_BTN);
put(frameworkDecoy, 'ui/atlas-hall/__decoy_marker__.png', DEFAULT_BTN);
const gamesDir = join(root, 'games');
const gameDir = join(gamesDir, 'demo');
mkdirSync(join(gameDir, 'assets'), { recursive: true });
writeFileSync(join(gameDir, 'package.json'), JSON.stringify({ name: 'demo', creator: { version: gameVersion } }));
symlinkSync(frameworkSrc, join(gameDir, 'assets', 'framework'), LINK_TYPE);
symlinkSync(frameworkDecoy, join(gameDir, 'assets', 'framework'), LINK_TYPE);
return { root, hostProject, frameworkSrc, gamesDir, gameDir, workspace: join(root, 'build-workspace') };
return { root, hostProject, frameworkSrc, frameworkDecoy, gamesDir, gameDir, workspace: join(root, 'build-workspace') };
}
function cleanup(root) { rmSync(root, { recursive: true, force: true }); }
@@ -83,6 +90,8 @@ test('buildGame 走通全流程:实体化 → 合成 → 调构建 → 写 bui
assert.ok(readFileSync(join(r.workspaceDir, 'assets', 'framework', 'ui', 'atlas-hall', 'btn.png')).equals(SKINNED));
assert.ok(readFileSync(join(s.frameworkSrc, 'ui', 'atlas-hall', 'btn.png')).equals(DEFAULT_BTN), '框架真源必须只读:合成只能发生在临时工作区');
assert.ok(!existsSync(join(r.workspaceDir, 'assets', 'framework', 'ui', 'atlas-hall', '__decoy_marker__.png')),
'framework 必须来自 frameworkSrc 真源,不能残留 junction 诱饵目标独有的文件(Critical 1)');
assert.equal(calls.length, 1);
assert.equal(calls[0][0], r.workspaceDir);
assert.equal(calls[0][1], 'web-mobile');