@buaa_smat/hometrans 0.1.16 → 0.1.17
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/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# 安卓应用迁移鸿蒙指导
|
|
2
2
|
|
|
3
|
+
**HomeTrans工具用于辅助鸿蒙开发者完成原生安卓应用迁移到鸿蒙应用,为应用迁移的各个环节提供能力支持。**
|
|
4
|
+
|
|
5
|
+
**本文档为使用HomeTrans工具进行应用迁移的流程指导。**
|
|
3
6
|
|
|
4
7
|
[](LICENSE)
|
|
5
8
|
[](https://nodejs.org/en/download)
|
|
@@ -11,8 +14,6 @@
|
|
|
11
14
|
[](#选择本地editor)
|
|
12
15
|
[](#选择本地editor)
|
|
13
16
|
|
|
14
|
-
本文档为完整的Android到HarmonyOS迁移流程指导。
|
|
15
|
-
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
## 安装DevEco Studio以及Android Studio
|
|
@@ -95,12 +96,19 @@
|
|
|
95
96
|
|
|
96
97
|
## 步骤1:UI迁移
|
|
97
98
|
|
|
98
|
-
### 1.
|
|
99
|
+
### 1.1 UI迁移
|
|
99
100
|
|
|
100
101
|
> 📦 依赖:uv(必需,页面快照解析脚本经 `uv run` 运行,uv 自带 Python);Android Studio + 安卓真机/模拟器(可选,仅自动抓取页面快照时需要)。
|
|
101
102
|
|
|
103
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-batch-ui-align,并且传递相关参数)
|
|
104
|
+
|
|
105
|
+
斜杠调用方式举例
|
|
106
|
+
```
|
|
107
|
+
/hmos-batch-ui-align android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> ui_info_root=<页面快照目录> apk_path=<安卓apk路径>
|
|
108
|
+
```
|
|
109
|
+
自然语言调用方式举例
|
|
102
110
|
```
|
|
103
|
-
|
|
111
|
+
帮我使用skill:hmos-batch-ui-align完成安卓应用页面到鸿蒙应用的迁移,传递的参数是 android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> ui_info_root=<页面快照目录> apk_path=<安卓apk路径>
|
|
104
112
|
```
|
|
105
113
|
|
|
106
114
|
**输入参数**
|
|
@@ -111,6 +119,7 @@
|
|
|
111
119
|
| `harmony_project_dir` | **必选** | HarmonyOS项目输出路径 |
|
|
112
120
|
| `ui_info_root` | 可选 | 包含 `page_NNNN_ActivityName` 格式子目录的父目录(每子目录含 `meta.json` + `view.xml` + 可选 `screenshot.png`);不提供时自动通过adb抓取 |
|
|
113
121
|
| `pages` | 可选 | 显式列出的页面子集(不提供则处理所有页面) |
|
|
122
|
+
| `apk_path` | 可选 | Android APK文件路径;透传给内部的 `hmos-resources-convert`,提供后跳过Gradle构建,直接用apktool解包该APK提取资源 |
|
|
114
123
|
|
|
115
124
|
**输出产物**
|
|
116
125
|
|
|
@@ -120,13 +129,20 @@
|
|
|
120
129
|
| 资源文件 | HarmonyOS项目下 | 页面所需资源(含UI迁移内部触发的资源转换产物) |
|
|
121
130
|
| 批量转换报告 | HarmonyOS项目下 | 各页面转换结果、缺陷与统计 |
|
|
122
131
|
|
|
123
|
-
### 1.
|
|
132
|
+
### 1.2 UI对齐(按需)
|
|
124
133
|
|
|
125
134
|
> 📦 依赖:DevEco Studio、Android Studio、uv(必需,双端页面采集脚本经 `uv run` 运行,uv 自带 Python);安卓真机/模拟器 + 鸿蒙真机/模拟器(必需)。
|
|
126
135
|
|
|
136
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-incremental-ui-align,并且传递相关参数)
|
|
137
|
+
|
|
138
|
+
斜杠调用方式举例
|
|
127
139
|
```
|
|
128
140
|
/hmos-incremental-ui-align android_project_dir=<安卓工程路径> harmony_project_dir=<鸿蒙工程路径>
|
|
129
141
|
```
|
|
142
|
+
自然语言调用方式举例
|
|
143
|
+
```
|
|
144
|
+
帮我使用skill:hmos-incremental-ui-align完成安卓和鸿蒙应用的页面对齐,传递的参数是 android_project_dir=<安卓工程路径> harmony_project_dir=<鸿蒙工程路径>
|
|
145
|
+
```
|
|
130
146
|
|
|
131
147
|
**输入参数**
|
|
132
148
|
|
|
@@ -146,7 +162,7 @@
|
|
|
146
162
|
|
|
147
163
|
---
|
|
148
164
|
|
|
149
|
-
## 步骤2:生成需求规格
|
|
165
|
+
## 步骤2:生成需求规格
|
|
150
166
|
|
|
151
167
|
> 📦 依赖:homegraph(必需)。
|
|
152
168
|
|
|
@@ -179,9 +195,16 @@
|
|
|
179
195
|
8,在播放界面隐藏:开关 默认关闭 打开后如果切换到播放页,则不在悬浮窗区域显示歌词
|
|
180
196
|
```
|
|
181
197
|
|
|
198
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-spec-generate,并且传递相关参数)
|
|
199
|
+
|
|
200
|
+
斜杠调用方式举例
|
|
182
201
|
```
|
|
183
202
|
/hmos-spec-generate requirement_description_file=<需求描述文件路径> android_project_dir=<安卓项目路径> spec_output_dir=<规格输出目录>
|
|
184
203
|
```
|
|
204
|
+
自然语言调用方式举例
|
|
205
|
+
```
|
|
206
|
+
帮我使用skill:hmos-spec-generate生成需求spec文档,传递的参数是 requirement_description_file=<需求描述文件路径> android_project_dir=<安卓项目路径> spec_output_dir=<规格输出目录>
|
|
207
|
+
```
|
|
185
208
|
|
|
186
209
|
**输入参数**
|
|
187
210
|
|
|
@@ -200,7 +223,7 @@
|
|
|
200
223
|
|
|
201
224
|
---
|
|
202
225
|
|
|
203
|
-
## 步骤3:逻辑代码转换流水线
|
|
226
|
+
## 步骤3:逻辑代码转换流水线
|
|
204
227
|
|
|
205
228
|
> 📦 依赖:DevEco Studio(必需,构建/评审修复);uv + 鸿蒙真机(集成测试阶段需要,可经 `skip_test` 跳过;uv 经 `uv run` 运行 `self_test_runner.py` 并自带 Python)。
|
|
206
229
|
|
|
@@ -208,9 +231,16 @@
|
|
|
208
231
|
> 1. 如果执行集成测试,需要**编写测试用例文档**输出前置测试用例(非必须,用于测试用例的环境准备等)通过pre_test_case_path参数传递,测试用例通过test_case_path参数传递。
|
|
209
232
|
> 2. 如果执行集成测试,需要**连接HarmonyOS真机**并且在DevEco Studio中[**配置签名**](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/networkboost-preparations#%E9%85%8D%E7%BD%AE%E7%AD%BE%E5%90%8D)。
|
|
210
233
|
|
|
234
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-convert-pipeline,并且传递相关参数)
|
|
235
|
+
|
|
236
|
+
斜杠调用方式举例
|
|
211
237
|
```
|
|
212
238
|
/hmos-convert-pipeline android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> spec_file_path=<需求规格文档路径>
|
|
213
239
|
```
|
|
240
|
+
自然语言调用方式举例
|
|
241
|
+
```
|
|
242
|
+
帮我使用skill:hmos-convert-pipeline完成逻辑代码开发,代码检视,集成测试,传递的参数是 android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> spec_file_path=<需求规格文档路径>
|
|
243
|
+
```
|
|
214
244
|
|
|
215
245
|
**输入参数**
|
|
216
246
|
|
|
@@ -240,7 +270,7 @@
|
|
|
240
270
|
|
|
241
271
|
---
|
|
242
272
|
|
|
243
|
-
## 步骤4:人工验收
|
|
273
|
+
## 步骤4:人工验收
|
|
244
274
|
|
|
245
275
|
**输入**
|
|
246
276
|
|
|
@@ -264,15 +294,22 @@
|
|
|
264
294
|
|
|
265
295
|
以下能力包含在迁移流程中也可按需独立调用。
|
|
266
296
|
|
|
267
|
-
### 资源转换
|
|
297
|
+
### 资源转换
|
|
268
298
|
|
|
269
299
|
> 📦 依赖:DevEco Studio中的java(可选,缺失时回退读取源码 `res/`)。
|
|
270
300
|
|
|
271
301
|
> ⚠️ 提示:如果步骤1采用 **UI迁移**(`hmos-batch-ui-align`),则跳过当前能力(UI迁移内部已包含资源转换)。
|
|
272
302
|
|
|
303
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-resources-convert,并且传递相关参数)
|
|
304
|
+
|
|
305
|
+
斜杠调用方式举例
|
|
273
306
|
```
|
|
274
307
|
/hmos-resources-convert android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> resource_mapping_path=<资源映射文档路径>
|
|
275
308
|
```
|
|
309
|
+
自然语言调用方式举例
|
|
310
|
+
```
|
|
311
|
+
帮我使用skill:hmos-resources-convert完成安卓应用到鸿蒙应用的资源转换,传递的参数是 android_project_dir=<安卓项目路径> harmony_project_dir=<鸿蒙项目路径> resource_mapping_path=<资源映射文档路径>
|
|
312
|
+
```
|
|
276
313
|
|
|
277
314
|
**输入参数**
|
|
278
315
|
|
|
@@ -281,7 +318,7 @@
|
|
|
281
318
|
| `android_project_dir` | **必选** | Android项目根目录路径(含 `build.gradle` 或 `build.gradle.kts`) |
|
|
282
319
|
| `harmony_project_dir` | **必选** | HarmonyOS项目输出路径 |
|
|
283
320
|
| `resource_mapping_path` | **必选** | Android ↔ HarmonyOS资源映射文档的完整输出路径(`.md`) |
|
|
284
|
-
| `apk_path` | 可选 | Android APK文件路径;提供后跳过Gradle
|
|
321
|
+
| `apk_path` | 可选 | Android APK文件路径;提供后跳过Gradle构建,直接用apktool解包该APK提取资源 |
|
|
285
322
|
|
|
286
323
|
**输出产物**
|
|
287
324
|
|
|
@@ -291,13 +328,20 @@
|
|
|
291
328
|
| 资源映射文档 | `resource_mapping_path` | Android ↔ HarmonyOS资源条目映射(`.md`) |
|
|
292
329
|
| 转换报告 | HarmonyOS项目下 | 资源转换过程与统计 |
|
|
293
330
|
|
|
294
|
-
### 集成测试(
|
|
331
|
+
### 集成测试(已包含在步骤3的流水线,也可以单独运行)
|
|
295
332
|
|
|
296
333
|
> 📦 依赖:DevEco Studio(必需,真机调试);uv(必需,AutoTest在uv下运行);鸿蒙真机(必需)。
|
|
297
334
|
|
|
335
|
+
打开本地editor(claude code,open code等),在会话中通过 `斜杠/ `或者 `自然语言 `的方式调用skill--hmos-integration-test,并且传递相关参数)
|
|
336
|
+
|
|
337
|
+
斜杠调用方式举例
|
|
298
338
|
```
|
|
299
339
|
/hmos-integration-test test_case_path=<测试用例路径> hap_path=<签名HAP路径>
|
|
300
340
|
```
|
|
341
|
+
自然语言调用方式举例
|
|
342
|
+
```
|
|
343
|
+
帮我使用skill:hmos-integration-test基于测试用例完成鸿蒙应用的集成测试,传递的参数是 test_case_path=<测试用例路径> hap_path=<签名HAP路径>
|
|
344
|
+
```
|
|
301
345
|
|
|
302
346
|
**输入参数**
|
|
303
347
|
|
package/dist/cli/init.js
CHANGED
|
@@ -980,6 +980,61 @@ async function installUv() {
|
|
|
980
980
|
console.log(chalk.gray(' Open a new terminal (PATH refresh), then verify: uv --version'));
|
|
981
981
|
}
|
|
982
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Editor name (as in config.json / defaultEditors) → homegraph `install --target`
|
|
985
|
+
* slug. homegraph ships its own MCP-into-editor installer; after the homegraph CLI
|
|
986
|
+
* is installed globally we invoke it so the homegraph MCP server is registered into
|
|
987
|
+
* the same editors HomeTrans is configuring. Scope intentionally matches init's
|
|
988
|
+
* supported editors — editors without a mapping (a user-added custom editor, or one
|
|
989
|
+
* homegraph doesn't support) are simply skipped.
|
|
990
|
+
*/
|
|
991
|
+
const HOMEGRAPH_TARGET_BY_EDITOR = {
|
|
992
|
+
'Claude Code': 'claude',
|
|
993
|
+
Cursor: 'cursor',
|
|
994
|
+
Codex: 'codex',
|
|
995
|
+
OpenCode: 'opencode',
|
|
996
|
+
'DevEco Code': 'deveco',
|
|
997
|
+
CodeBuddy: 'codebuddy',
|
|
998
|
+
};
|
|
999
|
+
/**
|
|
1000
|
+
* Register the homegraph MCP server into the selected editors via the homegraph
|
|
1001
|
+
* CLI (`homegraph install --target=<...> --location=global --yes`). Runs after
|
|
1002
|
+
* installNodeDependencies() so the homegraph binary is present. A single invocation
|
|
1003
|
+
* with a comma-joined target list configures every selected editor at once. Never
|
|
1004
|
+
* throws — failures are reported and `ht init` continues. On Windows the global npm
|
|
1005
|
+
* bin is a `.cmd` shim, so the command runs through the shell there (same as
|
|
1006
|
+
* homegraphVersion()).
|
|
1007
|
+
*/
|
|
1008
|
+
async function installHomegraphToEditors(selectedEditors) {
|
|
1009
|
+
const targets = selectedEditors
|
|
1010
|
+
.map((name) => HOMEGRAPH_TARGET_BY_EDITOR[name])
|
|
1011
|
+
.filter((t) => Boolean(t));
|
|
1012
|
+
if (targets.length === 0)
|
|
1013
|
+
return;
|
|
1014
|
+
const targetArg = `--target=${targets.join(',')}`;
|
|
1015
|
+
console.log(chalk.gray(' homegraph MCP -> editors'));
|
|
1016
|
+
// Needs the homegraph CLI on PATH. A fresh `npm install -g` may not be visible
|
|
1017
|
+
// until a PATH refresh, but homegraphVersion() probes through the shell so the
|
|
1018
|
+
// shim is usually resolvable within the same `ht init` run.
|
|
1019
|
+
if (homegraphVersion() === null) {
|
|
1020
|
+
console.log(chalk.yellow(' ! homegraph not available — skipping MCP registration into editors.'));
|
|
1021
|
+
console.log(chalk.gray(` After homegraph is installed, run: homegraph install ${targetArg} --location=global --yes`));
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
console.log(chalk.gray(` Running: homegraph install ${targetArg} --location=global --yes ...`));
|
|
1025
|
+
try {
|
|
1026
|
+
execFileSync('homegraph', ['install', targetArg, '--location=global', '--yes'], {
|
|
1027
|
+
stdio: 'inherit',
|
|
1028
|
+
timeout: 5 * 60 * 1000,
|
|
1029
|
+
shell: process.platform === 'win32',
|
|
1030
|
+
});
|
|
1031
|
+
console.log(chalk.green(` + homegraph MCP registered into: ${targets.join(', ')}`));
|
|
1032
|
+
}
|
|
1033
|
+
catch (err) {
|
|
1034
|
+
console.log(chalk.red(` ! homegraph install failed: ${err.message}`));
|
|
1035
|
+
console.log(chalk.gray(` Re-run manually: homegraph install ${targetArg} --location=global --yes`));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
983
1038
|
export async function initCommand(options = {}) {
|
|
984
1039
|
ensureChalkColor();
|
|
985
1040
|
const banner = figlet.textSync('HomeTrans', { font: 'Standard' });
|
|
@@ -1129,6 +1184,15 @@ export async function initCommand(options = {}) {
|
|
|
1129
1184
|
abortInit();
|
|
1130
1185
|
console.log(chalk.yellow(` ! homegraph / arkanalyzer install skipped: ${err.message}`));
|
|
1131
1186
|
}
|
|
1187
|
+
// With homegraph installed, register its MCP server into the editors the user
|
|
1188
|
+
// selected, using homegraph's own installer. Scoped to the selected editors so
|
|
1189
|
+
// it stays consistent with what HomeTrans configures below.
|
|
1190
|
+
try {
|
|
1191
|
+
await installHomegraphToEditors(selectedEditors);
|
|
1192
|
+
}
|
|
1193
|
+
catch (err) {
|
|
1194
|
+
console.log(chalk.yellow(` ! homegraph editor registration skipped: ${err.message}`));
|
|
1195
|
+
}
|
|
1132
1196
|
try {
|
|
1133
1197
|
await installUv();
|
|
1134
1198
|
}
|
package/dist/cli/uninstall.js
CHANGED
|
@@ -22,6 +22,12 @@ import { removeEnvVars } from './env-vars.js';
|
|
|
22
22
|
const execFileAsync = promisify(execFile);
|
|
23
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
24
|
const __dirname = path.dirname(__filename);
|
|
25
|
+
/**
|
|
26
|
+
* Global Node CLIs that `ht init` installs as a pair (`npm install -g homegraph
|
|
27
|
+
* arkanalyzer`). `ht uninstall` removes the same pair so the two commands stay
|
|
28
|
+
* symmetric.
|
|
29
|
+
*/
|
|
30
|
+
const NODE_DEPS = ['homegraph', 'arkanalyzer'];
|
|
25
31
|
/**
|
|
26
32
|
* Machine env var names that `ht init` may have set and that have a non-empty
|
|
27
33
|
* value in config.env: the SDK paths / tool dir plus the mirrored unified model
|
|
@@ -72,6 +78,57 @@ async function listBundledAgentEntries(agentsRoot) {
|
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
80
|
/* ------------------------------------------------------------------ */
|
|
81
|
+
/* Global npm dependency removal */
|
|
82
|
+
/* ------------------------------------------------------------------ */
|
|
83
|
+
/**
|
|
84
|
+
* Is `npm` runnable? On Windows the global npm bin is a `.cmd` shim, so the
|
|
85
|
+
* command must run through the shell there (same as init's homegraphVersion()).
|
|
86
|
+
*/
|
|
87
|
+
async function npmAvailable() {
|
|
88
|
+
try {
|
|
89
|
+
await execFileAsync('npm', ['--version'], {
|
|
90
|
+
timeout: 15000,
|
|
91
|
+
shell: process.platform === 'win32',
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Does `homegraph --version` return normally (exit 0)? Used as the presence
|
|
101
|
+
* probe for the homegraph/arkanalyzer pair — the symmetric counterpart to
|
|
102
|
+
* init's idempotency check, which skips the install when homegraph is already
|
|
103
|
+
* present.
|
|
104
|
+
*/
|
|
105
|
+
async function homegraphInstalled() {
|
|
106
|
+
try {
|
|
107
|
+
await execFileAsync('homegraph', ['--version'], {
|
|
108
|
+
timeout: 15000,
|
|
109
|
+
shell: process.platform === 'win32',
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Decide which global npm packages to remove. Returns the homegraph/arkanalyzer
|
|
119
|
+
* pair when homegraph is detected on the machine (mirroring init, which installs
|
|
120
|
+
* them together), or an empty list when npm is unavailable or homegraph is not
|
|
121
|
+
* present — so a machine that never ran `ht init`'s dependency install shows
|
|
122
|
+
* nothing to remove here.
|
|
123
|
+
*/
|
|
124
|
+
async function detectNodeDepsToRemove() {
|
|
125
|
+
if (!(await npmAvailable()))
|
|
126
|
+
return [];
|
|
127
|
+
if (!(await homegraphInstalled()))
|
|
128
|
+
return [];
|
|
129
|
+
return [...NODE_DEPS];
|
|
130
|
+
}
|
|
131
|
+
/* ------------------------------------------------------------------ */
|
|
75
132
|
/* MCP config removal helpers */
|
|
76
133
|
/* ------------------------------------------------------------------ */
|
|
77
134
|
function detectIndentation(raw) {
|
|
@@ -303,6 +360,7 @@ function renderDiff(oldContent, newContent, context = 3) {
|
|
|
303
360
|
function isPlanEmpty(plan) {
|
|
304
361
|
return (plan.deletions.length === 0 &&
|
|
305
362
|
plan.modifications.length === 0 &&
|
|
363
|
+
plan.npmDeps.length === 0 &&
|
|
306
364
|
plan.envVars.length === 0 &&
|
|
307
365
|
plan.configDir === null);
|
|
308
366
|
}
|
|
@@ -329,6 +387,14 @@ function renderPlan(plan) {
|
|
|
329
387
|
console.log('');
|
|
330
388
|
}
|
|
331
389
|
}
|
|
390
|
+
if (plan.npmDeps.length > 0) {
|
|
391
|
+
console.log(chalk.red.bold(` Global npm packages to remove (${plan.npmDeps.length}):`));
|
|
392
|
+
console.log(chalk.gray(` (will run: npm uninstall -g ${plan.npmDeps.join(' ')})`));
|
|
393
|
+
for (const dep of plan.npmDeps) {
|
|
394
|
+
console.log(` ${chalk.red('-')} ${dep}`);
|
|
395
|
+
}
|
|
396
|
+
console.log('');
|
|
397
|
+
}
|
|
332
398
|
if (plan.envVars.length > 0) {
|
|
333
399
|
console.log(chalk.red.bold(` Environment variables to remove (${plan.envVars.length}):`));
|
|
334
400
|
console.log(chalk.gray(process.platform === 'win32'
|
|
@@ -351,6 +417,7 @@ function renderPlan(plan) {
|
|
|
351
417
|
async function executePlan(plan) {
|
|
352
418
|
let deleted = 0;
|
|
353
419
|
let modified = 0;
|
|
420
|
+
let npmNote = null;
|
|
354
421
|
let envNote = null;
|
|
355
422
|
for (const mod of plan.modifications) {
|
|
356
423
|
await fs.writeFile(mod.absPath, mod.newContent, 'utf-8');
|
|
@@ -366,6 +433,22 @@ async function executePlan(plan) {
|
|
|
366
433
|
// codex CLI unavailable or failed — TOML path already handled above
|
|
367
434
|
}
|
|
368
435
|
}
|
|
436
|
+
// Remove the global npm CLIs installed by `ht init` (homegraph + arkanalyzer).
|
|
437
|
+
// Best-effort: a failure is reported in the note but never aborts the rest of
|
|
438
|
+
// the uninstall.
|
|
439
|
+
if (plan.npmDeps.length > 0) {
|
|
440
|
+
console.log(chalk.gray(` Running: npm uninstall -g ${plan.npmDeps.join(' ')} ...`));
|
|
441
|
+
try {
|
|
442
|
+
await execFileAsync('npm', ['uninstall', '-g', ...plan.npmDeps], {
|
|
443
|
+
timeout: 5 * 60 * 1000,
|
|
444
|
+
shell: process.platform === 'win32',
|
|
445
|
+
});
|
|
446
|
+
npmNote = `removed ${plan.npmDeps.join(', ')}`;
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
npmNote = `failed (${err.message}) — remove manually: npm uninstall -g ${plan.npmDeps.join(' ')}`;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
369
452
|
for (const del of plan.deletions) {
|
|
370
453
|
try {
|
|
371
454
|
await fs.rm(del.absPath, { recursive: true, force: true });
|
|
@@ -388,7 +471,7 @@ async function executePlan(plan) {
|
|
|
388
471
|
// best-effort
|
|
389
472
|
}
|
|
390
473
|
}
|
|
391
|
-
return { deleted, modified, envNote };
|
|
474
|
+
return { deleted, modified, npmNote, envNote };
|
|
392
475
|
}
|
|
393
476
|
/* ------------------------------------------------------------------ */
|
|
394
477
|
/* Entry point */
|
|
@@ -408,12 +491,16 @@ export async function uninstallCommand() {
|
|
|
408
491
|
deletions: [],
|
|
409
492
|
modifications: [],
|
|
410
493
|
codexRemove: false,
|
|
494
|
+
npmDeps: [],
|
|
411
495
|
envVars: [],
|
|
412
496
|
configDir: null,
|
|
413
497
|
};
|
|
414
498
|
for (const editor of editors) {
|
|
415
499
|
await buildPlanForEditor(editor, skillNames, agentEntries, plan);
|
|
416
500
|
}
|
|
501
|
+
// Global npm CLIs installed by `ht init` (homegraph + arkanalyzer) — detected
|
|
502
|
+
// the same way init decides whether to (re)install them.
|
|
503
|
+
plan.npmDeps = await detectNodeDepsToRemove();
|
|
417
504
|
// Env vars to remove come from config.json (`env` + the unified model
|
|
418
505
|
// fields); the ~/.hometrans dir (which holds config.json) is deleted last.
|
|
419
506
|
plan.envVars = plannedEnvVarNames(config);
|
|
@@ -451,6 +538,9 @@ export async function uninstallCommand() {
|
|
|
451
538
|
const summary = await executePlan(plan);
|
|
452
539
|
console.log('');
|
|
453
540
|
console.log(chalk.green(` Uninstalled: ${summary.deleted} entries deleted, ${summary.modified} files modified.`));
|
|
541
|
+
if (summary.npmNote) {
|
|
542
|
+
console.log(chalk.green(` Global npm packages: ${summary.npmNote}`));
|
|
543
|
+
}
|
|
454
544
|
if (summary.envNote) {
|
|
455
545
|
console.log(chalk.green(` Environment variables: ${summary.envNote}`));
|
|
456
546
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buaa_smat/hometrans",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "HomeTrans (Android-to-HarmonyOS) skill + agent installer. Run `ht init` to distribute conversion skills and subagents into AI editors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@ Extract 4 paths from the user's message (natural language, no fixed format):
|
|
|
27
27
|
| `harmony_project_dir` | HarmonyOS target project root | "target Harmony project", "鸿蒙项目", "output project" |
|
|
28
28
|
| `ui_info_root` | **Parent directory containing all `page_NNNN_*` subdirectories** | "page screenshots and view tree", "page snapshots", "page folder" |
|
|
29
29
|
| `pages` (optional) | User-explicitly listed page subset | When user lists `1. .../page_0001_X` ... take these |
|
|
30
|
+
| `apk_path` (optional) | Full path of the Android APK corresponding to `android_project_dir`. Passed through to `hmos-resources-convert` in Step 2 to skip the Gradle build and decompile this APK directly with apktool to extract resources | "APK path", "apk文件", "安卓apk" |
|
|
30
31
|
|
|
31
32
|
**`references_dir` and MVVM document directories always use skill-bundled relative paths** — **do not** accept user-provided overrides:
|
|
32
33
|
|
|
@@ -41,6 +42,7 @@ Invoke the `hmos-resources-convert` skill with the following parameters:
|
|
|
41
42
|
1. **Android project path** = `android_project_dir`
|
|
42
43
|
2. **HarmonyOS project output path** = `harmony_project_dir`
|
|
43
44
|
3. **resource_mapping_path** = generate the resource mapping document under `harmony_project_dir` at `${harmony_project_dir}/resource_mapping.md`
|
|
45
|
+
4. **Android apk path** = `apk_path` — **only if the user provided it**. When passed through, `hmos-resources-convert` skips the Gradle build and decompiles this APK directly with apktool to extract resources. Omit this parameter entirely when `apk_path` was not provided.
|
|
44
46
|
|
|
45
47
|
This skill batch-converts Android resources (strings, colors, drawables, images, etc.) to HarmonyOS resource format and generates the mapping document.
|
|
46
48
|
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
@echo off
|
|
2
|
-
setlocal
|
|
3
|
-
set BASENAME=apktool_
|
|
4
|
-
chcp 65001 2>nul >nul
|
|
5
|
-
|
|
6
|
-
set java_exe=java.exe
|
|
7
|
-
|
|
8
|
-
if defined JAVA_HOME (
|
|
9
|
-
set "java_exe=%JAVA_HOME%\bin\java.exe"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
rem Find the highest version .jar available in the same directory as the script
|
|
13
|
-
setlocal EnableDelayedExpansion
|
|
14
|
-
pushd "%~dp0"
|
|
15
|
-
if exist apktool.jar (
|
|
16
|
-
set BASENAME=apktool
|
|
17
|
-
goto skipversioned
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
set BASENAME=apktool
|
|
21
|
-
set max_major=0
|
|
22
|
-
set max_minor=0
|
|
23
|
-
set max_patch=0
|
|
24
|
-
|
|
25
|
-
rem Loop through all versioned .jar files matching the basename
|
|
26
|
-
for %%F in (%BASENAME%*.jar) do (
|
|
27
|
-
set "filename=%%~nF"
|
|
28
|
-
|
|
29
|
-
rem Extract version part (apktool-X.Y.Z)
|
|
30
|
-
for /f "tokens=2 delims=_-" %%A in ("!filename!") do (
|
|
31
|
-
for /f "tokens=1,2,3 delims=." %%B in ("%%A") do (
|
|
32
|
-
set "major=%%B"
|
|
33
|
-
set "minor=%%C"
|
|
34
|
-
set "patch=%%D"
|
|
35
|
-
|
|
36
|
-
rem Set Default minor/patch to 0
|
|
37
|
-
if "!minor!"=="" set "minor=0"
|
|
38
|
-
if "!patch!"=="" set "patch=0"
|
|
39
|
-
|
|
40
|
-
rem Compare major version
|
|
41
|
-
if !major! gtr !max_major! (
|
|
42
|
-
set "max_major=!major!"
|
|
43
|
-
set "max_minor=!minor!"
|
|
44
|
-
set "max_patch=!patch!"
|
|
45
|
-
) else if !major! == !max_major! (
|
|
46
|
-
rem Compare minor version
|
|
47
|
-
if !minor! gtr !max_minor! (
|
|
48
|
-
set "max_minor=!minor!"
|
|
49
|
-
set "max_patch=!patch!"
|
|
50
|
-
) else if !minor! == !max_minor! (
|
|
51
|
-
rem Compare patch version
|
|
52
|
-
if !patch! gtr !max_patch! (
|
|
53
|
-
set "max_patch=!patch!"
|
|
54
|
-
)
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
rem Construct full version string
|
|
62
|
-
set "max=_!max_major!.!max_minor!.!max_patch!"
|
|
63
|
-
|
|
64
|
-
:skipversioned
|
|
65
|
-
popd
|
|
66
|
-
setlocal DisableDelayedExpansion
|
|
67
|
-
|
|
68
|
-
rem Find out if the commandline is a parameterless .jar or directory, for fast unpack/repack
|
|
69
|
-
if "%~1"=="" goto load
|
|
70
|
-
if not "%~2"=="" goto load
|
|
71
|
-
set ATTR=%~a1
|
|
72
|
-
if "%ATTR:~0,1%"=="d" (
|
|
73
|
-
rem Directory, rebuild
|
|
74
|
-
set fastCommand=b
|
|
75
|
-
)
|
|
76
|
-
if "%ATTR:~0,1%"=="-" if "%~x1"==".apk" (
|
|
77
|
-
rem APK file, unpack
|
|
78
|
-
set fastCommand=d
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
:load
|
|
82
|
-
"%java_exe%" -jar -Xmx1024M -Duser.language=en -Dfile.encoding=UTF8 -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true "%~dp0%BASENAME%%max%.jar" %fastCommand% %*
|
|
83
|
-
|
|
84
|
-
rem Pause when ran non interactively
|
|
85
|
-
for %%i in (%cmdcmdline%) do if /i "%%~i"=="/c" pause & exit /b
|