feat(scripts): PoC 阶段 1 - CLI + Layer00619 BackHall 端到端跑通

- scripts/legacy-layer-to-cocos-prefab-cli.mjs (CLI 入口: <inputXml> [<youlePrefab>] <outputDir>)
- framework-tests/legacy-layer-migration/poc-iconbutton.test.mjs (1 个 node:test 端到端校验 cc.Prefab 头 + group-55 父节点)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab (生成产物, 437 lines)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab.diff (vs YouleNexus Layer619_BackOtherGame.prefab, 1244 lines)

累计: 30/30 PASS (29 prior + 1 PoC). Layer00619 (1 Spirit, type=3 ProgressBar, group=55) 是最简 PoC, diff 大是预期 (脚本首个端到端输出 vs 手写 prefab 必然差异大).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-02 08:30:34 +08:00
co-authored by Claude Code
parent 9742509d32
commit ba5aec9bca
4 changed files with 1748 additions and 0 deletions
@@ -0,0 +1,438 @@
[
{
"__type__": "cc.Prefab",
"_name": "BackHall_Layer",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "BackHall_Layer",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 17
},
{
"__id__": 19
}
],
"_prefab": {
"__id__": 20
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "group-55",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 3
}
],
"_active": true,
"_components": [
{
"__id__": 12
},
{
"__id__": 14
}
],
"_prefab": {
"__id__": 15
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Spirit465",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 5
},
{
"__id__": 7
},
{
"__id__": 9
}
],
"_prefab": {
"__id__": 10
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 569.5,
"y": 168.5,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "Pda866NWkdQmjRO6KXJuOY"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 91,
"height": 91
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "X9Ol3V2LBqVbnRM6MhOLyz"
},
{
"__type__": "cc.ProgressBar",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "8e3905be-b629-4c4d-858b-b8a76e9f7752@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_reverse": false,
"_totalLength": 100,
"_progress": 0,
"_barSprite": null,
"_mode": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "J3KwtsMdpurmWVjh9Eh7Kp"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 8
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "chxWR5DPKUVCzLlo5XGVvA",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "MDZAGEVNBDRyYrq4pKFhLE"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1600,
"height": 720
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "jJHgeXr4FQEWQO4XIzOOHh"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "slVD8fJQaBPa4l52pf5IXq",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ZxNiZBkby9tj7SSbfpOQmU"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 16
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1600,
"height": 720
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "h4SYJyuqRHeM6Xiu5UirLi"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 18
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "ur25Y7TU0TKvsIbemih80X",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
}
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
// PoC 阶段 1 端到端: Layer00619 BackHall_Layer (1 Spirit, type=3 ProgressBar) →
// prefab JSON + (diff, if any) → 关键字段校验.
//
// 注: 原 brief 建议 IconButton, 但 IconButton 无对应 Layer; spec 改用 BackHall_Layer
// 作为最简 1-Spirit PoC. Target YouleNexus prefab 不存在时 CLI 仅生成 prefab, 不报错.
//
// 路径从 cocoscreator_projects/ 运行 (与其他 legacy-layer-migration 测试一致).
const CLI = 'node scripts/legacy-layer-to-cocos-prefab-cli.mjs';
const INPUT = '../projects/Game_Surface_3/save/Layer00619.xml';
const TARGET = 'YouleNexus/assets/framework/ui/prefabs/widgets/Layer619_BackOtherGame.prefab';
const OUTPUT_DIR = 'YouleNexus/out/legacy-migration/';
const OUTPUT = 'YouleNexus/out/legacy-migration/BackHall_Layer.prefab';
test('Layer00619 端到端生成 + 校验关键字段', () => {
execSync(`${CLI} ${INPUT} ${TARGET} ${OUTPUT_DIR}`, { stdio: 'pipe' });
assert.ok(existsSync(OUTPUT), `${OUTPUT} 应生成`);
const json = JSON.parse(readFileSync(OUTPUT, 'utf8'));
assert.ok(Array.isArray(json));
assert.equal(json[0].__type__, 'cc.Prefab');
assert.equal(json[0]._name, 'BackHall_Layer');
// 校验含 group-55 父节点 (BackHall_Layer.xml Spirit BelongGroupID=55).
const groupNode = json.find(o => o.__type__ === 'cc.Node' && o._name === 'group-55');
assert.ok(groupNode, '应有 group-55 父节点');
});
@@ -0,0 +1,35 @@
#!/usr/bin/env node
// CLI 入口: Layer XML → Cocos prefab JSON (+ 对比 diff, 如提供 YouleNexus 现存 prefab).
//
// 用法: node legacy-layer-to-cocos-prefab-cli.mjs <input.xml> [<youle-prefab>] <output-dir>
//
// 设计要点 (Task 9 brief):
// - 只输出到隔离目录 (out/legacy-migration/), 不直接覆盖 YouleNexus 现有 prefab.
// - 必填参数: inputXml + outputDir; youlePrefab 可选, 提供且文件存在时生成 .diff.
// - diff 命令来自 Git Bash / POSIX 环境 (Windows: Git for Windows 自带 diff).
import { readLayerXml, emitPrefabJson } from './legacy-layer-to-cocos-prefab.mjs';
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { join } from 'node:path';
const [, , inputXml, youlePrefab, outputDir] = process.argv;
if (!inputXml || !outputDir) {
console.error('Usage: node legacy-layer-to-cocos-prefab-cli.mjs <input.xml> <youle-prefab-path?> <output-dir>');
process.exit(1);
}
const layer = readLayerXml(inputXml);
const prefabJson = emitPrefabJson(layer);
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
const outputPath = join(outputDir, `${layer.layerName}.prefab`);
writeFileSync(outputPath, JSON.stringify(prefabJson, null, 2));
console.log(`Generated: ${outputPath}`);
if (youlePrefab && existsSync(youlePrefab)) {
const diffPath = `${outputPath}.diff`;
// `|| true` 确保 diff 找到差异时返回非零退出码不致 CLI 崩溃.
const diff = execSync(`diff -u "${youlePrefab}" "${outputPath}" || true`, { encoding: 'utf8' });
writeFileSync(diffPath, diff);
console.log(`Diff: ${diffPath} (${diff.split('\n').length} lines)`);
}