mta-mcp 3.15.6 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/agents/flutter.agent.md
CHANGED
|
@@ -36,11 +36,23 @@
|
|
|
36
36
|
5. 只有当 compact 无法解释局部布局/样式时,再补用 measure/style
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### 图标资产强制规则(v4.7.0 新增)
|
|
40
|
+
|
|
41
|
+
- 默认流程改为:**先在 `assets/icons/` 查找用户手工导出的图标 PNG**,文件名优先匹配设计稿图层名对应的 `*_icon@4x.png`,找到即直接复用。
|
|
42
|
+
- 先看测量结果里的图层类型:`icon/svg` 才允许落地为 SVG;如果 Sketch 返回的是 `Image`,禁止伪造 SVG。
|
|
43
|
+
- `Image` 类型的 icon/button/arrow/help/customer-service 图层,优先做 **精确文件名查找**,不要先做语义联想或 canonical 合并。
|
|
44
|
+
- `Help Button`、`Customer Service Button`、`Customer Service Icon` 不能只因为位置相近就合并成同一个语义;先按 Sketch 图层名区分资产,再决定是否复用。
|
|
45
|
+
- 只有当 `assets/icons/` 中不存在对应的手工导出资源时,才允许提示用户补导;不要再优先产出 page-local PNG 副本。
|
|
46
|
+
- 手工 `*_icon@4x.png` 约定下,默认不再强制要求补齐 `2.0x/3.0x`;只有旧位图流程才继续要求多倍率变体。
|
|
47
|
+
- 已存在但内容损坏的 SVG 视为无效资产,必须修复或替换;禁止为了绕过损坏文件再平行创建第二个语义相同的新图标。
|
|
48
|
+
- 设计源是 `Image` 时,回复里必须明确说明“来源优先是 assets/icons 下的手工导出 PNG,不是设计稿原生 SVG”,避免误报为高清 SVG。
|
|
49
|
+
|
|
39
50
|
**注意:**
|
|
40
51
|
- 不要手动读取 artboard-measure.js(已废弃,合并进 sketch-tools.js)
|
|
41
52
|
- 不要手动拼接 _SKETCH_CMD(由 sketch_measure skill 自动处理)
|
|
42
53
|
- 测量结果中所有颜色均包含 `flutterColor: "Color(0xAARRGGBB)"` 格式,可直接使用
|
|
43
54
|
- 设计稿还原时,restorationContract 优先于现有业务组件、路由标题、i18n 文案和常见 CTA 习惯
|
|
55
|
+
- 图标资产落地前,必须先做一次 `assets/icons/` 精确搜索;先看设计稿图层名对应的 `*_icon@4x.png`,再考虑其他复用路径
|
|
44
56
|
|
|
45
57
|
### 测量数据关键字段(v4.6.0 新增带★标记)
|
|
46
58
|
|
package/dist/index.js
CHANGED
|
@@ -6406,7 +6406,7 @@ var OUTPUT_GUIDE = {
|
|
|
6406
6406
|
"- **rp**: [left,top,right,bottom] \u76F8\u5BF9\u7236\u5BB9\u5668\u8FB9\u8DDD",
|
|
6407
6407
|
"- **vf**: [x,y,w,h] \u89C6\u89C9\u8FB9\u754C\uFF1B\u5F53\u80CC\u666F\u5C42/\u9634\u5F71\u8D85\u51FA\u7236 Group frame \u65F6\uFF0C\u5BB9\u5668\u5C3A\u5BF8\u548C\u89C6\u89C9\u5E95\u8FB9\u8DDD\u4F18\u5148\u770B vf",
|
|
6408
6408
|
"- **icon/svg/cs/xs**: \u56FE\u6807\u6807\u8BC6/\u771F\u5B9E SvgPicture.string(...) \u4EE3\u7801/containerSize/contentSize",
|
|
6409
|
-
"- **image/img/bitmapPath/bitmapScales**: \u4F4D\u56FE\u56FE\u5C42\u6807\u8BC6/Image.asset \u4EE3\u7801/\u5EFA\u8BAE\u843D\u76D8\u8DEF\u5F84
|
|
6409
|
+
"- **image/img/bitmapPath/bitmapScales/bitmapLookupCandidates**: \u4F4D\u56FE\u56FE\u5C42\u6807\u8BC6/Image.asset \u4EE3\u7801/\u5EFA\u8BAE\u843D\u76D8\u8DEF\u5F84/\u5BFC\u51FA\u7EA6\u5B9A/\u4F18\u5148\u641C\u7D22\u7684\u624B\u5DE5\u56FE\u6807\u6587\u4EF6\u540D",
|
|
6410
6410
|
"- **svgMode/svgReason/svgFile**: preserve|tint / \u989C\u8272\u7B56\u7565\u539F\u56E0 / \u5EFA\u8BAE\u843D\u76D8\u6587\u4EF6\u540D",
|
|
6411
6411
|
"- **text/style/family/textHeight**: \u6587\u672C\u5185\u5BB9/\u5B8C\u6574 TextStyle/\u5B57\u4F53\u65CF/\u884C\u9AD8\u6BD4",
|
|
6412
6412
|
"- **lh/strut**: strutHeight / \u5B8C\u6574 StrutStyle(...)",
|
|
@@ -6423,7 +6423,7 @@ var OUTPUT_GUIDE = {
|
|
|
6423
6423
|
"- \u56FE\u6807\u662F\u5426\u8BEF\u7528\u65E7\u9875\u9762\u8D44\u4EA7\uFF1A\u540C\u540D\u7BAD\u5934/\u56FE\u6807\u9ED8\u8BA4\u4E0D\u53EF\u4FE1\uFF0C\u5148\u6838\u5BF9\u6765\u6E90\u56FE\u5C42",
|
|
6424
6424
|
"- SVG \u662F\u5426\u8BEF\u52A0 ColorFilter\uFF1A\u4FDD\u7559\u539F\u8272\u7684 SVG \u4E0D\u5F97\u4E8C\u6B21\u67D3\u8272",
|
|
6425
6425
|
"- \u56FE\u6807\u662F\u5426\u7528\u9519\u5C3A\u5BF8\uFF1A16x16 \u69FD\u4F4D\u4E0D\u7B49\u4E8E 16x16 \u56FE\u5F62\uFF0C\u6309 xs/cs \u5206\u79BB\u5360\u4F4D\u4E0E\u6E32\u67D3",
|
|
6426
|
-
"- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u5BFC\u51FA
|
|
6426
|
+
"- Bitmap \u662F\u5426\u8BEF\u5F53 SVG\uFF1ASketch Image \u56FE\u5C42\u5FC5\u987B\u4F18\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png\uFF0C\u547D\u4E2D\u540E\u76F4\u63A5\u590D\u7528\uFF0C\u4E0D\u518D\u4F2A\u9020 SVG",
|
|
6427
6427
|
"- \u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u7981\u6B62\u628A\u6574\u9875\u9501\u6210 390 \u753B\u677F\u58F3\u5B50",
|
|
6428
6428
|
"- Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
|
|
6429
6429
|
"- \u82E5\u8282\u70B9\u540C\u65F6\u7ED9\u51FA f \u4E0E vf\uFF0C\u8BF4\u660E\u80CC\u666F\u5C42/\u9634\u5F71\u8D85\u51FA\u903B\u8F91 frame\uFF1B\u5BB9\u5668\u5C3A\u5BF8\u548C\u5E95\u90E8\u89C6\u89C9\u95F4\u8DDD\u4F18\u5148\u4F7F\u7528 vf",
|
|
@@ -6493,11 +6493,20 @@ async function sketchMeasure(args) {
|
|
|
6493
6493
|
const scriptVersion = vm ? vm[1] : "1.0.0";
|
|
6494
6494
|
const source = `plugin:${scriptFile}`;
|
|
6495
6495
|
let fullScript = scriptContent;
|
|
6496
|
+
const measureOutputPath = cmd !== "svg" ? args.outputPath || path19.join(os.tmpdir(), `mta_sketch_${cmd}_output.json`) : null;
|
|
6496
6497
|
if (cmd === "svg" && args.outputPath) {
|
|
6497
|
-
fullScript = `var _SVG_OUTPUT_PATH = '${args.outputPath.replace(/'/g, "\\'")}'
|
|
6498
|
+
fullScript = `var _SVG_OUTPUT_PATH = '${args.outputPath.replace(/'/g, "\\'")}';
|
|
6499
|
+
` + scriptContent;
|
|
6498
6500
|
} else if (cmd === "compact") {
|
|
6501
|
+
if (measureOutputPath) {
|
|
6502
|
+
fullScript = `var _MEASURE_OUTPUT_PATH = '${measureOutputPath.replace(/'/g, "\\'")}';
|
|
6503
|
+
` + fullScript;
|
|
6504
|
+
}
|
|
6499
6505
|
fullScript = `var _FLUTTER_COMPACT = true;
|
|
6500
|
-
` +
|
|
6506
|
+
` + fullScript;
|
|
6507
|
+
} else if (measureOutputPath) {
|
|
6508
|
+
fullScript = `var _MEASURE_OUTPUT_PATH = '${measureOutputPath.replace(/'/g, "\\'")}';
|
|
6509
|
+
` + fullScript;
|
|
6501
6510
|
}
|
|
6502
6511
|
const tempScriptPath = path19.join(os.tmpdir(), "mta_sketch_script.js");
|
|
6503
6512
|
fs19.writeFileSync(tempScriptPath, fullScript, "utf-8");
|
|
@@ -6518,13 +6527,14 @@ async function sketchMeasure(args) {
|
|
|
6518
6527
|
scriptPath: tempScriptPath,
|
|
6519
6528
|
scriptLength: fullScript.length,
|
|
6520
6529
|
pluginInstall: installStatus,
|
|
6521
|
-
instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\
|
|
6530
|
+
instruction: "\u5C06 loaderScript \u4F20\u5165 mcp_sketch_run_code({ code: loaderScript }) \u6267\u884C\u3002\u8BBE\u8BA1\u7A3F\u8FD8\u539F\u9996\u8F6E\u4F18\u5148\u4F7F\u7528 cmd=compact\uFF1B\u53EA\u6709\u5F53 compact contract \u4E0D\u8DB3\u4EE5\u89E3\u91CA\u5C40\u90E8\u5E03\u5C40\u6216\u6837\u5F0F\u65F6\uFF0C\u518D\u8865\u7528 measure/style\u3002\u82E5\u504F\u5DEE\u96C6\u4E2D\u5728\u5355\u4E2A icon/svg/bitmap\uFF0C\u8BF7\u5148\u5728 assets/icons/ \u4E2D\u6309\u8BBE\u8BA1\u7A3F\u56FE\u5C42\u540D\u67E5\u627E\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png\uFF0C\u5E76\u590D\u67E5 colorStrategy\u3001viewBox\u3001containerSize/contentSize\u3001vf \u4E0E bitmapLookupCandidates\uFF0C\u907F\u514D\u7EE7\u7EED\u63A8\u65AD\u65E7\u9875\u9762\u8D44\u4EA7\u6216\u4F2A\u9020 SVG\u3002\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding \u9002\u914D\uFF1BSketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762\u3002compact \u82E5\u68C0\u6D4B\u5230\u4F60\u9009\u4E2D\u7684\u662F\u5185\u5C42 glyph\uFF0C\u4F1A\u81EA\u52A8\u63D0\u5347\u5230\u6240\u5C5E\u753B\u677F\u5E76\u901A\u8FC7 selectionContext \u56DE\u62A5\u3002\u8BF7\u5148\u5728 Sketch \u4E2D\u9009\u4E2D\u76EE\u6807\u753B\u677F/\u56FE\u5C42\u3002",
|
|
6531
|
+
measureOutputPath,
|
|
6522
6532
|
restoreChecklist: [
|
|
6523
6533
|
"\u5148\u6309 restorationContract \u9501\u5B9A\u9875\u9762\u9AA8\u67B6\uFF0C\u518D\u5904\u7406\u5C40\u90E8\u6837\u5F0F",
|
|
6524
|
-
"\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\
|
|
6534
|
+
"\u5355\u4E2A\u56FE\u6807\u504F\u5DEE\u5148\u67E5 assets/icons/ \u4E2D\u8BBE\u8BA1\u7A3F\u540C\u540D *_icon@4x.png\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u8865\u5BFC",
|
|
6525
6535
|
"preserve \u6A21\u5F0F SVG \u7981\u6B62\u8FFD\u52A0 ColorFilter",
|
|
6526
6536
|
"16x16 \u69FD\u4F4D\u4E0E 6x8/12x12 \u56FE\u5F62\u8981\u5206\u5F00\u5904\u7406",
|
|
6527
|
-
"Sketch Image \u56FE\u5C42\
|
|
6537
|
+
"Sketch Image \u56FE\u5C42\u4F18\u5148\u590D\u7528 assets/icons/*_icon@4x.png\uFF0C\u4E0D\u8981\u4F2A\u88C5\u6210 SVG\uFF1B\u53EA\u6709\u65E7\u4F4D\u56FE\u6D41\u7A0B\u624D\u8865 2.0x/3.0x",
|
|
6528
6538
|
"\u5168\u5C4F app \u9875\u9762\u5728\u5BBD\u5C4F\u5E94\u8DDF\u968F\u5BBF\u4E3B Scaffold/SafeArea/AppBar \u548C\u9875\u9762 padding\uFF0C\u53EA\u6709\u660E\u786E\u6D4B\u5F97\u7684\u5C40\u90E8\u6D6E\u5C42/\u5361\u7247\u5BB9\u5668\u624D\u505A clamp",
|
|
6529
6539
|
"Sketch \u9876\u90E8\u65F6\u95F4/\u4FE1\u53F7/WiFi/\u7535\u6C60\u5C5E\u4E8E\u8BBE\u5907\u7CFB\u7EDF chrome\uFF0C\u4E0D\u5C5E\u4E8E\u9875\u9762\u4E1A\u52A1 UI\uFF0C\u7981\u6B62\u76F4\u63A5\u8FD8\u539F\u5230 Flutter \u9875\u9762",
|
|
6530
6540
|
"\u82E5\u8282\u70B9\u540C\u65F6\u51FA\u73B0 f \u4E0E vf\uFF0C\u5BB9\u5668\u5C3A\u5BF8\u548C\u5E95\u90E8\u89C6\u89C9\u95F4\u8DDD\u4F18\u5148\u4F7F\u7528 vf\uFF0C\u4E0D\u8981\u53EA\u6284\u903B\u8F91 frame",
|
|
@@ -6536,7 +6546,7 @@ async function sketchMeasure(args) {
|
|
|
6536
6546
|
required: true,
|
|
6537
6547
|
checks: ["contract-validator", "asset-source-validator", "route-review-validator"],
|
|
6538
6548
|
exampleArgs: {
|
|
6539
|
-
measurePath: "/path/to/sketch-compact-output.json",
|
|
6549
|
+
measurePath: measureOutputPath || "/path/to/sketch-compact-output.json",
|
|
6540
6550
|
codeFilePath: "/path/to/generated_page.dart",
|
|
6541
6551
|
routeFilePath: "/path/to/app_pages.dart",
|
|
6542
6552
|
routeName: "AppRoutes.xxx",
|
|
@@ -6557,30 +6567,57 @@ function isRecord2(value) {
|
|
|
6557
6567
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6558
6568
|
}
|
|
6559
6569
|
function parseNestedJson(raw) {
|
|
6560
|
-
|
|
6561
|
-
|
|
6570
|
+
var _a;
|
|
6571
|
+
const queue = [raw.trim()];
|
|
6572
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6573
|
+
let lastError = null;
|
|
6574
|
+
while (queue.length > 0) {
|
|
6575
|
+
const candidate = (_a = queue.shift()) == null ? void 0 : _a.trim();
|
|
6576
|
+
if (!candidate || seen.has(candidate)) {
|
|
6577
|
+
continue;
|
|
6578
|
+
}
|
|
6579
|
+
seen.add(candidate);
|
|
6562
6580
|
try {
|
|
6563
6581
|
const parsed = JSON.parse(candidate);
|
|
6564
6582
|
if (typeof parsed === "string") {
|
|
6565
|
-
|
|
6583
|
+
queue.push(parsed.trim());
|
|
6566
6584
|
continue;
|
|
6567
6585
|
}
|
|
6568
6586
|
if (isRecord2(parsed) && Array.isArray(parsed.content) && parsed.content[0] && isRecord2(parsed.content[0])) {
|
|
6569
6587
|
const text = parsed.content[0].text;
|
|
6570
6588
|
if (typeof text === "string") {
|
|
6571
|
-
|
|
6589
|
+
queue.push(text.trim());
|
|
6572
6590
|
continue;
|
|
6573
6591
|
}
|
|
6574
6592
|
}
|
|
6575
6593
|
return parsed;
|
|
6576
|
-
} catch {
|
|
6577
|
-
|
|
6578
|
-
|
|
6594
|
+
} catch (error) {
|
|
6595
|
+
lastError = error;
|
|
6596
|
+
}
|
|
6597
|
+
if (candidate.startsWith("'") && candidate.endsWith("'") || candidate.startsWith('"') && candidate.endsWith('"')) {
|
|
6598
|
+
queue.push(candidate.slice(1, -1));
|
|
6599
|
+
}
|
|
6600
|
+
const decoded = candidate.replace(/\\r/g, "\r").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
6601
|
+
if (decoded !== candidate) {
|
|
6602
|
+
queue.push(decoded);
|
|
6603
|
+
}
|
|
6604
|
+
const firstBrace = candidate.indexOf("{");
|
|
6605
|
+
const lastBrace = candidate.lastIndexOf("}");
|
|
6606
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
6607
|
+
const sliced = candidate.slice(firstBrace, lastBrace + 1);
|
|
6608
|
+
if (sliced !== candidate) {
|
|
6609
|
+
queue.push(sliced);
|
|
6610
|
+
}
|
|
6611
|
+
const decodedSliced = sliced.replace(/\\r/g, "\r").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
6612
|
+
if (decodedSliced !== sliced) {
|
|
6613
|
+
queue.push(decodedSliced);
|
|
6579
6614
|
}
|
|
6580
|
-
candidate = candidate.replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
6581
6615
|
}
|
|
6582
6616
|
}
|
|
6583
|
-
|
|
6617
|
+
if (lastError instanceof Error) {
|
|
6618
|
+
throw lastError;
|
|
6619
|
+
}
|
|
6620
|
+
throw new Error("measure payload \u89E3\u6790\u5931\u8D25");
|
|
6584
6621
|
}
|
|
6585
6622
|
function loadMeasurePayload(args) {
|
|
6586
6623
|
const raw = args.measureContent || (args.measurePath ? fs20.readFileSync(args.measurePath, "utf-8") : "");
|
|
@@ -6615,7 +6652,9 @@ function collectAssetContracts(node, bucket = []) {
|
|
|
6615
6652
|
sketchName: node.n || "Image",
|
|
6616
6653
|
fileName: typeof node.bitmapFile === "string" ? path20.posix.basename(node.bitmapFile) : typeof node.bitmapPath === "string" ? path20.posix.basename(node.bitmapPath) : void 0,
|
|
6617
6654
|
sourceType: "bitmap",
|
|
6618
|
-
scaleVariants: node.bitmapScales
|
|
6655
|
+
scaleVariants: node.bitmapScales,
|
|
6656
|
+
reuseMode: node.bitmapReuseMode,
|
|
6657
|
+
reuseKey: node.bitmapReuseKey
|
|
6619
6658
|
});
|
|
6620
6659
|
}
|
|
6621
6660
|
for (const child of node.ch || []) {
|
|
@@ -6624,9 +6663,12 @@ function collectAssetContracts(node, bucket = []) {
|
|
|
6624
6663
|
return bucket;
|
|
6625
6664
|
}
|
|
6626
6665
|
function extractCodeAssets(codeContent) {
|
|
6627
|
-
const matches = codeContent.match(/assets\/[
|
|
6666
|
+
const matches = codeContent.match(/assets\/[^'")]+\.(svg|png|jpg|jpeg|webp)/g);
|
|
6628
6667
|
return matches ? [...new Set(matches)] : [];
|
|
6629
6668
|
}
|
|
6669
|
+
function isManualExport4xBitmapAsset(assetPath) {
|
|
6670
|
+
return /_icon@4x\.(png|jpg|jpeg|webp)$/i.test(path20.posix.basename(assetPath));
|
|
6671
|
+
}
|
|
6630
6672
|
function hasCodePropertyLiteral(codeContent, property, value) {
|
|
6631
6673
|
const escapedValue = String(value).replace(".", "\\.");
|
|
6632
6674
|
return new RegExp(`${property}\\s*:\\s*${escapedValue}(?:\\.0+)?\\b`).test(codeContent);
|
|
@@ -6747,17 +6789,17 @@ function buildContractValidator(payload, codeContent) {
|
|
|
6747
6789
|
});
|
|
6748
6790
|
}
|
|
6749
6791
|
}
|
|
6750
|
-
const needsSupportButton = blockOrder.some((block) => /customer service|support
|
|
6792
|
+
const needsSupportButton = blockOrder.some((block) => /customer service|support|客服|help[_ -]?button|帮助/i.test(`${block.name || ""} ${block.label || ""}`));
|
|
6751
6793
|
if (needsSupportButton) {
|
|
6752
6794
|
requiredBlocks += 1;
|
|
6753
|
-
if (hasAnyPattern(codeContent, [/customer[_ ]service/i, /support/i, /recipient_customer_service_button/i, /客服/])) {
|
|
6795
|
+
if (hasAnyPattern(codeContent, [/customer[_ -]?service/i, /support/i, /help[_ -]?button/i, /recipient_customer_service_button/i, /客服/])) {
|
|
6754
6796
|
matchedBlocks += 1;
|
|
6755
6797
|
} else {
|
|
6756
6798
|
findings.push({
|
|
6757
6799
|
id: "missing_customer_service_block",
|
|
6758
6800
|
severity: "error",
|
|
6759
|
-
message: "\u8BBE\u8BA1\u7A3F\u5B58\u5728 Customer Service Button\uFF0C\u4F46\u4EE3\u7801\u4E2D\u672A\u68C0\u6D4B\u5230\u5BF9\u5E94\u5165\u53E3\u3002",
|
|
6760
|
-
suggestion: "\u8865\u9F50\u5BA2\u670D\
|
|
6801
|
+
message: "\u8BBE\u8BA1\u7A3F\u5B58\u5728 Help/Customer Service Button\uFF0C\u4F46\u4EE3\u7801\u4E2D\u672A\u68C0\u6D4B\u5230\u5BF9\u5E94\u5165\u53E3\u3002",
|
|
6802
|
+
suggestion: "\u8865\u9F50\u53F3\u4E0A\u89D2\u5E2E\u52A9/\u5BA2\u670D\u5165\u53E3\uFF0C\u5E76\u4FDD\u6301\u4E0E\u8BBE\u8BA1\u56FE\u5C42\u8BED\u4E49\u4E00\u81F4\uFF0C\u4E0D\u8981\u628A Help Button \u66FF\u6362\u6210\u5176\u4ED6\u5BA2\u670D\u56FE\u6807\u3002"
|
|
6761
6803
|
});
|
|
6762
6804
|
}
|
|
6763
6805
|
}
|
|
@@ -6854,26 +6896,50 @@ function buildAssetValidator(payload, codeContent, args) {
|
|
|
6854
6896
|
findings.push({
|
|
6855
6897
|
id: "bitmap_asset_source_check_required",
|
|
6856
6898
|
severity: "info",
|
|
6857
|
-
message: "\u4EE3\u7801\u4E2D\u5B58\u5728\u4F4D\u56FE\u8D44\u6E90\uFF0C\u9700\u4EBA\u5DE5\u786E\u8BA4\u5B83\u4EEC\u6765\u81EA\u5F53\u524D\u753B\u677F\
|
|
6899
|
+
message: "\u4EE3\u7801\u4E2D\u5B58\u5728\u4F4D\u56FE\u8D44\u6E90\uFF0C\u9700\u4EBA\u5DE5\u786E\u8BA4\u5B83\u4EEC\u6765\u81EA\u5F53\u524D\u753B\u677F\uFF0C\u6216\u5DF2\u6B63\u786E\u590D\u7528\u5F53\u524D\u8BBE\u8BA1\u7EA6\u5B9A\u7684 canonical \u5171\u4EAB\u8D44\u4EA7\u3002",
|
|
6858
6900
|
evidence: codeBitmapAssets,
|
|
6859
|
-
suggestion: "\
|
|
6901
|
+
suggestion: "\u4F18\u5148\u5148\u5728 assets/icons/ \u4E2D\u67E5\u627E\u7528\u6237\u624B\u5DE5\u5BFC\u51FA\u7684\u8BBE\u8BA1\u540C\u540D *_icon@4x.png\uFF1B\u53EA\u6709\u7F3A\u5931\u65F6\uFF0C\u624D\u6309 contract \u6307\u5B9A\u65B9\u5F0F\u8865\u5BFC\u6216\u590D\u7528\u5171\u4EAB\u8D44\u4EA7\u3002"
|
|
6860
6902
|
});
|
|
6861
6903
|
}
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6904
|
+
const sharedBitmapFiles = new Set(
|
|
6905
|
+
assetContracts.filter((item) => item.sourceType === "bitmap" && item.reuseMode === "shared-canonical" && item.fileName).map((item) => item.fileName.toLowerCase())
|
|
6906
|
+
);
|
|
6907
|
+
if (sharedBitmapFiles.size > 0) {
|
|
6908
|
+
const usedBitmapFiles = new Set(
|
|
6909
|
+
codeBitmapAssets.map((asset) => path20.posix.basename(asset).toLowerCase())
|
|
6910
|
+
);
|
|
6911
|
+
const missingSharedBitmapFiles = [...sharedBitmapFiles].filter((fileName) => !usedBitmapFiles.has(fileName));
|
|
6912
|
+
if (missingSharedBitmapFiles.length > 0) {
|
|
6913
|
+
findings.push({
|
|
6914
|
+
id: "shared_bitmap_asset_not_reused",
|
|
6915
|
+
severity: "warning",
|
|
6916
|
+
message: "\u8BBE\u8BA1\u7A3F\u5DF2\u6807\u8BB0\u8BE5\u4F4D\u56FE\u4E3A\u5171\u4EAB canonical \u8D44\u4EA7\uFF0C\u4F46\u4EE3\u7801\u6CA1\u6709\u590D\u7528\u5BF9\u5E94\u6587\u4EF6\u540D\uFF0C\u5B58\u5728\u91CD\u590D\u7F13\u5B58\u6216\u628A Help/Customer Service \u8BED\u4E49\u8BEF\u5408\u5E76\u7684\u98CE\u9669\u3002",
|
|
6917
|
+
evidence: missingSharedBitmapFiles,
|
|
6918
|
+
suggestion: "\u4F18\u5148\u590D\u7528 contract \u8FD4\u56DE\u7684\u5171\u4EAB bitmap \u6587\u4EF6\u540D\uFF1BHelp Button \u4E0E Customer Service Button \u5FC5\u987B\u6309\u5404\u81EA canonical \u6587\u4EF6\u72EC\u7ACB\u590D\u7528\uFF0C\u4E0D\u80FD\u518D\u5408\u5E76\u6210\u540C\u4E00\u4E2A\u540D\u5B57\u3002"
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
const requiresPageLocalBitmap = assetContracts.some(
|
|
6923
|
+
(item) => item.sourceType === "bitmap" && item.reuseMode !== "shared-canonical"
|
|
6924
|
+
);
|
|
6925
|
+
if (expectedBitmapCount > 0 && codeBitmapAssets.length > 0 && requiresPageLocalBitmap) {
|
|
6926
|
+
const hasManualExport4xBitmap = codeBitmapAssets.some((asset) => isManualExport4xBitmapAsset(asset));
|
|
6927
|
+
if (!hasManualExport4xBitmap) {
|
|
6928
|
+
const pageClassName = extractPageClassName(codeContent) || "";
|
|
6929
|
+
const normalizedStem = pageClassName.replace(/Page$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
6930
|
+
if (normalizedStem) {
|
|
6931
|
+
const hasPageLocalBitmap = codeBitmapAssets.some(
|
|
6932
|
+
(asset) => path20.posix.basename(asset).toLowerCase().includes(normalizedStem)
|
|
6933
|
+
);
|
|
6934
|
+
if (!hasPageLocalBitmap) {
|
|
6935
|
+
findings.push({
|
|
6936
|
+
id: "bitmap_assets_not_page_local",
|
|
6937
|
+
severity: "warning",
|
|
6938
|
+
message: "\u5F53\u524D\u9875\u9762\u4F4D\u56FE\u8D44\u6E90\u770B\u8D77\u6765\u4E0D\u662F page-local \u547D\u540D\uFF0C\u5B58\u5728\u7EE7\u7EED\u590D\u7528\u65E7\u9875\u9762 PNG \u7684\u98CE\u9669\u3002",
|
|
6939
|
+
evidence: codeBitmapAssets,
|
|
6940
|
+
suggestion: "\u4F4D\u56FE\u8D44\u6E90\u4F18\u5148\u6309\u5F53\u524D\u9875\u9762\u547D\u540D\u5BFC\u51FA\uFF0C\u4F8B\u5982\u5E26\u4E0A\u9875\u9762 stem\uFF1B\u82E5\u9879\u76EE\u5DF2\u6539\u4E3A\u624B\u5DE5 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u5219\u65E0\u9700\u518D\u8FFD\u52A0 page-local stem\u3002"
|
|
6941
|
+
});
|
|
6942
|
+
}
|
|
6877
6943
|
}
|
|
6878
6944
|
}
|
|
6879
6945
|
}
|
|
@@ -6885,6 +6951,9 @@ function buildAssetValidator(payload, codeContent, args) {
|
|
|
6885
6951
|
if (requiresRetinaVariants) {
|
|
6886
6952
|
const missingRetinaEvidence = [];
|
|
6887
6953
|
for (const asset of codeBitmapAssets) {
|
|
6954
|
+
if (isManualExport4xBitmapAsset(asset)) {
|
|
6955
|
+
continue;
|
|
6956
|
+
}
|
|
6888
6957
|
const assetOnDisk = resolveProjectAssetPath(args.projectPath, asset);
|
|
6889
6958
|
if (!fs20.existsSync(assetOnDisk)) {
|
|
6890
6959
|
missingRetinaEvidence.push(`${asset} (base missing)`);
|
|
@@ -6900,9 +6969,9 @@ function buildAssetValidator(payload, codeContent, args) {
|
|
|
6900
6969
|
findings.push({
|
|
6901
6970
|
id: "bitmap_retina_variants_missing",
|
|
6902
6971
|
severity: "error",
|
|
6903
|
-
message: "\u4F4D\u56FE\u8D44\u6E90\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
|
|
6972
|
+
message: "\u4F4D\u56FE\u8D44\u6E90\u65E2\u6CA1\u6709\u4F7F\u7528\u624B\u5DE5\u5BFC\u51FA\u7684 *_icon@4x.png \u7EA6\u5B9A\uFF0C\u4E5F\u7F3A\u5C11 2.0x/3.0x \u53D8\u4F53\uFF0C\u9AD8\u5206\u5C4F\u4E0B\u5BB9\u6613\u53D1\u7CCA\u3002",
|
|
6904
6973
|
evidence: missingRetinaEvidence,
|
|
6905
|
-
suggestion: "\
|
|
6974
|
+
suggestion: "\u4F18\u5148\u6539\u4E3A assets/icons/*_icon@4x.png \u7684\u624B\u5DE5\u5BFC\u51FA\u7EA6\u5B9A\uFF1B\u5982\u679C\u4ECD\u8D70\u65E7\u4F4D\u56FE\u6D41\u7A0B\uFF0C\u518D\u8865\u9F50 assets/.../2.0x \u4E0E 3.0x \u53D8\u4F53\u3002"
|
|
6906
6975
|
});
|
|
6907
6976
|
}
|
|
6908
6977
|
}
|