feat: fix-borders 升级为通用 meta 噪音清理 + 修中文路径 bug

- 新增「纯 key 顺序/格式变化(值没变)」检测:还原成 git 原文,消 git diff 噪音
  (Cocos 重启会重排 userData 里 trimType/atlasUuid 等字段位置)
- 修中文路径 bug:git 调用统一加 core.quotePath=false;之前中文名图(如 1公共资源/)
  的 border meta 被 git 引号转义跳过、漏还原,现已覆盖
- border 还原后若整体已等于 git 则直接写 git 原文(连带消顺序噪音)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
furao
2026-06-07 18:45:24 +08:00
parent fc93911d31
commit 7ae2f994cf
3 changed files with 68 additions and 39 deletions
+6 -4
View File
@@ -28,10 +28,12 @@ function cmdFixBorders(argv) {
project = argv[++i]; project = argv[++i];
} else if (a === '--help' || a === '-h') { } else if (a === '--help' || a === '-h') {
process.stdout.write( process.stdout.write(
'cocos-mcp-cli fix-borders — 修复 Cocos 重启把九宫格 border 重置成 0 的 meta\n\n' + 'cocos-mcp-cli fix-borders — 清理 Cocos 重启造成的图片 meta 噪音\n\n' +
' --project, -p <dir> 项目路径(git 仓库或其子目录,默认当前目录)\n' + ' --project, -p <dir> 项目路径(git 仓库或其子目录,默认当前目录)\n' +
' --dry-run 只预览将还原哪些,不写文件\n\n' + ' --dry-run 只预览将还原哪些,不写文件\n\n' +
'原理:扫 git 改动的 .meta,把「git 有值、工作区被清 0」的 border 还原成 git 的值。\n'); '处理两类噪音(都靠 git 对比,正确处理中文路径):\n' +
' 1) 纯 key 顺序/格式变化(值没变)→ 还原成 git 原文\n' +
' 2) 九宫格 border 被重置成 0 → 用 git 的值精准还原\n');
return; return;
} else { } else {
die('未知参数 "' + a + '"'); die('未知参数 "' + a + '"');
@@ -47,8 +49,8 @@ function cmdFixBorders(argv) {
process.stdout.write( process.stdout.write(
(dryRun ? '[dry-run] ' : '') + (dryRun ? '[dry-run] ' : '') +
'扫描 ' + r.scanned + ' 个改动 meta,还原 border' + '扫描 ' + r.scanned + ' 个改动 meta:顺序噪音还原 ' + r.reorderFiles +
r.fixedFiles + ' 个 meta / ' + r.fixedFrames + ' 个 sprite-frame\n'); ' 个,border 还原 ' + r.borderFiles + ' 个 meta / ' + r.borderFrames + ' frame\n');
if (r.details.length) { if (r.details.length) {
process.stdout.write(r.details.slice(0, 50).join('\n') + '\n'); process.stdout.write(r.details.slice(0, 50).join('\n') + '\n');
+61 -34
View File
@@ -1,14 +1,19 @@
// ============================================================ // ============================================================
// editor/fix-borders.js — 修复 Cocos 重启把图片九宫格 border 重置成 0 的 meta // editor/fix-borders.js — 清理 Cocos 重启/重新导入造成的图片 meta 噪音
// //
// 现象Cocos 重启/重新导入有时把已设九宫格的图 meta 里 // 现象Cocos 重启/重新导入有时发生):
// subMetas.<hash>.userData.{borderTop,borderBottom,borderLeft,borderRight} // 1) 纯 key 顺序/格式变化:如 userData 里 trimType/atlasUuid 等字段被重排位置,
// 重置成 0(九宫格丢失) // 值没变,只产生 git diff 噪音
// 2) 九宫格 border 被重置:subMetas.<hash>.userData.{borderTop,Bottom,Left,Right}
// 被清成 0(九宫格丢失,数据真丢)。
// //
// 修复:扫 git 工作区改动的 .meta,找出「git 版本 border 非 0、工作区被重置成 0」的, // 修复:扫 git 工作区改动的 .meta,逐个对比 git 版本
// 把 git 的 border 值还原回去。靠 git 对比拿正确值,只动 border 四个字段、 // - 值深度相等(只 key 顺序/格式不同)→ 直接还原成 git 原文,消噪音;
// 保留 meta 其它内容;写回用 Cocos 标准格式(2 空格缩进 + 尾随换行)。 // - 值不等但只是九宫格 border 被重置成 0 → 用 git 的值精准还原 border
// 还原后若整体已等于 git(差异仅 border + 顺序)也直接写 git 原文,否则只改 border
// 字段、保留 meta 其它真实改动。
// //
// 靠 git 对比拿正确值;git 调用统一带 core.quotePath=false,正确处理中文路径。
// 纯 Node + git,跨平台。供 CLIfix-borders-cmd)和 MCP tool 共用。 // 纯 Node + git,跨平台。供 CLIfix-borders-cmd)和 MCP tool 共用。
// ============================================================ // ============================================================
@@ -20,39 +25,45 @@ const cp = require('child_process');
const BORDER_KEYS = ['borderTop', 'borderBottom', 'borderLeft', 'borderRight']; const BORDER_KEYS = ['borderTop', 'borderBottom', 'borderLeft', 'borderRight'];
function git(root, args) {
return cp.execFileSync('git', ['-C', root].concat(args), {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
});
}
// 四个 border 都为 0 / 缺失(被重置的特征)
function allZero(ud) { return BORDER_KEYS.every(function (b) { return !ud[b]; }); } function allZero(ud) { return BORDER_KEYS.every(function (b) { return !ud[b]; }); }
// 至少一个 border 非 0(git 里有正确九宫格值的特征)
function anyNonZero(ud) { return BORDER_KEYS.some(function (b) { return !!ud[b]; }); } function anyNonZero(ud) { return BORDER_KEYS.some(function (b) { return !!ud[b]; }); }
// 深度规范化(递归排序对象 key),用于「忽略 key 顺序」的值比较
function norm(o) {
if (Array.isArray(o)) return o.map(norm);
if (o && typeof o === 'object') {
var r = {};
Object.keys(o).sort().forEach(function (k) { r[k] = norm(o[k]); });
return r;
}
return o;
}
function valueEqual(a, b) { return JSON.stringify(norm(a)) === JSON.stringify(norm(b)); }
/** /**
* 扫 git 改动的 .meta,还原被 Cocos 重置成 0 的九宫格 border。 * 清理被 Cocos 重启搞乱的 .meta(顺序噪音 + 九宫格 border 重置)
* @param {string} projectOrRoot 项目路径(git 仓库或其子目录均可) * @param {string} projectOrRoot 项目路径(git 仓库或其子目录均可)
* @param {{dryRun?:boolean}} [opts] * @param {{dryRun?:boolean}} [opts]
* @returns {{gitRoot:string, scanned:number, fixedFiles:number, fixedFrames:number, details:string[]}} * @returns {{gitRoot, scanned, reorderFiles, borderFiles, borderFrames, details:string[]}}
*/ */
function fixResetBorders(projectOrRoot, opts) { function fixResetBorders(projectOrRoot, opts) {
opts = opts || {}; opts = opts || {};
var dryRun = !!opts.dryRun; var dryRun = !!opts.dryRun;
var gitRoot; // 统一带 core.quotePath=false:否则 git 把中文等非 ASCII 路径加引号转义,path.join 会失配
try { function git(root, args) {
gitRoot = git(projectOrRoot, ['rev-parse', '--show-toplevel']).trim(); return cp.execFileSync('git', ['-C', root, '-c', 'core.quotePath=false'].concat(args),
} catch (e) { { encoding: 'utf8', maxBuffer: 128 * 1024 * 1024 });
throw new Error('不是 git 仓库(或 git 不可用): ' + projectOrRoot);
} }
var gitRoot;
try { gitRoot = git(projectOrRoot, ['rev-parse', '--show-toplevel']).trim(); }
catch (e) { throw new Error('不是 git 仓库(或 git 不可用): ' + projectOrRoot); }
var changed = git(gitRoot, ['diff', '--name-only', '--', '*.meta']) var changed = git(gitRoot, ['diff', '--name-only', '--', '*.meta'])
.split('\n').map(function (s) { return s.trim(); }).filter(Boolean); .split('\n').map(function (s) { return s.trim(); }).filter(Boolean);
var fixedFiles = 0, fixedFrames = 0; var reorderFiles = 0, borderFiles = 0, borderFrames = 0;
var details = []; var details = [];
for (var i = 0; i < changed.length; i++) { for (var i = 0; i < changed.length; i++) {
@@ -60,10 +71,19 @@ function fixResetBorders(projectOrRoot, opts) {
var abs = path.join(gitRoot, rel); var abs = path.join(gitRoot, rel);
if (!fs.existsSync(abs)) continue; if (!fs.existsSync(abs)) continue;
var work, head; var workStr, headStr, work, head;
try { work = JSON.parse(fs.readFileSync(abs, 'utf8')); } catch (e) { continue; } try { workStr = fs.readFileSync(abs, 'utf8'); work = JSON.parse(workStr); } catch (e) { continue; }
try { head = JSON.parse(git(gitRoot, ['show', 'HEAD:' + rel])); } catch (e) { continue; } // git 里没有(新文件)跳过 try { headStr = git(gitRoot, ['show', 'HEAD:' + rel]); head = JSON.parse(headStr); } catch (e) { continue; } // git 里没有(新文件)跳过
// 1) 纯 key 顺序/格式噪音(值深度相等)→ 还原 git 原文
if (valueEqual(work, head)) {
if (!dryRun) fs.writeFileSync(abs, headStr, 'utf8');
reorderFiles++;
details.push('[顺序] ' + rel);
continue;
}
// 2) 值不等 → 检查九宫格 border 是否被重置成 0,精准还原
var wSubs = work && work.subMetas; var wSubs = work && work.subMetas;
var hSubs = head && head.subMetas; var hSubs = head && head.subMetas;
if (!wSubs || !hSubs || typeof wSubs !== 'object' || typeof hSubs !== 'object') continue; if (!wSubs || !hSubs || typeof wSubs !== 'object' || typeof hSubs !== 'object') continue;
@@ -76,24 +96,31 @@ function fixResetBorders(projectOrRoot, opts) {
var hud = hSubs[key] && hSubs[key].userData; var hud = hSubs[key] && hSubs[key].userData;
if (!wud || !hud) continue; if (!wud || !hud) continue;
if (!BORDER_KEYS.some(function (b) { return b in wud; })) continue; if (!BORDER_KEYS.some(function (b) { return b in wud; })) continue;
// 工作区 border 全 0、git 版本有非 0 → 被重置,还原 git 的值
if (allZero(wud) && anyNonZero(hud)) { if (allZero(wud) && anyNonZero(hud)) {
for (var b = 0; b < BORDER_KEYS.length; b++) wud[BORDER_KEYS[b]] = hud[BORDER_KEYS[b]]; for (var b = 0; b < BORDER_KEYS.length; b++) wud[BORDER_KEYS[b]] = hud[BORDER_KEYS[b]];
metaChanged = true; metaChanged = true;
fixedFrames++; borderFrames++;
details.push(rel + ' [' + key + '] → T' + hud.borderTop + '/B' + hud.borderBottom + details.push('[border] ' + rel + ' [' + key + '] → T' + hud.borderTop + '/B' + hud.borderBottom +
'/L' + hud.borderLeft + '/R' + hud.borderRight); '/L' + hud.borderLeft + '/R' + hud.borderRight);
} }
} }
if (metaChanged) { if (metaChanged) {
fixedFiles++; borderFiles++;
if (!dryRun) fs.writeFileSync(abs, JSON.stringify(work, null, 2) + '\n', 'utf8'); if (!dryRun) {
// 还原 border 后若整体已等于 git(差异仅 border + 顺序)→ 直接写 git 原文,最干净;
// 否则只改了 border 字段、保留其它真实改动 → 按 work 写回(Cocos 标准格式)
if (valueEqual(work, head)) fs.writeFileSync(abs, headStr, 'utf8');
else fs.writeFileSync(abs, JSON.stringify(work, null, 2) + '\n', 'utf8');
}
} }
} }
return { gitRoot: gitRoot, scanned: changed.length, fixedFiles: fixedFiles, fixedFrames: fixedFrames, details: details }; return {
gitRoot: gitRoot, scanned: changed.length,
reorderFiles: reorderFiles, borderFiles: borderFiles, borderFrames: borderFrames,
details: details,
};
} }
module.exports = { fixResetBorders: fixResetBorders }; module.exports = { fixResetBorders: fixResetBorders };
+1 -1
View File
@@ -310,7 +310,7 @@ function defineTools(ctx) {
}, },
{ {
name: 'meta_fix_reset_border', name: 'meta_fix_reset_border',
description: '修复 Cocos 重启把图片九宫格 bordersubMetas.*.userData.border*)重置成 0 的 meta:扫 git 改动的 .meta,把「git 有值、工作区被清 0」的 border 还原成 git 的正确值,只动 border 字段、保留 meta 其它改动。dryRun=true 只预览不写。', description: '清理 Cocos 重启造成的图片 meta 噪音:① 纯 key 顺序/格式变化(值没变只 git diff 噪音)还原成 git 原文;② 九宫格 bordersubMetas.*.userData.border*重置成 0 的精准还原 git 的值。靠 git 对比、正确处理中文路径。dryRun=true 只预览不写。',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { dryRun: { type: 'boolean', description: 'true=只预览不写文件,列出将还原的 meta' } }, properties: { dryRun: { type: 'boolean', description: 'true=只预览不写文件,列出将还原的 meta' } },