@harmonyos-arkts/d2h 0.1.10 → 0.1.12

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.
Files changed (126) hide show
  1. package/.claude-plugin/marketplace.json +15 -13
  2. package/.claude-plugin/plugin.json +13 -7
  3. package/AGENTS.md +5 -5
  4. package/README.md +212 -212
  5. package/agents/android-to-hmos-00-orchestrator.md +318 -318
  6. package/agents/d2h.md +157 -157
  7. package/dist/bin/install-opencode.js +30 -30
  8. package/docs/environment-check.md +86 -86
  9. package/install-opencode.sh +160 -160
  10. package/opencode/agents.json +16 -16
  11. package/opencode/vscode-d2h-package.json +7 -7
  12. package/opencode/vscode-d2h-plugin.mjs +29 -29
  13. package/package.json +54 -54
  14. package/schemas/android-source-manifest.schema.json +25 -25
  15. package/schemas/checkpoint-provenance.schema.json +67 -67
  16. package/schemas/final-acceptance.schema.json +29 -29
  17. package/schemas/managed-evidence-index.schema.json +15 -15
  18. package/schemas/managed-evidence.schema.json +114 -114
  19. package/schemas/migration-config.schema.json +51 -51
  20. package/schemas/migration-report-index.schema.json +39 -39
  21. package/schemas/migration-report-item.schema.json +51 -51
  22. package/schemas/migration-report-summary.schema.json +25 -25
  23. package/schemas/migration-status.schema.json +202 -202
  24. package/schemas/preflight.schema.json +60 -60
  25. package/schemas/source-order-audit.schema.json +16 -16
  26. package/schemas/source-provenance-event.schema.json +19 -19
  27. package/schemas/spec-app-shard.schema.json +45 -45
  28. package/schemas/spec-fact-corrections-shard.schema.json +13 -13
  29. package/schemas/spec-features-shard.schema.json +43 -43
  30. package/schemas/spec-index.schema.json +26 -26
  31. package/schemas/spec-interactions-shard.schema.json +40 -40
  32. package/schemas/spec-page.schema.json +98 -98
  33. package/schemas/spec-pages-index.schema.json +1 -1
  34. package/schemas/spec-unresolved-shard.schema.json +1 -1
  35. package/schemas/task-envelope.schema.json +109 -109
  36. package/schemas/task-plan.schema.json +341 -341
  37. package/scripts/check-environment.mjs +320 -320
  38. package/skills/android2hmos_resources_convert/SKILL.md +180 -180
  39. package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
  40. package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
  41. package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
  42. package/skills/arkts-app-identity/SKILL.md +238 -238
  43. package/skills/arkts-i18n/SKILL.md +496 -496
  44. package/skills/arkts-i18n/evals/evals.json +84 -84
  45. package/skills/arkts-i18n/references/code-examples.md +302 -302
  46. package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
  47. package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
  48. package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
  49. package/skills/arkts-i18n/references/language-codes.md +104 -104
  50. package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
  51. package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
  52. package/skills/arkts-i18n/references/v1-compat.md +244 -244
  53. package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
  54. package/skills/arkts-icon-sizing/SKILL.md +211 -211
  55. package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
  56. package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
  57. package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
  58. package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
  59. package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
  60. package/skills/d2h-hypium-forge/SKILL.md +309 -309
  61. package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
  62. package/skills/d2h-hypium-forge/references/official-api.md +135 -135
  63. package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
  64. package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
  65. package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
  66. package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
  67. package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
  68. package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
  69. package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
  70. package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
  71. package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
  72. package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
  73. package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
  74. package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
  75. package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
  76. package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
  77. package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
  78. package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
  79. package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
  80. package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
  81. package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
  82. package/skills/d2h-page-develop/SKILL.md +112 -112
  83. package/skills/d2h-page-develop/agents/openai.yaml +7 -7
  84. package/skills/d2h-page-spec-generate/SKILL.md +206 -206
  85. package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
  86. package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
  87. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
  88. package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
  89. package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
  90. package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
  91. package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
  92. package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
  93. package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
  94. package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
  95. package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
  96. package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
  97. package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
  98. package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
  99. package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
  100. package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
  101. package/skills/d2h-utg-static-generate/SKILL.md +73 -73
  102. package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
  103. package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
  104. package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
  105. package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
  106. package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
  107. package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
  108. package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
  109. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
  110. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
  111. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
  112. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
  113. package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
  114. package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
  115. package/skills/maintaining-migration-report/SKILL.md +144 -144
  116. package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
  117. package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
  118. package/skills/managing-d2h-toolchain/SKILL.md +24 -24
  119. package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
  120. package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
  121. package/skills/managing-d2h-toolchain/toolchain.json +16 -16
  122. package/skills/native-library-substitution/SKILL.md +384 -384
  123. package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
  124. package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
  125. package/skills/preparing-migration-workspace/SKILL.md +131 -131
  126. package/skills/reviewing-migration-process/SKILL.md +62 -62
