/** * A2 自检脚本(可复现):验证「同一秒内两个同名任务 → 两个不等的 savedName」 * * 直接加载生产代码 `src/utils/image/name-collision.ts`(零依赖纯函数), * 由 Node 原生类型剥离执行,无需打包工具: * * node scripts/selfcheck-naming.mjs * * 结果同时打印到终端并写入 `scripts/selfcheck-naming.log`(UTF-8), * 避免 Windows 控制台代码页把中文输出弄成乱码。 * 模拟 `core.ts#saveResult` 的去冲突步骤: * 候选名 + 「当天历史 ∪ 本次会话已用名」集合 → dedupeName → 命中则追加 -2、-3。 */ import { writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { dedupeName } from '../src/utils/image/name-collision.ts' const lines = [] let failed = 0 function assert(cond, msg) { const line = `${cond ? 'ok ' : 'FAIL'}: ${msg}` lines.push(line) if (!cond) failed++ } // —— 核心场景:同一秒、同工具、同原图,连续产出两次 —— const candidate = '去除背景-IMG_2043-20260918153012.png' const taken = [] const n1 = dedupeName(candidate, taken) taken.push(n1) const n2 = dedupeName(candidate, taken) taken.push(n2) assert(n1 === candidate, `第一次产出保持干净格式:${n1}`) assert(n2 !== n1, `两次产出文件名不相等:${n1} vs ${n2}`) assert(n2 === '去除背景-IMG_2043-20260918153012-2.png', `第二次追加 -2:${n2}`) // —— 边界:时间戳(14 位数字)绝不能被误判成 `-N` 计数器 —— assert(n2.endsWith('-20260918153012-2.png'), '时间戳未被当作计数器截断') // —— 边界:第三次继续递增 —— const n3 = dedupeName(candidate, taken) assert(n3 === '去除背景-IMG_2043-20260918153012-3.png', `第三次追加 -3:${n3}`) // —— 边界:主干已带 -N 后缀时从 N+1 起,避免出现 xxx-2-2 —— assert(dedupeName('x-2.png', ['x-2.png']) === 'x-3.png', '已带后缀时递增不叠加(-2 → -3)') // —— 边界:无冲突时原样返回(正常路径不改名) —— assert(dedupeName('a.png', []) === 'a.png', '无冲突原样返回') // —— 边界:无扩展名的候选 —— assert(dedupeName('plain', ['plain']) === 'plain-2', '无扩展名时追加 -2') lines.push('') lines.push(failed ? `${failed} check(s) FAILED` : 'ALL CHECKS PASSED') const out = lines.join('\n') // eslint-disable-next-line no-console console.log(out) try { const here = dirname(fileURLToPath(import.meta.url)) writeFileSync(join(here, 'selfcheck-naming.log'), `${out}\n`, 'utf8') } catch { /* 忽略:写日志失败不影响自检结论 */ } process.exit(failed ? 1 : 0)