fe-update-mutate.mjs 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * 前端 `/update` 入口的两向变异驱动。
  3. *
  4. * 每条形变都:① 断言锚点**恰好命中一次**(命中 0 次或多次都不动文件);② 打补丁;
  5. * ③ 跑完由调用方 `restore`,`restore` 从 `.mut-bak` 原样拷回并复核 sha256。
  6. * ★ 「还原」本身也要被验证 —— 所以 restore 会打印还原后的 sha256,由外面的脚本与基线对撞。
  7. *
  8. * 用法:node fe-update-mutate.mjs <mutationId|restore> <目标文件>
  9. */
  10. import fs from "node:fs";
  11. import crypto from "node:crypto";
  12. const PAGE = "D:/project2/payment-platform/frontend/src/views/module_payment/collection/onboarding/index.vue";
  13. const ID = process.argv[2];
  14. const MUTATIONS = {
  15. // 红向 ①:把「减去两个」拆掉 ⇒ 更新报文会带上 merchant_name_business / lease_contract
  16. "M-U1": {
  17. file: PAGE,
  18. from: "const UPDATE_KEYS: string[] = CREATE_KEYS.filter((k) => !UPDATE_EXCLUDED_KEYS.includes(k));",
  19. to: "const UPDATE_KEYS: string[] = CREATE_KEYS.slice();",
  20. },
  21. // 红向 ②:把白名单换成整表摊开 ⇒ category / tenant_id / rate_code 全都会发出去
  22. "M-U2": {
  23. file: PAGE,
  24. from: ` const body: Record<string, string> = { mch_no: form.mch_no.trim() };
  25. for (const key of UPDATE_KEYS) {
  26. const value = (form[key] ?? "").trim();
  27. if (value !== "") body[key] = value;
  28. }
  29. return body;`,
  30. to: ` const body: Record<string, string> = { mch_no: form.mch_no.trim() };
  31. for (const key of Object.keys(form)) {
  32. const value = (form[key] ?? "").trim();
  33. if (value !== "") body[key] = value;
  34. }
  35. return body;`,
  36. },
  37. // 红向 ③:渲染投影失效 ⇒ 更新模式下那两格会渲染出来(按下去必 500)
  38. "M-U3": {
  39. file: PAGE,
  40. from: ` return mode.value === "create"
  41. ? s.fields
  42. : s.fields.filter((f) => !UPDATE_EXCLUDED_KEYS.includes(f.key));`,
  43. to: ` return s.fields;`,
  44. },
  45. // 无害向:给更新腿的 catch 加一句注释 —— 行为**应当完全不变**(这一条是「刻意钝感」的那一侧)
  46. "M-U5": {
  47. file: PAGE,
  48. from: ` } catch {
  49. // 失败文案由拦截器统一弹(两道封盖的 400、非本租户的 404、上游失败的 502 都在 \`msg\` 里)`,
  50. to: ` } catch {
  51. // MUTANT-HARMLESS:只加一行注释
  52. // 失败文案由拦截器统一弹(两道封盖的 400、非本租户的 404、上游失败的 502 都在 \`msg\` 里)`,
  53. },
  54. };
  55. /**
  56. * ★ 这两条是给 **`vue-tsc`** 用的,不是给执行级台子用的:`ts:check` 在本仓有一个**非零历史基线**
  57. * ⇒ 「前后输出一模一样」也可能是因为 vue-tsc **压根没看我新加的文件**。要主张「没引入新错」,
  58. * 就必须先证明它**看得见**这两个文件。
  59. */
  60. const API = "D:/project2/payment-platform/frontend/src/api/module_payment/onboarding.ts";
  61. MUTATIONS["M-T1"] = {
  62. file: PAGE,
  63. from: `const mode = ref<"create" | "update">("create");`,
  64. to: `const mode = ref<"create" | "update">("create");\nconst __tsProbe: number = "故意写错的类型";`,
  65. };
  66. MUTATIONS["M-T2"] = {
  67. file: API,
  68. from: `export function updateOnboardingMerchant(data: Record<string, unknown>) {`,
  69. to: `const __tsProbe2: number = "故意写错的类型";\nexport function updateOnboardingMerchant(data: Record<string, unknown>) {`,
  70. };
  71. const sha = (f) => crypto.createHash("sha256").update(fs.readFileSync(f)).digest("hex");
  72. if (ID === "restore") {
  73. for (const f of new Set(Object.values(MUTATIONS).map((m) => m.file))) {
  74. const bak = f + ".mut-bak";
  75. if (fs.existsSync(bak)) {
  76. fs.copyFileSync(bak, f);
  77. fs.unlinkSync(bak);
  78. console.log(`restored ${f} sha256=${sha(f)}`);
  79. }
  80. }
  81. process.exit(0);
  82. }
  83. const m = MUTATIONS[ID];
  84. if (!m) throw new Error("未知变异:" + ID);
  85. const bak = m.file + ".mut-bak";
  86. if (!fs.existsSync(bak)) fs.copyFileSync(m.file, bak);
  87. const src = fs.readFileSync(m.file, "utf8");
  88. const hits = src.split(m.from).length - 1;
  89. if (hits !== 1) throw new Error(`[${ID}] 锚点命中 ${hits} 次,期望恰好 1 次 —— 文件未被修改`);
  90. fs.writeFileSync(m.file, src.replace(m.from, m.to));
  91. console.log(`[${ID}] patched ${m.file} sha256=${sha(m.file)}`);