@@ -1,131 +1,131 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """第 1/3 步 —— 审计 ArkUI (.ets) 中欠约束尺寸的 Image() 组件。
4
-
5
- 成因
6
- Android UI 迁移到 HarmonyOS/ArkUI 时,转换器常保留 `.objectFit(...)` 却
7
- 丢掉用了 `android:layout_width/height="wrap_content"` 的 ImageView 的
8
- `.width()/.height()`。位图被拷进 `resources/base/media`(无密度限定符),
9
- 于是 HarmonyOS 按其原始像素尺寸当作 vp 渲染 —— 图标被放大(按源密度倍数,
10
- 如 xxhdpi = 3 倍),非方形图标还会比例失真。
11
-
12
- 会标记什么(一个 Image 的修饰链满足)
13
- - 既无 width 又无 height 且无 size() -> kind = NO-DIMS
14
- - 恰好只有 width/height 之一,且无 aspectRatio -> kind = ONE-DIM-NO-ASPECT
15
- (width('100%') + height('100%') = 满铺,视为有意为之。)
16
- 同时列出 ImageFit.Fill 用法(可能拉伸)供人工复核。
17
-
18
- 输出
19
- 打印摘要并写出 JSON 报告(默认 icon_report.json),供 icon_dims.py /
20
- icon_fix.py / icon_autofix.py 消费。
21
-
22
- 用法
23
- python icon_audit.py # 自动发现 */src/main/ets 根
24
- python icon_audit.py --roots entry/src/main/ets/pages entry/.../components
25
- python icon_audit.py --json icon_report.json
26
- """
27
- import os, re, json, argparse
28
-
29
-
30
- def discover_roots():
31
- """找出每一个 */src/main/ets 目录(entry + 任意 HSP/HAR 模块)。"""
32
- roots, skip = [], {'node_modules', 'oh_modules', 'build', '.git', '.hvigor', '.idea'}
33
- for base, dirs, _ in os.walk('.'):
34
- dirs[:] = [d for d in dirs if d not in skip]
35
- if base.replace('\\', '/').endswith('src/main/ets'):
36
- roots.append(base)
37
- return sorted(set(roots))
38
-
39
-
40
- def chain_window(lines, i):
41
- """第 i 行 Image 的修饰链 = 紧随其后、缩进更深的那些行。"""
42
- base = len(lines[i]) - len(lines[i].lstrip())
43
- out, j = [], i + 1
44
- while j < len(lines):
45
- ln = lines[j]
46
- if ln.strip() == '':
47
- j += 1
48
- continue
49
- if (len(ln) - len(ln.lstrip())) <= base:
50
- break
51
- out.append(ln)
52
- j += 1
53
- return '\n'.join(out)
54
-
55
-
56
- def audit(roots):
57
- flagged, fill_used, full_bleed, scanned = [], [], 0, 0
58
- for root in roots:
59
- for dp, _, files in os.walk(root):
60
- for fn in files:
61
- if not fn.endswith('.ets'):
62
- continue
63
- path = os.path.join(dp, fn).replace('\\', '/')
64
- lines = open(path, encoding='utf-8').read().split('\n')
65
- for i, ln in enumerate(lines):
66
- st = ln.lstrip()
67
- # 跳过注释行,避免被注释掉的 Image()/ImageView 引用误命中
68
- if st.startswith('*') or st.startswith('//') or st.startswith('/*'):
69
- continue
70
- code = ln.split('//')[0]
71
- if not re.search(r'\bImage\(', code):
72
- continue
73
- scanned += 1
74
- win = chain_window(lines, i)
75
- has_w, has_h = '.width(' in win, '.height(' in win
76
- has_size = '.size(' in win
77
- has_aspect = '.aspectRatio(' in win
78
- has_weight = '.layoutWeight(' in win
79
- w100, h100 = ".width('100%')" in win, ".height('100%')" in win
80
- fitm = re.search(r'ImageFit\.(\w+)', win)
81
- fit = fitm.group(1) if fitm else None
82
- iconm = re.search(r"(?:app|sys)\.media\.([A-Za-z0-9_]+)", code)
83
- icon = iconm.group(1) if iconm else None
84
- if 'ImageFit.Fill' in win:
85
- fill_used.append({'file': path, 'line': i + 1, 'icon': icon})
86
- if w100 and h100:
87
- full_bleed += 1
88
- continue
89
- ok = has_size or (has_w and has_h) or \
90
- ((has_w ^ has_h) and (has_aspect or has_weight))
91
- if ok:
92
- continue
93
- flagged.append({
94
- 'file': path, 'line': i + 1, 'icon': icon,
95
- 'dynamic': icon is None,
96
- 'kind': 'NO-DIMS' if not (has_w or has_h) else 'ONE-DIM-NO-ASPECT',
97
- 'fit': fit, 'expr': code.strip()[:60],
98
- })
99
- return {'scanned': scanned, 'flagged': flagged,
100
- 'fill': fill_used, 'full_bleed_count': full_bleed}
101
-
102
-
103
- def main():
104
- ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
105
- ap.add_argument('--roots', nargs='*', help='要扫描的 ets 目录(默认:自动发现 */src/main/ets)')
106
- ap.add_argument('--json', default='icon_report.json', help='报告输出路径')
107
- a = ap.parse_args()
108
- roots = a.roots or discover_roots()
109
- if not roots:
110
- print('未找到 ets 根。请从 HarmonyOS 工程根运行,或用 --roots 指定。')
111
- return
112
- print('扫描根目录:')
113
- for r in roots:
114
- print(' ' + r)
115
- rep = audit(roots)
116
- print(f'\n== 扫描 {rep["scanned"]} 个 Image() | 标记 {len(rep["flagged"])} '
117
- f'| Fill {len(rep["fill"])} | 满铺 {rep["full_bleed_count"]} ==\n')
118
- stat = [f for f in rep['flagged'] if not f['dynamic']]
119
- dyn = [f for f in rep['flagged'] if f['dynamic']]
120
- print(f'-- 欠约束的静态图标({len(stat)})--')
121
- for f in sorted(stat, key=lambda x: (x['file'], x['line'])):
122
- print(f" [{f['kind']:18}] fit={f['fit'] or '-':8} {os.path.basename(f['file'])}:{f['line']} {f['icon']}")
123
- print(f'\n-- 动态源图片({len(dyn)})-> 手工解析资源(见 SKILL.md)--')
124
- for f in sorted(dyn, key=lambda x: (x['file'], x['line'])):
125
- print(f" {os.path.basename(f['file'])}:{f['line']} {f['expr']}")
126
- json.dump(rep, open(a.json, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
127
- print(f'\n已写出 {a.json} -> 下一步:python icon_dims.py --report {a.json}')
128
-
129
-
130
- if __name__ == '__main__':
131
- main()
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """第 1/3 步 —— 审计 ArkUI (.ets) 中欠约束尺寸的 Image() 组件。
4
+
5
+ 成因
6
+ Android UI 迁移到 HarmonyOS/ArkUI 时,转换器常保留 `.objectFit(...)` 却
7
+ 丢掉用了 `android:layout_width/height="wrap_content"` 的 ImageView 的
8
+ `.width()/.height()`。位图被拷进 `resources/base/media`(无密度限定符),
9
+ 于是 HarmonyOS 按其原始像素尺寸当作 vp 渲染 —— 图标被放大(按源密度倍数,
10
+ 如 xxhdpi = 3 倍),非方形图标还会比例失真。
11
+
12
+ 会标记什么(一个 Image 的修饰链满足)
13
+ - 既无 width 又无 height 且无 size() -> kind = NO-DIMS
14
+ - 恰好只有 width/height 之一,且无 aspectRatio -> kind = ONE-DIM-NO-ASPECT
15
+ (width('100%') + height('100%') = 满铺,视为有意为之。)
16
+ 同时列出 ImageFit.Fill 用法(可能拉伸)供人工复核。
17
+
18
+ 输出
19
+ 打印摘要并写出 JSON 报告(默认 icon_report.json),供 icon_dims.py /
20
+ icon_fix.py / icon_autofix.py 消费。
21
+
22
+ 用法
23
+ python icon_audit.py # 自动发现 */src/main/ets 根
24
+ python icon_audit.py --roots entry/src/main/ets/pages entry/.../components
25
+ python icon_audit.py --json icon_report.json
26
+ """
27
+ import os, re, json, argparse
28
+
29
+
30
+ def discover_roots():
31
+ """找出每一个 */src/main/ets 目录(entry + 任意 HSP/HAR 模块)。"""
32
+ roots, skip = [], {'node_modules', 'oh_modules', 'build', '.git', '.hvigor', '.idea'}
33
+ for base, dirs, _ in os.walk('.'):
34
+ dirs[:] = [d for d in dirs if d not in skip]
35
+ if base.replace('\\', '/').endswith('src/main/ets'):
36
+ roots.append(base)
37
+ return sorted(set(roots))
38
+
39
+
40
+ def chain_window(lines, i):
41
+ """第 i 行 Image 的修饰链 = 紧随其后、缩进更深的那些行。"""
42
+ base = len(lines[i]) - len(lines[i].lstrip())
43
+ out, j = [], i + 1
44
+ while j < len(lines):
45
+ ln = lines[j]
46
+ if ln.strip() == '':
47
+ j += 1
48
+ continue
49
+ if (len(ln) - len(ln.lstrip())) <= base:
50
+ break
51
+ out.append(ln)
52
+ j += 1
53
+ return '\n'.join(out)
54
+
55
+
56
+ def audit(roots):
57
+ flagged, fill_used, full_bleed, scanned = [], [], 0, 0
58
+ for root in roots:
59
+ for dp, _, files in os.walk(root):
60
+ for fn in files:
61
+ if not fn.endswith('.ets'):
62
+ continue
63
+ path = os.path.join(dp, fn).replace('\\', '/')
64
+ lines = open(path, encoding='utf-8').read().split('\n')
65
+ for i, ln in enumerate(lines):
66
+ st = ln.lstrip()
67
+ # 跳过注释行,避免被注释掉的 Image()/ImageView 引用误命中
68
+ if st.startswith('*') or st.startswith('//') or st.startswith('/*'):
69
+ continue
70
+ code = ln.split('//')[0]
71
+ if not re.search(r'\bImage\(', code):
72
+ continue
73
+ scanned += 1
74
+ win = chain_window(lines, i)
75
+ has_w, has_h = '.width(' in win, '.height(' in win
76
+ has_size = '.size(' in win
77
+ has_aspect = '.aspectRatio(' in win
78
+ has_weight = '.layoutWeight(' in win
79
+ w100, h100 = ".width('100%')" in win, ".height('100%')" in win
80
+ fitm = re.search(r'ImageFit\.(\w+)', win)
81
+ fit = fitm.group(1) if fitm else None
82
+ iconm = re.search(r"(?:app|sys)\.media\.([A-Za-z0-9_]+)", code)
83
+ icon = iconm.group(1) if iconm else None
84
+ if 'ImageFit.Fill' in win:
85
+ fill_used.append({'file': path, 'line': i + 1, 'icon': icon})
86
+ if w100 and h100:
87
+ full_bleed += 1
88
+ continue
89
+ ok = has_size or (has_w and has_h) or \
90
+ ((has_w ^ has_h) and (has_aspect or has_weight))
91
+ if ok:
92
+ continue
93
+ flagged.append({
94
+ 'file': path, 'line': i + 1, 'icon': icon,
95
+ 'dynamic': icon is None,
96
+ 'kind': 'NO-DIMS' if not (has_w or has_h) else 'ONE-DIM-NO-ASPECT',
97
+ 'fit': fit, 'expr': code.strip()[:60],
98
+ })
99
+ return {'scanned': scanned, 'flagged': flagged,
100
+ 'fill': fill_used, 'full_bleed_count': full_bleed}
101
+
102
+
103
+ def main():
104
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
105
+ ap.add_argument('--roots', nargs='*', help='要扫描的 ets 目录(默认:自动发现 */src/main/ets)')
106
+ ap.add_argument('--json', default='icon_report.json', help='报告输出路径')
107
+ a = ap.parse_args()
108
+ roots = a.roots or discover_roots()
109
+ if not roots:
110
+ print('未找到 ets 根。请从 HarmonyOS 工程根运行,或用 --roots 指定。')
111
+ return
112
+ print('扫描根目录:')
113
+ for r in roots:
114
+ print(' ' + r)
115
+ rep = audit(roots)
116
+ print(f'\n== 扫描 {rep["scanned"]} 个 Image() | 标记 {len(rep["flagged"])} '
117
+ f'| Fill {len(rep["fill"])} | 满铺 {rep["full_bleed_count"]} ==\n')
118
+ stat = [f for f in rep['flagged'] if not f['dynamic']]
119
+ dyn = [f for f in rep['flagged'] if f['dynamic']]
120
+ print(f'-- 欠约束的静态图标({len(stat)})--')
121
+ for f in sorted(stat, key=lambda x: (x['file'], x['line'])):
122
+ print(f" [{f['kind']:18}] fit={f['fit'] or '-':8} {os.path.basename(f['file'])}:{f['line']} {f['icon']}")
123
+ print(f'\n-- 动态源图片({len(dyn)})-> 手工解析资源(见 SKILL.md)--')
124
+ for f in sorted(dyn, key=lambda x: (x['file'], x['line'])):
125
+ print(f" {os.path.basename(f['file'])}:{f['line']} {f['expr']}")
126
+ json.dump(rep, open(a.json, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
127
+ print(f'\n已写出 {a.json} -> 下一步:python icon_dims.py --report {a.json}')
128
+
129
+
130
+ if __name__ == '__main__':
131
+ main()
@@ -1,88 +1,88 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """全自动单入口 —— 一次跑完 审计→测量→修复→复审,输出单个机读 JSON,无任何人工 gate。
4
-
5
- 由调用方或 CI 在输入边界明确时调用;也可直接使用 --apply。
6
-
7
- 为什么无人工审核也安全:
8
- 尺寸 = 像素 ÷ 密度 是确定性计算;只动「无尺寸 / 单尺寸无 aspectRatio」的 Image,
9
- 绝不碰已带尺寸的;幂等(重复跑第二次 fixed_count=0);调用者负责保护已有工作区改动。
10
- 动态源图片 / 无 raster 项只记录、不修改,由调用方判断其是否阻塞当前请求。
11
-
12
- 用法
13
- python icon_autofix.py --apply --output-json icon_autofix.json
14
- python icon_autofix.py --apply --android-res <a>/app/src/main/res <a>/base/src/main/res \
15
- --output-json icon_autofix.json
16
- python icon_autofix.py --apply --density 3 --output-json icon_autofix.json # 无 Android 源
17
- """
18
- import os, json, argparse
19
- import sys
20
- from pathlib import Path
21
-
22
- # 允许从本脚本目录直接 import 同级模块
23
- sys.path.insert(0, str(Path(__file__).parent))
24
- from icon_audit import discover_roots, audit
25
- from icon_dims import find_media_dir, build_plan
26
- from icon_fix import apply_fixes
27
-
28
-
29
- def autofix(android_res, density, apply=True):
30
- """审计→测量→修复→复审,返回结构化结果 dict。"""
31
- roots = discover_roots()
32
- if not roots:
33
- return {'passed': True, 'skipped': True, 'reason': '未找到 */src/main/ets 根'}
34
- report = audit(roots)
35
- media = find_media_dir()
36
- names = sorted({f['icon'] for f in report['flagged'] if f.get('icon')})
37
- if media:
38
- plan, no_raster = build_plan(names, media, android_res, density)
39
- else:
40
- plan, no_raster = {}, names
41
- applied, dynamic = apply_fixes(plan, report, apply=apply)
42
- # 复审:验证收敛。有 raster 却仍被标记的静态图标 = 异常
43
- report2 = audit(roots)
44
- residual = [f for f in report2['flagged']
45
- if not f['dynamic'] and f.get('icon') in plan]
46
- return {
47
- 'passed': True, # 自愈永不阻断;执行异常时由外层 main 置 False
48
- 'media': media,
49
- 'scanned': report['scanned'],
50
- 'flagged_before': len(report['flagged']),
51
- 'flagged_after': len(report2['flagged']),
52
- 'fixed_count': len(applied),
53
- 'fixed': applied,
54
- 'dynamic_unresolved': dynamic, # Image(this.foo()) / 三元等,记录不修
55
- 'no_raster_unresolved': no_raster, # 纯 svg shape / 矢量图,记录不修
56
- 'fill_for_review': report.get('fill', []),
57
- 'residual_static': [{'file': f['file'], 'line': f['line'], 'icon': f['icon']} for f in residual],
58
- 'density_source': ('android-res' if android_res else f'fallback {density}x'),
59
- }
60
-
61
-
62
- def main():
63
- ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
64
- ap.add_argument('--project-root', default='.', help='HarmonyOS 工程根(脚本会 chdir 到此)')
65
- ap.add_argument('--android-res', nargs='*', default=[], help='Android res 根,用于逐图密度(缺省回退 --density)')
66
- ap.add_argument('--density', type=float, default=3.0, help='回退密度倍数(默认 3 = xxhdpi)')
67
- ap.add_argument('--apply', action='store_true', help='落盘(默认 dry-run,仅计算)')
68
- ap.add_argument('--output-json', required=True, help='结果 JSON 输出路径')
69
- a = ap.parse_args()
70
-
71
- out_path = Path(a.output_json).resolve() # chdir 前先解析为绝对路径
72
- android_res = [str(Path(r).resolve()) for r in a.android_res] or None
73
- if a.project_root and a.project_root != '.':
74
- os.chdir(a.project_root)
75
-
76
- try:
77
- out = autofix(android_res, a.density, apply=a.apply)
78
- except Exception as e:
79
- out = {'passed': False, 'error': str(e)}
80
-
81
- out_path.parent.mkdir(parents=True, exist_ok=True)
82
- out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding='utf-8')
83
- brief = {k: out.get(k) for k in ('passed', 'fixed_count', 'dynamic_unresolved', 'density_source', 'skipped', 'error')}
84
- print(json.dumps({k: v for k, v in brief.items() if v is not None}, ensure_ascii=False))
85
-
86
-
87
- if __name__ == '__main__':
88
- main()
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """全自动单入口 —— 一次跑完 审计→测量→修复→复审,输出单个机读 JSON,无任何人工 gate。
4
+
5
+ 由调用方或 CI 在输入边界明确时调用;也可直接使用 --apply。
6
+
7
+ 为什么无人工审核也安全:
8
+ 尺寸 = 像素 ÷ 密度 是确定性计算;只动「无尺寸 / 单尺寸无 aspectRatio」的 Image,
9
+ 绝不碰已带尺寸的;幂等(重复跑第二次 fixed_count=0);调用者负责保护已有工作区改动。
10
+ 动态源图片 / 无 raster 项只记录、不修改,由调用方判断其是否阻塞当前请求。
11
+
12
+ 用法
13
+ python icon_autofix.py --apply --output-json icon_autofix.json
14
+ python icon_autofix.py --apply --android-res <a>/app/src/main/res <a>/base/src/main/res \
15
+ --output-json icon_autofix.json
16
+ python icon_autofix.py --apply --density 3 --output-json icon_autofix.json # 无 Android 源
17
+ """
18
+ import os, json, argparse
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ # 允许从本脚本目录直接 import 同级模块
23
+ sys.path.insert(0, str(Path(__file__).parent))
24
+ from icon_audit import discover_roots, audit
25
+ from icon_dims import find_media_dir, build_plan
26
+ from icon_fix import apply_fixes
27
+
28
+
29
+ def autofix(android_res, density, apply=True):
30
+ """审计→测量→修复→复审,返回结构化结果 dict。"""
31
+ roots = discover_roots()
32
+ if not roots:
33
+ return {'passed': True, 'skipped': True, 'reason': '未找到 */src/main/ets 根'}
34
+ report = audit(roots)
35
+ media = find_media_dir()
36
+ names = sorted({f['icon'] for f in report['flagged'] if f.get('icon')})
37
+ if media:
38
+ plan, no_raster = build_plan(names, media, android_res, density)
39
+ else:
40
+ plan, no_raster = {}, names
41
+ applied, dynamic = apply_fixes(plan, report, apply=apply)
42
+ # 复审:验证收敛。有 raster 却仍被标记的静态图标 = 异常
43
+ report2 = audit(roots)
44
+ residual = [f for f in report2['flagged']
45
+ if not f['dynamic'] and f.get('icon') in plan]
46
+ return {
47
+ 'passed': True, # 自愈永不阻断;执行异常时由外层 main 置 False
48
+ 'media': media,
49
+ 'scanned': report['scanned'],
50
+ 'flagged_before': len(report['flagged']),
51
+ 'flagged_after': len(report2['flagged']),
52
+ 'fixed_count': len(applied),
53
+ 'fixed': applied,
54
+ 'dynamic_unresolved': dynamic, # Image(this.foo()) / 三元等,记录不修
55
+ 'no_raster_unresolved': no_raster, # 纯 svg shape / 矢量图,记录不修
56
+ 'fill_for_review': report.get('fill', []),
57
+ 'residual_static': [{'file': f['file'], 'line': f['line'], 'icon': f['icon']} for f in residual],
58
+ 'density_source': ('android-res' if android_res else f'fallback {density}x'),
59
+ }
60
+
61
+
62
+ def main():
63
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
64
+ ap.add_argument('--project-root', default='.', help='HarmonyOS 工程根(脚本会 chdir 到此)')
65
+ ap.add_argument('--android-res', nargs='*', default=[], help='Android res 根,用于逐图密度(缺省回退 --density)')
66
+ ap.add_argument('--density', type=float, default=3.0, help='回退密度倍数(默认 3 = xxhdpi)')
67
+ ap.add_argument('--apply', action='store_true', help='落盘(默认 dry-run,仅计算)')
68
+ ap.add_argument('--output-json', required=True, help='结果 JSON 输出路径')
69
+ a = ap.parse_args()
70
+
71
+ out_path = Path(a.output_json).resolve() # chdir 前先解析为绝对路径
72
+ android_res = [str(Path(r).resolve()) for r in a.android_res] or None
73
+ if a.project_root and a.project_root != '.':
74
+ os.chdir(a.project_root)
75
+
76
+ try:
77
+ out = autofix(android_res, a.density, apply=a.apply)
78
+ except Exception as e:
79
+ out = {'passed': False, 'error': str(e)}
80
+
81
+ out_path.parent.mkdir(parents=True, exist_ok=True)
82
+ out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding='utf-8')
83
+ brief = {k: out.get(k) for k in ('passed', 'fixed_count', 'dynamic_unresolved', 'density_source', 'skipped', 'error')}
84
+ print(json.dumps({k: v for k, v in brief.items() if v is not None}, ensure_ascii=False))
85
+
86
+
87
+ if __name__ == '__main__':
88
+ main()