@harmonyos-arkts/d2h 0.1.11 → 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.
- package/.claude-plugin/marketplace.json +15 -13
- package/.claude-plugin/plugin.json +13 -7
- package/AGENTS.md +5 -5
- package/README.md +212 -212
- package/agents/android-to-hmos-00-orchestrator.md +318 -318
- package/agents/d2h.md +157 -157
- package/dist/bin/install-opencode.js +30 -30
- package/docs/environment-check.md +86 -86
- package/install-opencode.sh +160 -160
- package/opencode/agents.json +16 -16
- package/opencode/vscode-d2h-package.json +7 -7
- package/opencode/vscode-d2h-plugin.mjs +29 -29
- package/package.json +54 -54
- package/schemas/android-source-manifest.schema.json +25 -25
- package/schemas/checkpoint-provenance.schema.json +67 -67
- package/schemas/final-acceptance.schema.json +29 -29
- package/schemas/managed-evidence-index.schema.json +15 -15
- package/schemas/managed-evidence.schema.json +114 -114
- package/schemas/migration-config.schema.json +51 -51
- package/schemas/migration-report-index.schema.json +39 -39
- package/schemas/migration-report-item.schema.json +51 -51
- package/schemas/migration-report-summary.schema.json +25 -25
- package/schemas/migration-status.schema.json +202 -202
- package/schemas/preflight.schema.json +60 -60
- package/schemas/source-order-audit.schema.json +16 -16
- package/schemas/source-provenance-event.schema.json +19 -19
- package/schemas/spec-app-shard.schema.json +45 -45
- package/schemas/spec-fact-corrections-shard.schema.json +13 -13
- package/schemas/spec-features-shard.schema.json +43 -43
- package/schemas/spec-index.schema.json +26 -26
- package/schemas/spec-interactions-shard.schema.json +40 -40
- package/schemas/spec-page.schema.json +98 -98
- package/schemas/spec-pages-index.schema.json +1 -1
- package/schemas/spec-unresolved-shard.schema.json +1 -1
- package/schemas/task-envelope.schema.json +109 -109
- package/schemas/task-plan.schema.json +341 -341
- package/scripts/check-environment.mjs +320 -320
- package/skills/android2hmos_resources_convert/SKILL.md +180 -180
- package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
- package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
- package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
- package/skills/arkts-app-identity/SKILL.md +238 -238
- package/skills/arkts-i18n/SKILL.md +496 -496
- package/skills/arkts-i18n/evals/evals.json +84 -84
- package/skills/arkts-i18n/references/code-examples.md +302 -302
- package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
- package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
- package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
- package/skills/arkts-i18n/references/language-codes.md +104 -104
- package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
- package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
- package/skills/arkts-i18n/references/v1-compat.md +244 -244
- package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
- package/skills/arkts-icon-sizing/SKILL.md +211 -211
- package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
- package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
- package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
- package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
- package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
- package/skills/d2h-hypium-forge/SKILL.md +309 -309
- package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
- package/skills/d2h-hypium-forge/references/official-api.md +135 -135
- package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
- package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
- package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
- package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
- package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
- package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
- package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
- package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
- package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
- package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
- package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
- package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
- package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
- package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
- package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
- package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
- package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
- package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
- package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
- package/skills/d2h-page-develop/SKILL.md +112 -112
- package/skills/d2h-page-develop/agents/openai.yaml +7 -7
- package/skills/d2h-page-spec-generate/SKILL.md +206 -206
- package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
- package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
- package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
- package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
- package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
- package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
- package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
- package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
- package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
- package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
- package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
- package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
- package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
- package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
- package/skills/d2h-utg-static-generate/SKILL.md +73 -73
- package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
- package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
- package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
- package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
- package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
- package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
- package/skills/maintaining-migration-report/SKILL.md +144 -144
- package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
- package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
- package/skills/managing-d2h-toolchain/SKILL.md +24 -24
- package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
- package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
- package/skills/managing-d2h-toolchain/toolchain.json +16 -16
- package/skills/native-library-substitution/SKILL.md +384 -384
- package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
- package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
- package/skills/preparing-migration-workspace/SKILL.md +131 -131
- 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()
|