/** * 前端 `/update` 入口的两向变异驱动。 * * 每条形变都:① 断言锚点**恰好命中一次**(命中 0 次或多次都不动文件);② 打补丁; * ③ 跑完由调用方 `restore`,`restore` 从 `.mut-bak` 原样拷回并复核 sha256。 * ★ 「还原」本身也要被验证 —— 所以 restore 会打印还原后的 sha256,由外面的脚本与基线对撞。 * * 用法:node fe-update-mutate.mjs <目标文件> */ import fs from "node:fs"; import crypto from "node:crypto"; const PAGE = "D:/project2/payment-platform/frontend/src/views/module_payment/collection/onboarding/index.vue"; const ID = process.argv[2]; const MUTATIONS = { // 红向 ①:把「减去两个」拆掉 ⇒ 更新报文会带上 merchant_name_business / lease_contract "M-U1": { file: PAGE, from: "const UPDATE_KEYS: string[] = CREATE_KEYS.filter((k) => !UPDATE_EXCLUDED_KEYS.includes(k));", to: "const UPDATE_KEYS: string[] = CREATE_KEYS.slice();", }, // 红向 ②:把白名单换成整表摊开 ⇒ category / tenant_id / rate_code 全都会发出去 "M-U2": { file: PAGE, from: ` const body: Record = { mch_no: form.mch_no.trim() }; for (const key of UPDATE_KEYS) { const value = (form[key] ?? "").trim(); if (value !== "") body[key] = value; } return body;`, to: ` const body: Record = { mch_no: form.mch_no.trim() }; for (const key of Object.keys(form)) { const value = (form[key] ?? "").trim(); if (value !== "") body[key] = value; } return body;`, }, // 红向 ③:渲染投影失效 ⇒ 更新模式下那两格会渲染出来(按下去必 500) "M-U3": { file: PAGE, from: ` return mode.value === "create" ? s.fields : s.fields.filter((f) => !UPDATE_EXCLUDED_KEYS.includes(f.key));`, to: ` return s.fields;`, }, // 无害向:给更新腿的 catch 加一句注释 —— 行为**应当完全不变**(这一条是「刻意钝感」的那一侧) "M-U5": { file: PAGE, from: ` } catch { // 失败文案由拦截器统一弹(两道封盖的 400、非本租户的 404、上游失败的 502 都在 \`msg\` 里)`, to: ` } catch { // MUTANT-HARMLESS:只加一行注释 // 失败文案由拦截器统一弹(两道封盖的 400、非本租户的 404、上游失败的 502 都在 \`msg\` 里)`, }, }; /** * ★ 这两条是给 **`vue-tsc`** 用的,不是给执行级台子用的:`ts:check` 在本仓有一个**非零历史基线** * ⇒ 「前后输出一模一样」也可能是因为 vue-tsc **压根没看我新加的文件**。要主张「没引入新错」, * 就必须先证明它**看得见**这两个文件。 */ const API = "D:/project2/payment-platform/frontend/src/api/module_payment/onboarding.ts"; MUTATIONS["M-T1"] = { file: PAGE, from: `const mode = ref<"create" | "update">("create");`, to: `const mode = ref<"create" | "update">("create");\nconst __tsProbe: number = "故意写错的类型";`, }; MUTATIONS["M-T2"] = { file: API, from: `export function updateOnboardingMerchant(data: Record) {`, to: `const __tsProbe2: number = "故意写错的类型";\nexport function updateOnboardingMerchant(data: Record) {`, }; const sha = (f) => crypto.createHash("sha256").update(fs.readFileSync(f)).digest("hex"); if (ID === "restore") { for (const f of new Set(Object.values(MUTATIONS).map((m) => m.file))) { const bak = f + ".mut-bak"; if (fs.existsSync(bak)) { fs.copyFileSync(bak, f); fs.unlinkSync(bak); console.log(`restored ${f} sha256=${sha(f)}`); } } process.exit(0); } const m = MUTATIONS[ID]; if (!m) throw new Error("未知变异:" + ID); const bak = m.file + ".mut-bak"; if (!fs.existsSync(bak)) fs.copyFileSync(m.file, bak); const src = fs.readFileSync(m.file, "utf8"); const hits = src.split(m.from).length - 1; if (hits !== 1) throw new Error(`[${ID}] 锚点命中 ${hits} 次,期望恰好 1 次 —— 文件未被修改`); fs.writeFileSync(m.file, src.replace(m.from, m.to)); console.log(`[${ID}] patched ${m.file} sha256=${sha(m.file)}`);