refactor: fix-borders 升级改名为 fix-meta,新增 trim 破坏识别

- 改名 fix-borders → fix-meta(CLI 子命令 fix-meta;MCP tool meta_fix),名字更通用
- 新增第三类噪音识别:trimType 从 none 被 Cocos 改成自动裁剪(连带 width/height/
  offset/顶点全变)→ 还原该 frame 到 git(恢复不裁剪)。保守:只还原 git=none 的
  明确破坏,不碰 git 本就 auto 的(分不清破坏 vs 真改)
- 三类统一处理:① 纯顺序/格式噪音 ② 九宫格 border 重置 ③ trim 强改

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
furao
2026-06-07 19:00:47 +08:00
parent 7ae2f994cf
commit f54f0762ef
5 changed files with 66 additions and 49 deletions
@@ -1,22 +1,23 @@
// ============================================================
// cli/fix-borders-cmd.js — fix-borders 子命令
// cli/fix-meta-cmd.js — fix-meta 子命令
//
// 用法:
// cocos-mcp-cli fix-borders [--project <dir>] [--dry-run]
// cocos-mcp-cli fix-meta [--project <dir>] [--dry-run]
//
// 修复 Cocos 重启把图片九宫格 bordersubMetas.*.userData.border*)重置成 0 的 meta
// 扫 git 改动的 .meta,把「git 里有值、工作区被清 0」的 border 还原成 git 的值。
// 只动 border 字段、保留 meta 其它改动。
// 清理 Cocos 重启造成的图片 meta 噪音(保守,只还原能确认是破坏的)
// 1) 纯 key 顺序/格式变化(值没变)→ 还原成 git 原文
// 2) 九宫格 border 被重置成 0 → 用 git 的值精准还原
// 3) trimType 从 none 被改成自动裁剪 → 还原该 frame 到 git(恢复不裁剪)
// --dry-run 只预览不写;--project 指定项目路径(默认当前目录)。
// ============================================================
'use strict';
const { fixResetBorders } = require('../editor/fix-borders.js');
const { fixMeta } = require('../editor/fix-meta.js');
function die(msg) { process.stderr.write('Error: ' + msg + '\n'); process.exit(1); }
function cmdFixBorders(argv) {
function cmdFixMeta(argv) {
let project = process.cwd();
let dryRun = false;
@@ -28,12 +29,13 @@ function cmdFixBorders(argv) {
project = argv[++i];
} else if (a === '--help' || a === '-h') {
process.stdout.write(
'cocos-mcp-cli fix-borders — 清理 Cocos 重启造成的图片 meta 噪音\n\n' +
'cocos-mcp-cli fix-meta — 清理 Cocos 重启造成的图片 meta 噪音\n\n' +
' --project, -p <dir> 项目路径(git 仓库或其子目录,默认当前目录)\n' +
' --dry-run 只预览将还原哪些,不写文件\n\n' +
'处理两类噪音(都靠 git 对比,正确处理中文路径):\n' +
'处理三类(都靠 git 对比,正确处理中文路径,保守只还原能确认的):\n' +
' 1) 纯 key 顺序/格式变化(值没变)→ 还原成 git 原文\n' +
' 2) 九宫格 border 被重置成 0 → 用 git 的值精准还原\n');
' 2) 九宫格 border 被重置成 0 → 用 git 的值精准还原\n' +
' 3) trimType 从 none 被改成自动裁剪 → 还原该 frame 到 git(恢复不裁剪)\n');
return;
} else {
die('未知参数 "' + a + '"');
@@ -42,20 +44,20 @@ function cmdFixBorders(argv) {
let r;
try {
r = fixResetBorders(project, { dryRun: dryRun });
r = fixMeta(project, { dryRun: dryRun });
} catch (e) {
die(e.message);
}
process.stdout.write(
(dryRun ? '[dry-run] ' : '') +
'扫描 ' + r.scanned + ' 个改动 meta:顺序噪音还原 ' + r.reorderFiles +
' 个,border 还原 ' + r.borderFiles + ' 个 meta / ' + r.borderFrames + ' frame\n');
'扫描 ' + r.scanned + ' 个改动 meta:顺序还原 ' + r.reorderFiles +
' 个,值修复 ' + r.fixedFiles + ' 个 metaborder ' + r.borderFrames + ' frame / trim ' + r.trimFrames + ' frame\n');
if (r.details.length) {
process.stdout.write(r.details.slice(0, 50).join('\n') + '\n');
if (r.details.length > 50) process.stdout.write('...(共 ' + r.details.length + ' 处,此处只列前 50\n');
if (r.details.length > 50) process.stdout.write('...(共 ' + r.details.length + ' 处,只列前 50\n');
}
}
module.exports = { cmdFixBorders };
module.exports = { cmdFixMeta };
+4 -4
View File
@@ -20,7 +20,7 @@ const { cmdExtractPrefab } = require('./extract-cmd.js');
const { cmdCompactPrefab } = require('./compact-cmd.js');
const { cmdEnsureMeta } = require('./ensure-meta-cmd.js');
const { cmdBuild } = require('./build-cmd.js');
const { cmdFixBorders } = require('./fix-borders-cmd.js');
const { cmdFixMeta } = require('./fix-meta-cmd.js');
function die(msg) {
process.stderr.write('Error: ' + msg + '\n');
@@ -56,10 +56,10 @@ function main(argv) {
cmdEnsureMeta(rest);
} else if (cmd === 'build') {
cmdBuild(rest);
} else if (cmd === 'fix-borders') {
cmdFixBorders(rest);
} else if (cmd === 'fix-meta') {
cmdFixMeta(rest);
} else {
die(`未知子命令 "${cmd}",可用: query / set / batch / anim / diff / create-prefab / extract-prefab / compact-prefab / ensure-meta / build / fix-borders`);
die(`未知子命令 "${cmd}",可用: query / set / batch / anim / diff / create-prefab / extract-prefab / compact-prefab / ensure-meta / build / fix-meta`);
}
}
@@ -1,20 +1,26 @@
// ============================================================
// editor/fix-borders.js — 清理 Cocos 重启/重新导入造成的图片 meta 噪音
// editor/fix-meta.js — 清理 Cocos 重启/重新导入造成的图片 meta 噪音
//
// 现象(Cocos 重启/重新导入有时发生)
// 1) 纯 key 顺序/格式变化:如 userData 里 trimType/atlasUuid 等字段被重排位置,
// Cocos 重启/重新导入有时会改坏图片 .meta,常见三类
// 1) 纯 key 顺序/格式变化:如 userData 里 trimType/atlasUuid 等被重排位置,
// 值没变,只产生 git diff 噪音。
// 2) 九宫格 border 被重置:subMetas.<hash>.userData.{borderTop,Bottom,Left,Right}
// 被清成 0(九宫格丢失,数据真丢)。
// 被清成 0(九宫格丢失)。
// 3) trim 裁剪被强改:用户设 trimType="none"(不裁剪)的图,被改成 "auto"(自动裁剪),
// 连带 width/height/offset/trimX-Y/顶点全变。
//
// 修复:扫 git 工作区改动的 .meta,逐个对比 git 版本:
// - 值深度相等(只 key 顺序/格式不同)→ 直接还原成 git 原文,消噪音;
// - 值不等但只是九宫格 border 被重置成 0 → 用 git 的值精准还原 border
// 还原后若整体已等于 git(差异仅 border + 顺序)也直接写 git 原文,否则只改 border
// 字段、保留 meta 其它真实改动。
// 修复(保守,只还原能确认是 Cocos 破坏的,不碰分不清的):扫 git 工作区改动的 .meta,
// 逐个对比 git 版本:
// - 值深度相等(只 key 顺序/格式不同)→ 还原成 git 原文
// - border 被重置成 0(git 有值)→ 用 git 的值精准还原 border
// - trimType 从 none 被改成裁剪 → 还原该 frame 的 userData 到 git(恢复不裁剪);
// - 还原后若整体已等于 git → 直接写 git 原文,否则只改动过的字段、保留 meta 其它真实改动。
//
// 不处理:git 本来就 trimType="auto" 的图(auto→auto 值变分不清破坏 vs 真改)、
// 以及其它无明确破坏特征的值变化。
//
// 靠 git 对比拿正确值;git 调用统一带 core.quotePath=false,正确处理中文路径。
// 纯 Node + git,跨平台。供 CLIfix-borders-cmd)和 MCP tool 共用。
// 纯 Node + git,跨平台。供 CLIfix-meta-cmd)和 MCP tool 共用。
// ============================================================
'use strict';
@@ -39,14 +45,15 @@ function norm(o) {
return o;
}
function valueEqual(a, b) { return JSON.stringify(norm(a)) === JSON.stringify(norm(b)); }
function deepClone(o) { return JSON.parse(JSON.stringify(o)); }
/**
* 清理被 Cocos 重启搞 .meta顺序噪音 + 九宫格 border 重置
* 清理被 Cocos 重启搞 .meta顺序噪音 / 九宫格 border 重置 / trim 被强改
* @param {string} projectOrRoot 项目路径git 仓库或其子目录均可
* @param {{dryRun?:boolean}} [opts]
* @returns {{gitRoot, scanned, reorderFiles, borderFiles, borderFrames, details:string[]}}
* @returns {{gitRoot, scanned, reorderFiles, fixedFiles, borderFrames, trimFrames, details:string[]}}
*/
function fixResetBorders(projectOrRoot, opts) {
function fixMeta(projectOrRoot, opts) {
opts = opts || {};
var dryRun = !!opts.dryRun;
@@ -63,7 +70,7 @@ function fixResetBorders(projectOrRoot, opts) {
var changed = git(gitRoot, ['diff', '--name-only', '--', '*.meta'])
.split('\n').map(function (s) { return s.trim(); }).filter(Boolean);
var reorderFiles = 0, borderFiles = 0, borderFrames = 0;
var reorderFiles = 0, fixedFiles = 0, borderFrames = 0, trimFrames = 0;
var details = [];
for (var i = 0; i < changed.length; i++) {
@@ -83,7 +90,7 @@ function fixResetBorders(projectOrRoot, opts) {
continue;
}
// 2) 值不等 → 检查九宫格 border 是否被重置成 0,精准还原
// 2) 值不等 → 逐 frame 识别 trim 破坏 / border 重置
var wSubs = work && work.subMetas;
var hSubs = head && head.subMetas;
if (!wSubs || !hSubs || typeof wSubs !== 'object' || typeof hSubs !== 'object') continue;
@@ -95,21 +102,28 @@ function fixResetBorders(projectOrRoot, opts) {
var wud = wSubs[key] && wSubs[key].userData;
var hud = hSubs[key] && hSubs[key].userData;
if (!wud || !hud) continue;
if (!BORDER_KEYS.some(function (b) { return b in wud; })) continue;
if (allZero(wud) && anyNonZero(hud)) {
// trim 破坏:git trimType=none、工作区被改成裁剪(none→非none)→ 还原整个 frame userData
if (hud.trimType === 'none' && wud.trimType && wud.trimType !== 'none') {
wSubs[key].userData = deepClone(hud);
metaChanged = true; trimFrames++;
details.push('[trim] ' + rel + ' [' + key + '] ' + wud.trimType + '→none(还原不裁剪)');
continue;
}
// border 重置:工作区 border 全 0、git 有非 0 → 精准还原 border 四字段
if (BORDER_KEYS.some(function (b) { return b in wud; }) && allZero(wud) && anyNonZero(hud)) {
for (var b = 0; b < BORDER_KEYS.length; b++) wud[BORDER_KEYS[b]] = hud[BORDER_KEYS[b]];
metaChanged = true;
borderFrames++;
metaChanged = true; borderFrames++;
details.push('[border] ' + rel + ' [' + key + '] → T' + hud.borderTop + '/B' + hud.borderBottom +
'/L' + hud.borderLeft + '/R' + hud.borderRight);
}
}
if (metaChanged) {
borderFiles++;
fixedFiles++;
if (!dryRun) {
// 还原 border 后若整体已等于 git(差异仅 border + 顺序)→ 直接写 git 原文,最干净;
// 否则只改了 border 字段、保留其它真实改动 → 按 work 写回(Cocos 标准格式)
// 还原后若整体已等于 git → 直接写 git 原文(连带消顺序噪音);否则按 work 写回、保留其它真实改动
if (valueEqual(work, head)) fs.writeFileSync(abs, headStr, 'utf8');
else fs.writeFileSync(abs, JSON.stringify(work, null, 2) + '\n', 'utf8');
}
@@ -118,9 +132,10 @@ function fixResetBorders(projectOrRoot, opts) {
return {
gitRoot: gitRoot, scanned: changed.length,
reorderFiles: reorderFiles, borderFiles: borderFiles, borderFrames: borderFrames,
reorderFiles: reorderFiles, fixedFiles: fixedFiles,
borderFrames: borderFrames, trimFrames: trimFrames,
details: details,
};
}
module.exports = { fixResetBorders: fixResetBorders };
module.exports = { fixMeta: fixMeta };