@expo-harmony/cli 55.0.26-harmony.7 → 55.0.26-harmony.9

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 (117) hide show
  1. package/README.md +74 -46
  2. package/build/bin/expo-harmony.js +1 -1
  3. package/build/buildHap/build.d.ts +3 -10
  4. package/build/buildHap/build.js +11 -69
  5. package/build/buildHap/index.d.ts +2 -0
  6. package/build/buildHap/index.js +19 -0
  7. package/build/cli.d.ts +3 -3
  8. package/build/cli.js +21 -187
  9. package/build/command.d.ts +9 -0
  10. package/build/command.js +20 -0
  11. package/build/doctor/doctor.d.ts +1 -1
  12. package/build/doctor/doctor.js +64 -56
  13. package/build/doctor/index.d.ts +2 -0
  14. package/build/doctor/index.js +16 -0
  15. package/build/exportEmbed/export.d.ts +2 -2
  16. package/build/exportEmbed/export.js +53 -49
  17. package/build/exportEmbed/index.d.ts +2 -0
  18. package/build/exportEmbed/index.js +19 -0
  19. package/build/file.d.ts +2 -1
  20. package/build/file.js +18 -8
  21. package/build/help.d.ts +1 -0
  22. package/build/help.js +36 -0
  23. package/build/index.d.ts +5 -3
  24. package/build/index.js +6 -5
  25. package/build/internal/prebuild.d.ts +3 -0
  26. package/build/internal/prebuild.js +9 -0
  27. package/build/log.d.ts +4 -0
  28. package/build/log.js +6 -0
  29. package/build/modules/index.d.ts +2 -0
  30. package/build/modules/index.js +16 -0
  31. package/build/native/bare.d.ts +4 -0
  32. package/build/native/bare.js +130 -0
  33. package/build/native/build.d.ts +15 -0
  34. package/build/native/build.js +67 -0
  35. package/build/{run → native}/cache.d.ts +2 -2
  36. package/build/{run → native}/cache.js +8 -8
  37. package/build/native/hvigor.d.ts +2 -0
  38. package/build/native/hvigor.js +77 -0
  39. package/build/native/install.d.ts +5 -0
  40. package/build/native/install.js +13 -0
  41. package/build/native/prepareProject.d.ts +11 -0
  42. package/build/native/prepareProject.js +68 -0
  43. package/build/native/project.d.ts +8 -0
  44. package/build/native/project.js +58 -0
  45. package/build/native/toolchain.d.ts +16 -0
  46. package/build/{tools.js → native/toolchain.js} +1 -57
  47. package/build/native/types.d.ts +47 -0
  48. package/build/native/types.js +2 -0
  49. package/build/prebuild/check.d.ts +7 -3
  50. package/build/prebuild/check.js +26 -20
  51. package/build/prebuild/clean.js +5 -5
  52. package/build/prebuild/index.d.ts +2 -0
  53. package/build/prebuild/index.js +51 -0
  54. package/build/prebuild/prebuild.js +11 -3
  55. package/build/prebuild/template.js +2 -2
  56. package/build/process.d.ts +11 -1
  57. package/build/process.js +16 -0
  58. package/build/profile.d.ts +1 -0
  59. package/build/profile.js +12 -0
  60. package/build/projectLock.js +7 -2
  61. package/build/run/devices.d.ts +1 -1
  62. package/build/run/devices.js +15 -10
  63. package/build/run/emulators.d.ts +1 -1
  64. package/build/run/emulators.js +2 -5
  65. package/build/run/index.d.ts +2 -0
  66. package/build/run/index.js +25 -0
  67. package/build/run/metro.js +1 -1
  68. package/build/run/run.d.ts +4 -10
  69. package/build/run/run.js +45 -90
  70. package/build/start/index.d.ts +2 -0
  71. package/build/start/index.js +33 -0
  72. package/package.json +14 -4
  73. package/src/bin/expo-harmony.ts +2 -2
  74. package/src/buildHap/build.ts +13 -105
  75. package/src/buildHap/index.ts +19 -0
  76. package/src/cli.ts +24 -227
  77. package/src/command.ts +24 -0
  78. package/src/doctor/doctor.ts +63 -61
  79. package/src/doctor/index.ts +16 -0
  80. package/src/exportEmbed/export.ts +64 -55
  81. package/src/exportEmbed/index.ts +17 -0
  82. package/src/file.ts +20 -11
  83. package/src/help.ts +33 -0
  84. package/src/index.ts +5 -3
  85. package/src/internal/prebuild.ts +3 -0
  86. package/src/log.ts +5 -0
  87. package/src/modules/index.ts +16 -0
  88. package/src/native/bare.ts +145 -0
  89. package/src/native/build.ts +81 -0
  90. package/src/{run → native}/cache.ts +10 -9
  91. package/src/native/hvigor.ts +81 -0
  92. package/src/native/install.ts +17 -0
  93. package/src/native/prepareProject.ts +99 -0
  94. package/src/native/project.ts +88 -0
  95. package/src/{tools.ts → native/toolchain.ts} +11 -118
  96. package/src/native/types.ts +44 -0
  97. package/src/prebuild/check.ts +30 -19
  98. package/src/prebuild/clean.ts +3 -3
  99. package/src/prebuild/index.ts +56 -0
  100. package/src/prebuild/prebuild.ts +11 -1
  101. package/src/prebuild/template.ts +2 -2
  102. package/src/process.ts +38 -1
  103. package/src/profile.ts +13 -0
  104. package/src/projectLock.ts +9 -1
  105. package/src/run/devices.ts +16 -11
  106. package/src/run/emulators.ts +4 -6
  107. package/src/run/index.ts +25 -0
  108. package/src/run/metro.ts +1 -1
  109. package/src/run/run.ts +56 -129
  110. package/src/start/index.ts +31 -0
  111. package/build/buildHap/common.d.ts +0 -24
  112. package/build/buildHap/common.js +0 -92
  113. package/build/run/install.d.ts +0 -7
  114. package/build/run/install.js +0 -26
  115. package/build/tools.d.ts +0 -39
  116. package/src/buildHap/common.ts +0 -145
  117. package/src/run/install.ts +0 -41
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [**GitHub 仓库**](https://github.com/renbaoshuo/expo-harmony/tree/master/packages/cli)
4
4
 
5
- Expo 项目的 HarmonyOS 命令行工具:启动 Metro 开发服务、生成原生工程、构建和安装 HAP。
5
+ Expo 项目的 HarmonyOS 命令行工具,用于启动 Metro 开发服务、生成原生工程,以及构建、安装和运行应用。
6
6
 
7
7
  ## 安装
8
8
 
@@ -10,29 +10,31 @@ Expo 项目的 HarmonyOS 命令行工具:启动 Metro 开发服务、生成原
10
10
  npm install --save-dev @expo-harmony/cli
11
11
  ```
12
12
 
13
- CLI 必须装在项目本地。JS 打包调用项目内的 Expo CLI,Hermes 字节码编译调用项目内的 `@expo/metro-config`,Release 原生构建回调项目本地的 `expo-harmony` 二进制生成 Bundle。
13
+ 请将 CLI 安装在应用项目中,并使用 Node.js 20 或更高版本。Metro 配置需要接入 `@expo-harmony/metro-config`。
14
14
 
15
- 项目需要 Node.js 20 或更高版本,在 Expo 配置中注册 `@expo-harmony/prebuild-config` 插件,并用 `@expo-harmony/metro-config` 组合 Metro 配置。
15
+ 通过 Expo 配置生成原生工程的项目(CNG),还需要在 Expo 配置中注册 `@expo-harmony/prebuild-config` 插件。
16
16
 
17
- HAP 构建需要完整的 HarmonyOS SDK(含 HMS OpenHarmony 组件)、OHPM、Hvigor 和 HDC。用 `doctor` 检查这些环境。
17
+ 手工维护 `harmony/` 工程的项目可以使用 bare 模式,按原生工程中的配置构建和运行,无需注册 prebuild 插件。接入步骤见 [bare 文档](../../docs/BARE_WORKFLOW.md)。
18
+
19
+ 构建 HAP 需要 HarmonyOS SDK(含 HMS 和 OpenHarmony 组件)、OHPM 和 Hvigor。连接设备或模拟器还需要 HDC。可以运行 `npx expo-harmony doctor` 检查环境。
18
20
 
19
21
  ## 命令
20
22
 
21
23
  | 命令 | 作用 |
22
24
  | --- | --- |
23
- | `expo-harmony start` | 启动 Harmony 开发所需的 Expo Metro 服务 |
25
+ | `expo-harmony start` | 启动 Metro 开发服务 |
24
26
  | `expo-harmony prebuild` | 使用 Expo CNG 生成或更新 HarmonyOS 原生工程 |
25
- | `expo-harmony prebuild --clean` | 安全删除并重新生成受管理的 `harmony` 目录 |
26
- | `expo-harmony prebuild --check` | 在隔离目录中生成期望状态并比较差异,不修改项目文件 |
27
- | `expo-harmony build` | 构建 HAP,不选择设备,也不安装或启动应用 |
28
- | `expo-harmony doctor` | 检查配置、依赖版本、Metro、RNOH、SDK、构建工具和签名 |
29
- | `expo-harmony modules list` | 列出发现的 Harmony Expo Module 候选及其来源 |
30
- | `expo-harmony modules inspect` | 展示模块解析后的 Harmony 元数据,如 HAR 与 ArkTS modules |
31
- | `expo-harmony modules verify` | 检查 canonical metadata、应用级 registration/OHPM 冲突、路径边界和约定产物 |
27
+ | `expo-harmony prebuild --clean` | 删除并重新生成由 CNG 管理的原生工程 |
28
+ | `expo-harmony prebuild --check` | 检查 CNG 生成的文件是否需要更新 |
29
+ | `expo-harmony build` | 构建 HAP |
30
+ | `expo-harmony doctor` | 检查项目配置、依赖、开发环境和签名配置 |
31
+ | `expo-harmony modules list` | 列出项目中的 Expo Modules 及其来源 |
32
+ | `expo-harmony modules inspect` | 查看模块的 Harmony 配置和原生文件路径 |
33
+ | `expo-harmony modules verify` | 检查模块配置、依赖冲突和原生文件 |
32
34
  | `expo-harmony export:embed` | 导出 Hermes 字节码、资源和 Source Map 并写入原生工程 |
33
- | `expo-harmony run` | 构建 HAP,在连接的 HarmonyOS 设备上安装并启动应用 |
35
+ | `expo-harmony run` | 构建 HAP,在设备或模拟器上安装并启动应用 |
34
36
 
35
- 所有命令都可以带一个可选的项目路径;`modules` 的路径写在子命令后面。不提供时,从当前目录向上找最近的项目根目录:
37
+ 所有命令都可以指定项目路径,`modules` 的路径写在子命令后面。不指定时,CLI 从当前目录向上查找最近的项目根目录:
36
38
 
37
39
  ```sh
38
40
  npx expo-harmony start ./my-app
@@ -50,13 +52,13 @@ npx expo-harmony start --port 8082
50
52
  npx expo-harmony start --clear
51
53
  ```
52
54
 
53
- CLI 入口先设置 `EXPO_HARMONY=1` `EXPO_METRO_TARGET=harmony`,再读取应用配置,所有命令都如此。
55
+ `start` 启动 Metro,为 Debug 应用提供 JS Bundle。运行这个命令不需要 HarmonyOS SDK 或已连接的设备。日志显示在当前终端,按 Ctrl+C 退出。
54
56
 
55
- `start` 调用项目本地的 Expo CLI,以 `--dev-client` 模式启动 Metro。它只启动 JS 开发服务:不生成原生工程,不构建、安装、启动应用,不做设备端口映射,也不要求 HarmonyOS SDK 和设备就绪。Metro 在当前终端输出日志,Ctrl+C 退出。
57
+ 可以用 `--port <number>` 指定端口,默认是 `8081`。`--reset-cache` 清除 Metro 缓存,也可以写成 `--clear` `-c`。
56
58
 
57
- 选项有 `--port <number>`(默认 `8081`)和 `--reset-cache`(别名 `--clear`、`-c`)。端口上已有 Metro 时提示并退出,不停止已有服务;这时缓存选项不生效,要先停掉已有的 Metro 再执行一次。端口被其他进程占用时报错。
59
+ 端口上已有 Metro 时,命令会提示并退出,已有服务继续运行。要清除它的缓存,需要先停止服务,再带缓存选项启动。端口被其他进程占用时,命令会报错。
58
60
 
59
- 要构建并启动应用,在另一个终端运行 `expo-harmony run --no-bundler`;指定了端口时,两个命令用同一个 `--port`。
61
+ 要构建并启动应用,在另一个终端运行 `npx expo-harmony run --no-bundler`。如果指定了端口,两个命令应使用相同的 `--port`。
60
62
 
61
63
  ## Prebuild
62
64
 
@@ -66,11 +68,19 @@ npx expo-harmony prebuild --clean
66
68
  npx expo-harmony prebuild --check
67
69
  ```
68
70
 
69
- `prebuild` 固定生成 HarmonyOS 平台,固定用 `@expo-harmony/template` 模板,因此没有 `--platform` 和 `--template`。依赖安装的选项透传给 Expo CLI:`--no-install`、`--npm`/`--yarn`/`--pnpm`/`--bun`(最多选一个)和 `--skip-dependency-update <packages>`。执行前先跑一遍 doctor,有阻塞错误就中止;这个阶段不要求构建工具就绪。
71
+ `prebuild` 根据 Expo 配置生成或更新 HarmonyOS 原生工程,仅用于 CNG 项目。它使用 `@expo-harmony/template` 模板,不接受 `--platform` 和 `--template` 选项。
72
+
73
+ 命令会先检查项目配置和依赖,有错误时停止。这一步不要求安装好原生构建工具。
74
+
75
+ 依赖安装支持以下选项:
70
76
 
71
- `--clean` 只删除带 Expo Harmony 模板标记的原生目录。CNG manifest 缺失、目标不是项目内的普通目录、模板标记异常,都拒绝删除。
77
+ - `--no-install`:跳过依赖安装。
78
+ - `--npm`、`--yarn`、`--pnpm` 或 `--bun`:选择包管理器,最多指定一个。
79
+ - `--skip-dependency-update <packages>`:跳过指定依赖的版本更新。
72
80
 
73
- `--check` 是只读操作。它把项目镜像到临时目录(`node_modules` 里的包目录用链接共享,Windows 下的普通文件复制过去,不需要文件符号链接权限),在临时目录里执行一次 prebuild,再比较受管文件。没有差异时退出码为 0;有差异时列出变更,退出码为 2。它不能和其他会修改工程的选项一起用。
81
+ `--clean` 删除原生工程后重新生成。如果工程缺少 CNG 清单或模板标记,或者目录是符号链接、位于项目之外,命令会拒绝删除。
82
+
83
+ `--check` 比较现有文件与当前配置生成的文件,不修改项目文件。没有差异时退出码为 0;有差异时列出变更,退出码为 2。它不能与其他会修改工程的选项一起使用。
74
84
 
75
85
  ## Build
76
86
 
@@ -80,9 +90,11 @@ npx expo-harmony build --variant release
80
90
  npx expo-harmony build --sync
81
91
  ```
82
92
 
83
- `build` 构建 HAP,不选设备,也不安装和启动应用。没有原生工程时先执行 prebuild;`--sync` 强制重新生成;默认只校验 CNG 期望状态,发现差错就报错。之后安装 OHPM 依赖、校验原生构建缓存、执行 Hvigor `assembleHap`,最后确认产物 HAP 存在且非空。原生依赖的指纹变化时,旧的 `.cxx` 和 `build` 缓存会失效,不会链接到过期产物。
93
+ `build` 构建 HAP,并输出文件路径。执行这个命令不需要连接设备,构建后也不会安装或启动应用。
94
+
95
+ 默认构建 Debug 版本。使用 `--variant release` 构建 Release 版本时,命令会将 JS Bundle 和资源打包进应用。
84
96
 
85
- 选项:`--variant debug|release`(默认 `debug`)和 `--sync`。Release 构建先执行生产导出,再带着预生成的 Bundle 进 Hvigor。
97
+ 项目没有原生工程时,命令会先运行 prebuild。已有 CNG 工程时,会检查生成的文件是否需要更新;有差异时停止构建,可以使用 `--sync` 重新生成后继续构建。bare 工程按现有原生配置构建,不支持 `--sync`。
86
98
 
87
99
  ## Doctor
88
100
 
@@ -92,16 +104,16 @@ npx expo-harmony doctor
92
104
 
93
105
  `doctor` 逐项检查:
94
106
 
95
- - Expo `harmony` 配置和 `@expo-harmony/prebuild-config` 插件注册
96
- - 签名配置文件
107
+ - CNG 工程的 Expo `harmony` 配置和 prebuild 插件;bare 工程的包名、模块、Ability 和构建配置
108
+ - 签名配置文件及其引用
97
109
  - Metro 配置是否启用 Harmony
98
- - 必需依赖:RNOH 运行时和 CLI、`@expo-harmony/expo-modules-autolinking`
99
- - Harmony Expo Modules
110
+ - RNOH 运行时和 CLI、`@expo-harmony/expo-modules-autolinking` 是否已安装
111
+ - Expo Modules 的 Harmony 支持和配置
100
112
  - HarmonyOS SDK 是否完整
101
113
  - HDC、OHPM、Hvigor 是否可用
102
- - 生成工程中的 Hvigor 文件和 RNOH 自动链接禁用状态
114
+ - 原生工程的 Hvigor 文件和自动链接配置
103
115
 
104
- 有 error 级别的问题时,命令以非零状态退出。未配置外部签名文件只警告,不影响未签名构建。
116
+ `error` 级别的问题时,命令以非零退出码结束。CNG 工程未配置外部签名文件时会显示警告,仍可构建未签名的 HAP。证书是否有效,需要在构建时由 Hvigor 检查。
105
117
 
106
118
  ## Modules
107
119
 
@@ -111,9 +123,13 @@ npx expo-harmony modules inspect --package expo-linear-gradient
111
123
  npx expo-harmony modules verify
112
124
  ```
113
125
 
114
- `list` 列出发现的模块候选(包名、版本、来源),标出不支持 Harmony 的模块、重复版本和缺失的必需依赖。`inspect` 展示模块解析后的元数据:包根、ArkTS modules、HAR 路径。`--package <name>` 筛选单个模块,未发现时报错。`verify` 输出诊断信息,有 error 时以非零状态退出。
126
+ `list` 显示模块的包名、版本和来源,并标出不支持 Harmony 的模块、重复版本和缺失的依赖。
115
127
 
116
- 三个子命令都支持 `--variant debug|release` `--native-modules-dir <dir>`(app-local 模块目录,默认 `./modules`)。
128
+ `inspect` 查看模块的 Harmony 配置,包括包目录、ArkTS 模块和 HAR 路径。可以用 `--package <name>` 筛选单个模块,找不到时会报错。
129
+
130
+ `verify` 检查模块配置、依赖冲突和原生文件。有 `error` 级别的问题时,以非零退出码结束。
131
+
132
+ 三个子命令都支持 `--variant debug|release`,也可以用 `--native-modules-dir <dir>` 指定应用内的模块目录,默认是 `./modules`。
117
133
 
118
134
  ## Export
119
135
 
@@ -123,11 +139,11 @@ npx expo-harmony export:embed --check
123
139
  npx expo-harmony export:embed --reset-cache
124
140
  ```
125
141
 
126
- `export:embed` 先运行 doctor,再用项目本地的 Expo CLI 生成 Harmony 平台的 JS Bundle 和资源,交给项目本地的 `@expo/metro-config` 的 Hermes 导出器编译字节码、合并 Source Map
142
+ `export:embed` 导出 Release 应用使用的 Hermes 字节码、资源和 Source Map。命令会先检查项目配置,再生成并校验导出文件。
127
143
 
128
- 产物先校验再写入:检查 Hermes 字节码魔数、Source Map 不含宿主机绝对路径,然后原子写入原生工程。Bundle 和资源进入模块的 `rawfile` 目录,Source Map 和清单写入 `.expo/harmony/export/`。
144
+ Bundle 和资源写入原生模块的 `rawfile` 目录,Source Map 写入 `.expo/harmony/export/`。导出清单位于 `.expo/harmony/export-manifest.json`。
129
145
 
130
- 清单 `.expo/harmony/export-manifest.json` 记录每个文件的哈希和大小。`--check` 按清单校验已有的导出,不重新构建。`--reset-cache` 透传给 Metro,清除转换缓存。
146
+ `--check` 检查已有导出文件是否与清单一致,不重新导出。`--reset-cache` 清除 Metro 转换缓存后重新导出。
131
147
 
132
148
  ## Run
133
149
 
@@ -138,30 +154,42 @@ npx expo-harmony run \
138
154
  --port 8081
139
155
  ```
140
156
 
141
- `run` 按顺序执行:环境诊断、确保原生工程(缺失时预构建,`--sync` 强制重新生成,否则复用已有工程)、选择设备、Release 生产导出、OHPM 安装、Hvigor 构建、Metro 端口反向映射(仅 Debug)、HAP 安装、Ability 启动。模块接线是否正确,由 OHPM、Hvigor、CMake 和 ArkTS 编译器验证。
157
+ `run` 构建 HAP,在设备或模拟器上安装并启动应用。它使用项目中已有的原生工程;如果工程不存在,会先运行 prebuild。
142
158
 
143
159
  常用选项:
144
160
 
145
- - `--variant debug|release`:构建模式,默认 `debug`。Release 先执行生产导出,不启动 Metro。
161
+ - `--variant debug|release`:构建模式,默认 `debug`。Release JS Bundle 和资源打包进应用,不启动 Metro。
146
162
  - `--device <id-or-name>`:选择已连接的 HDC 设备,或按完整名称启动本地模拟器,例如 `--device "Pura 90 Pro"`。有多个候选目标时必须指定。
147
- - `--port <number>`:Metro 端口和设备反向映射端口,默认 `8081`。
148
- - `--no-bundler`:连接已经在运行的 Expo Metro,不启动新的服务;端口空闲或被其他进程占用时报错。不加此选项时,端口上已有 Metro 在运行就直接复用。
163
+ - `--port <number>`:电脑上的 Metro 端口,默认 `8081`。Debug 模式下会配置设备端口映射,让应用连接到这个端口。
164
+ - `--no-bundler`:使用已运行的 Metro,找不到服务时报错。不加此选项时,会启动 Metro,或复用端口上已有的 Metro
149
165
  - `--reset-cache`:Debug 模式下启动 Metro 时清除 Metro 缓存;Release 模式下清除生产导出缓存。
150
- - `--no-install`:跳过 HAP 安装,直接启动设备上已有的应用。
151
- - `--app-id <bundleName>`:指定要启动的应用;与生成的包名不同时,必须同时使用 `--no-install`。
152
- - `--sync`:构建前强制重新执行一次预构建。
166
+ - `--no-install`:仍会构建 HAP,但跳过安装,启动设备上已有的应用。
167
+ - `--app-id <bundleName>`:指定要启动的应用;与项目配置中的包名不同时,必须同时使用 `--no-install`。
168
+ - `--sync`:构建前重新运行 prebuild,仅用于 CNG 项目。bare 项目不支持此选项。
169
+
170
+ Debug 模式下,如果 Metro 由这个命令启动,日志会显示在当前终端,按 Ctrl+C 退出。
153
171
 
154
- Debug 模式下由 CLI 启动的 Metro 接管终端输出日志,Ctrl+C 退出。
172
+ 未指定 `--device` 时,优先使用已连接的设备。没有连接的设备时,先等待正在启动的模拟器;如果也没有正在启动的模拟器,就启动本地唯一的模拟器。有多个可选设备或模拟器时,需要用 `--device` 指定。
155
173
 
156
- 未指定 `--device` 时,优先用已连接的设备。没有连接的设备时,先等正在启动的模拟器;否则启动唯一的本地模拟器。有多个候选模拟器时列出名称,用 `--device "模拟器名称"` 选择。通过名称选择或自动拉起模拟器时,CLI HDC 连接、确认开机完成后才继续构建、安装和启动,最多等 120 秒。退出 CLI 或 Metro 后,模拟器继续运行。
174
+ 按名称选择或自动启动模拟器时,CLI 会等待模拟器开机并连接 HDC,最多等待 120 秒。退出 CLI 或 Metro 后,模拟器继续运行。
157
175
 
158
- 自动启动需要 DevEco Studio 6.1.0 或更新版本,并且已在 Device Manager 中创建模拟器。CLI [华为模拟器命令行文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-emulator-command-line)调用 `Emulator -list -details` 和 `Emulator -start <name>`,使用 DevEco Studio 配置的实例和镜像路径。启动日志在项目的 `.expo/harmony/emulator.log`。启动失败时,先在 DevEco Studio 里处理首次使用协议或登录要求;文档注明需要登录开发者账号的模拟器版本无法从命令行启动。
176
+ 自动启动模拟器需要 DevEco Studio 6.1.0 或更高版本,并且已在 Device Manager 中创建模拟器。启动日志位于 `.expo/harmony/emulator.log`。启动失败时,可以先在 DevEco Studio 中打开模拟器,处理首次使用协议或登录提示。需要登录开发者账号的模拟器版本不支持从命令行启动,详见 [华为的模拟器文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-emulator-command-line)
159
177
 
160
178
  ## 工具链与环境变量
161
179
 
162
- 工具链按「环境变量覆盖 DevEco Studio 安装布局 PATH」的顺序解析。
180
+ CLI 先读取环境变量指定的工具路径,再查找 DevEco Studio 的安装目录,最后从 `PATH` 查找。
163
181
 
164
- `HARMONY_HDC`、`HARMONY_EMULATOR`、`HARMONY_OHPM`、`HARMONY_HVIGORW` 和 `HARMONY_NODE` 覆盖对应工具的路径。SDK 根目录用 `DEVECO_SDK_HOME`、`HARMONY_HOME` 或 `OHOS_SDK_HOME` 指定。Emulator 支持 DevEco Studio 的 `tools/emulator` 和 Command Line Tools 的 `emulator` 两种布局,只在需要模拟器时调用。原生构建回调 CLI 时用的 Node.js 可以用 `EXPO_HARMONY_NODE` 指定。
182
+ 找不到工具或需要使用其他版本时,可以设置以下变量:
183
+
184
+ | 环境变量 | 用途 |
185
+ | --- | --- |
186
+ | `HARMONY_HDC` | 指定 HDC 路径 |
187
+ | `HARMONY_EMULATOR` | 指定模拟器命令行工具路径 |
188
+ | `HARMONY_OHPM` | 指定 OHPM 路径 |
189
+ | `HARMONY_HVIGORW` | 指定 Hvigor 路径 |
190
+ | `HARMONY_NODE` | 指定 OHPM、Hvigor 使用的 Node.js 路径 |
191
+ | `EXPO_HARMONY_NODE` | 指定原生构建过程中运行 Expo Harmony CLI 的 Node.js 路径,要求 Node.js 20 或更高版本 |
192
+ | `DEVECO_SDK_HOME`、`HARMONY_HOME` 或 `OHOS_SDK_HOME` | 指定 HarmonyOS SDK 根目录 |
165
193
 
166
194
  ## Author
167
195
 
@@ -2,4 +2,4 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const cli_1 = require("../cli");
5
- (0, cli_1.main)();
5
+ (0, cli_1.mainAsync)();
@@ -1,18 +1,11 @@
1
+ import { type NativeBuildResult } from '../native/build';
1
2
  export interface HarmonyBuildOptions {
2
3
  io?: Pick<Console, 'error' | 'log' | 'warn'>;
3
- /** @internal For release verification after a successful isolated prebuild check. */
4
- skipGeneratedProjectCheck?: boolean;
5
4
  sync?: boolean;
6
5
  variant?: 'debug' | 'release';
7
6
  }
8
- export interface HarmonyBuildResult {
7
+ export interface HarmonyBuildResult extends NativeBuildResult {
9
8
  bundleName: string;
10
- export: null | {
11
- assetCount: number;
12
- bundleSha256: string;
13
- sourceMapSha256: string;
14
- };
15
- hapPath: string;
16
9
  headless: true;
17
10
  installed: false;
18
11
  launched: false;
@@ -21,5 +14,5 @@ export interface HarmonyBuildResult {
21
14
  steps: Record<string, number>;
22
15
  variant: 'debug' | 'release';
23
16
  }
24
- declare function buildHarmonyAsync(projectRoot: string, options?: HarmonyBuildOptions): Promise<HarmonyBuildResult>;
17
+ declare function buildHarmonyAsync(root: string, options?: HarmonyBuildOptions): Promise<HarmonyBuildResult>;
25
18
  export { buildHarmonyAsync };
@@ -1,23 +1,14 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.buildHarmonyAsync = buildHarmonyAsync;
7
- const node_path_1 = __importDefault(require("node:path"));
8
- const errors_1 = require("../errors");
9
- const export_1 = require("../exportEmbed/export");
10
- const path_1 = require("../path");
11
4
  const projectLock_1 = require("../projectLock");
12
- const cache_1 = require("../run/cache");
13
- const install_1 = require("../run/install");
14
- const tools_1 = require("../tools");
15
- const common_1 = require("./common");
16
- async function buildHarmonyUnlockedAsync(projectRoot, options = {}) {
17
- const normalizedOptions = {
5
+ const build_1 = require("../native/build");
6
+ const prepareProject_1 = require("../native/prepareProject");
7
+ const toolchain_1 = require("../native/toolchain");
8
+ async function buildHarmonyUnlockedAsync(root, options = {}) {
9
+ const settings = {
18
10
  io: options.io || console,
19
11
  requireDeviceTools: false,
20
- skipGeneratedProjectCheck: Boolean(options.skipGeneratedProjectCheck),
21
12
  sync: Boolean(options.sync),
22
13
  variant: options.variant || 'debug',
23
14
  };
@@ -28,69 +19,20 @@ async function buildHarmonyUnlockedAsync(projectRoot, options = {}) {
28
19
  metro: 0,
29
20
  metroPort: 0,
30
21
  };
31
- await (0, common_1.ensureGeneratedProjectAsync)(projectRoot, normalizedOptions, steps);
32
- const plan = await (0, common_1.timed)(steps, 'buildPlan', () => (0, tools_1.resolveHarmonyBuildPlanAsync)(projectRoot, { buildMode: normalizedOptions.variant }));
33
- const toolchain = (0, tools_1.resolveHarmonyToolchain)();
34
- let exportManifest = null;
35
- if (normalizedOptions.variant === 'release') {
36
- (0, common_1.progress)(normalizedOptions, 'Exporting the release Hermes bundle');
37
- exportManifest = await (0, common_1.timed)(steps, 'export', () => (0, export_1.exportEmbedAsync)(projectRoot, { skipDoctor: true }));
38
- }
39
- else {
40
- steps.export = 0;
41
- }
42
- (0, common_1.progress)(normalizedOptions, 'Installing Harmony project dependencies');
43
- await (0, common_1.timed)(steps, 'ohpm', () => (0, install_1.installHarmonyDependenciesAsync)(plan, toolchain));
44
- (0, common_1.progress)(normalizedOptions, 'Checking Harmony native dependency cache');
45
- const nativeBuildCache = await (0, common_1.timed)(steps, 'nativeCache', () => ((0, cache_1.prepareHarmonyNativeBuildCacheAsync)(projectRoot, plan)));
46
- if (nativeBuildCache.changed) {
47
- (0, common_1.progress)(normalizedOptions, 'Invalidated stale Harmony native build objects');
48
- }
49
- (0, common_1.progress)(normalizedOptions, `Building the ${normalizedOptions.variant} HAP without a device`);
50
- const buildEnv = {
51
- ...process.env,
52
- EXPO_HARMONY_NODE: process.env.EXPO_HARMONY_NODE || process.execPath,
53
- EXPO_METRO_TARGET: 'harmony',
54
- HERMES_V1_ENABLED: 'true',
55
- ...(normalizedOptions.variant === 'release' ? { EXPO_HARMONY_BUNDLE_PREBUILT: '1' } : {}),
56
- ...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
57
- ? { DEVECO_SDK_HOME: toolchain.sdkHome }
58
- : {}),
59
- };
60
- await (0, common_1.timed)(steps, 'build', () => (0, common_1.runCheckedAsync)(toolchain.hvigor.command, [
61
- ...toolchain.hvigor.args,
62
- ...plan.hvigorArgs,
63
- ], {
64
- code: 'ERR_HARMONY_BUILD_FAILED',
65
- cwd: plan.harmonyRoot,
66
- env: buildEnv,
67
- message: 'Hvigor build',
68
- operation: 'hvigor-build',
69
- timeoutMs: 15 * 60_000,
70
- }));
71
- if (!(0, common_1.isNonEmptyRegularFile)(plan.expectedHap)) {
72
- throw new errors_1.HarmonyCliError('ERR_HARMONY_HAP_MISSING', 'Hvigor completed without producing the expected non-empty regular HAP.', { operation: 'verify-hap' });
73
- }
74
- await (0, common_1.timed)(steps, 'nativeCacheCommit', () => (0, cache_1.commitHarmonyNativeBuildCacheAsync)(nativeBuildCache));
22
+ const plan = await (0, prepareProject_1.prepareNativeProjectAsync)(root, settings, steps);
23
+ const built = await (0, build_1.buildNativeAsync)(root, plan, (0, toolchain_1.resolveHarmonyToolchain)(), settings, steps);
75
24
  return {
76
25
  bundleName: plan.bundleName,
77
- export: exportManifest
78
- ? {
79
- assetCount: exportManifest.assets.length,
80
- bundleSha256: exportManifest.bundle.sha256,
81
- sourceMapSha256: exportManifest.sourceMap.sha256,
82
- }
83
- : null,
84
- hapPath: (0, path_1.toPosixPath)(node_path_1.default.relative(projectRoot, plan.expectedHap)),
26
+ ...built,
85
27
  headless: true,
86
28
  installed: false,
87
29
  launched: false,
88
30
  ok: true,
89
31
  schemaVersion: 1,
90
32
  steps,
91
- variant: normalizedOptions.variant,
33
+ variant: settings.variant,
92
34
  };
93
35
  }
94
- async function buildHarmonyAsync(projectRoot, options = {}) {
95
- return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'build', () => buildHarmonyUnlockedAsync(projectRoot, options));
36
+ async function buildHarmonyAsync(root, options = {}) {
37
+ return (0, projectLock_1.withHarmonyProjectLockAsync)(root, 'build', () => buildHarmonyUnlockedAsync(root, options));
96
38
  }
@@ -0,0 +1,2 @@
1
+ import { type Command } from '../command';
2
+ export declare const command: Command;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const options_1 = require("./options");
6
+ const command = async (argv, io) => {
7
+ const args = (0, command_1.parseCommandArgs)(options_1.parseBuildArgs, argv, io);
8
+ if (!args)
9
+ return 0;
10
+ const { projectRoot: root, options } = args;
11
+ const { buildHarmonyAsync } = await import('./build.js');
12
+ const result = await buildHarmonyAsync(root, {
13
+ ...options,
14
+ io,
15
+ });
16
+ io.log(`Built ${result.variant} HAP at ${result.hapPath}; no device actions were performed.`);
17
+ return 0;
18
+ };
19
+ exports.command = command;
package/build/cli.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare function runAsync(argv?: string[], io?: Pick<Console, 'error' | 'log' | 'warn'>): Promise<number>;
2
- declare function main(argv?: string[]): Promise<void>;
3
- export { main, runAsync };
1
+ import type { Log } from './log';
2
+ export declare function runAsync(argv?: string[], io?: Log): Promise<number>;
3
+ export declare function mainAsync(argv?: string[]): Promise<void>;
package/build/cli.js CHANGED
@@ -1,204 +1,38 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.main = main;
7
3
  exports.runAsync = runAsync;
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const options_1 = require("./buildHap/options");
10
- const options_2 = require("./doctor/options");
4
+ exports.mainAsync = mainAsync;
11
5
  const errors_1 = require("./errors");
12
- const options_3 = require("./exportEmbed/options");
13
- const options_4 = require("./modules/options");
14
- const options_5 = require("./prebuild/options");
15
- const project_1 = require("./project");
16
- const projectLock_1 = require("./projectLock");
17
- const options_6 = require("./run/options");
18
- const options_7 = require("./start/options");
19
- const Help = `Usage: expo-harmony <command> [project] [options]
20
-
21
- Commands:
22
- start Start Expo Metro for Harmony development
23
- prebuild Generate the Harmony native project with Expo CNG
24
- prebuild --clean Safely recreate the managed Harmony directory
25
- prebuild --check Compare generated desired state without project writes
26
- build Build a HAP without selecting or contacting a device
27
- doctor Validate config, versions, Metro, RNOH, SDK, and signing
28
- modules list List native module candidates and their discovery source
29
- modules inspect Resolve Harmony metadata (--package narrows the result)
30
- modules verify Validate module config, registration conflicts and safe paths
31
- export:embed Export validated Hermes bytecode, assets, and a source map
32
- run Build, install, and launch the Harmony app
33
-
34
- Options:
35
- --no-install Skip dependency install (prebuild) or HAP install (run)
36
- --npm|--yarn|--pnpm|--bun
37
- Select the package manager used by prebuild dependency install
38
- --skip-dependency-update <packages>
39
- Preserve comma-separated dependency versions
40
- --device <id-or-name> Select an HDC target or start a local emulator by name
41
- --variant <mode> Build debug or release (default: debug)
42
- --no-bundler Use an already-running Expo Metro server
43
- --app-id <bundleName>
44
- Launch another installed app (requires --no-install when different)
45
- --port <number> Metro and device reverse port (default: 8081)
46
- --sync Re-run prebuild before building
47
- --check Validate an existing export without writing
48
- --reset-cache Reset Metro while exporting or starting
49
- -c, --clear Alias for --reset-cache (start)
50
- -h, --help Show this help
51
- `;
52
- function parseInvocation(argv) {
53
- if (argv.length === 0 || (argv.length === 1 && ['--help', '-h'].includes(argv[0]))) {
54
- return { command: 'help' };
55
- }
56
- const command = argv[0];
57
- if (!['build', 'prebuild', 'doctor', 'export:embed', 'modules', 'run', 'start'].includes(command)) {
58
- throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unknown command: ${command}`, {
59
- operation: 'parse-arguments',
60
- });
61
- }
62
- const parsed = command === 'build'
63
- ? (0, options_1.parseBuildArgs)(argv.slice(1))
64
- : command === 'prebuild'
65
- ? (0, options_5.parsePrebuildArgs)(argv.slice(1), { allowProject: true })
66
- : command === 'doctor'
67
- ? (0, options_2.parseDoctorArgs)(argv.slice(1))
68
- : command === 'export:embed'
69
- ? (0, options_3.parseExportEmbedArgs)(argv.slice(1))
70
- : command === 'modules'
71
- ? (0, options_4.parseModulesArgs)(argv.slice(1))
72
- : command === 'start'
73
- ? (0, options_7.parseStartArgs)(argv.slice(1))
74
- : (0, options_6.parseRunArgs)(argv.slice(1));
75
- if (parsed.help)
76
- return { command: 'help' };
77
- const projectRoot = (0, project_1.resolveProject)(parsed.project ? node_path_1.default.resolve(parsed.project) : process.cwd());
78
- return { command, parsed, projectRoot };
79
- }
6
+ const help_1 = require("./help");
7
+ const commands = {
8
+ 'start': () => import('./start/index.js'),
9
+ 'prebuild': () => import('./prebuild/index.js'),
10
+ 'build': () => import('./buildHap/index.js'),
11
+ 'doctor': () => import('./doctor/index.js'),
12
+ 'modules': () => import('./modules/index.js'),
13
+ 'export:embed': () => import('./exportEmbed/index.js'),
14
+ 'run': () => import('./run/index.js'),
15
+ };
80
16
  async function runAsync(argv = process.argv.slice(2), io = console) {
81
17
  process.env.EXPO_HARMONY = '1';
82
18
  process.env.EXPO_METRO_TARGET = 'harmony';
83
- const invocation = parseInvocation(argv);
84
- if (invocation.command === 'help') {
85
- io.log(Help);
86
- return 0;
87
- }
88
- if (invocation.command === 'start') {
89
- const { startExpoMetroAsync } = await import('./run/metro.js');
90
- const metro = await startExpoMetroAsync(invocation.projectRoot, {
91
- ...invocation.parsed,
92
- interactive: true,
93
- });
94
- try {
95
- if (metro.owner === 'existing') {
96
- io.log(`Expo Metro is already running on port ${metro.port}.`);
97
- if (invocation.parsed.resetCache) {
98
- io.warn('Stop the existing Metro server and run this command again to reset its cache.');
99
- }
100
- }
101
- else {
102
- io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
103
- await metro.waitAsync();
104
- }
105
- return 0;
106
- }
107
- finally {
108
- await metro.stop();
109
- }
110
- }
111
- if (invocation.command === 'doctor') {
112
- const { doctorAsync, formatDoctor } = await import('./doctor/doctor.js');
113
- const result = await doctorAsync(invocation.projectRoot, { requireBuildTools: true });
114
- io.log(formatDoctor(result));
115
- return result.ok ? 0 : 1;
116
- }
117
- if (invocation.command === 'build') {
118
- const { buildHarmonyAsync } = await import('./buildHap/build.js');
119
- const result = await buildHarmonyAsync(invocation.projectRoot, {
120
- ...invocation.parsed,
121
- io,
122
- });
123
- io.log(`Built ${result.variant} HAP at ${result.hapPath}; no device actions were performed.`);
124
- return 0;
125
- }
126
- if (invocation.command === 'export:embed') {
127
- const { exportEmbedAsync } = await import('./exportEmbed/export.js');
128
- const result = await exportEmbedAsync(invocation.projectRoot, invocation.parsed);
129
- if (invocation.parsed.check)
130
- io.log('Harmony export bundle, assets, and source map are valid.');
131
- else
132
- io.log(`Exported Hermes bytecode ${result.bundle.path} with ${result.assets.length} asset file(s).`);
133
- return 0;
134
- }
135
- if (invocation.command === 'modules') {
136
- const { formatModulesResult, runModulesCommandAsync } = await import('./modules/modules.js');
137
- const result = await runModulesCommandAsync(invocation.projectRoot, invocation.parsed);
138
- io.log(formatModulesResult(result));
139
- return result.ok ? 0 : 1;
140
- }
141
- if (invocation.command === 'run') {
142
- const { runHarmonySessionAsync } = await import('./run/run.js');
143
- const session = await runHarmonySessionAsync(invocation.projectRoot, {
144
- ...invocation.parsed,
145
- interactiveBundler: true,
146
- io,
147
- });
148
- const { result } = session;
149
- io.log(`Launched ${result.bundleName} on ${result.device.id} (${result.variant}).`);
150
- if (session.metro.owner === 'started') {
151
- io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
152
- await session.metro.waitAsync();
153
- }
19
+ if (argv.length === 0 || (argv.length === 1 && ['--help', '-h'].includes(argv[0]))) {
20
+ io.log(help_1.Help);
154
21
  return 0;
155
22
  }
156
- const { check, clean, passthrough } = invocation.parsed;
157
- if (check && passthrough.length) {
158
- throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--check cannot be combined with mutating prebuild options.');
23
+ const name = argv[0];
24
+ if (!Object.hasOwn(commands, name)) {
25
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unknown command: ${name}`, { operation: 'parse-arguments' });
159
26
  }
160
- return (0, projectLock_1.withHarmonyProjectLockAsync)(invocation.projectRoot, check ? 'prebuild-check' : 'prebuild', async () => {
161
- if (clean) {
162
- const { assertSafeCleanTarget } = await import('./prebuild/clean.js');
163
- await assertSafeCleanTarget(invocation.projectRoot);
164
- }
165
- const { doctorAsync, formatDoctor } = await import('./doctor/doctor.js');
166
- const doctor = await doctorAsync(invocation.projectRoot, {
167
- requireBuildTools: false,
168
- validateGeneratedProject: !clean,
169
- validateModules: false,
170
- });
171
- if (!doctor.ok) {
172
- io.error(formatDoctor(doctor));
173
- throw new errors_1.HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', 'Harmony doctor found blocking errors.', {
174
- operation: 'doctor',
175
- });
176
- }
177
- for (const item of doctor.checks.filter(item => item.status === 'warn'))
178
- io.warn(`! ${item.message}`);
179
- if (check) {
180
- const { checkAsync } = await import('./prebuild/check.js');
181
- const result = await checkAsync(invocation.projectRoot);
182
- if (result.clean)
183
- io.log('Harmony CNG output is up to date.');
184
- else
185
- for (const change of result.changes)
186
- io.log(`${change.type}: ${change.path}`);
187
- return result.clean ? 0 : 2;
188
- }
189
- const { prebuildParsedAsync } = await import('./prebuild/prebuild.js');
190
- await prebuildParsedAsync(invocation.projectRoot, passthrough);
191
- return 0;
192
- });
27
+ const { command } = await commands[name]();
28
+ return command(argv.slice(1), io);
193
29
  }
194
- async function main(argv = process.argv.slice(2)) {
30
+ async function mainAsync(argv = process.argv.slice(2)) {
195
31
  try {
196
- const code = await runAsync(argv);
197
- process.exitCode = code;
32
+ process.exitCode = await runAsync(argv);
198
33
  }
199
34
  catch (error) {
200
- const code = error.code || 'ERR_HARMONY_UNKNOWN';
201
- console.error(`[${code}] ${error.message}`);
35
+ console.error(`[${error.code || 'ERR_HARMONY_UNKNOWN'}] ${error.message}`);
202
36
  process.exitCode = error.exitCode || 1;
203
37
  }
204
38
  }
@@ -0,0 +1,9 @@
1
+ import type { Log } from './log';
2
+ export type Command = (argv: string[], io: Log) => Promise<number>;
3
+ export declare function parseCommandArgs<T extends {
4
+ help: boolean;
5
+ project?: string;
6
+ }>(parse: (argv: string[]) => T, argv: string[], io: Log): {
7
+ options: T;
8
+ projectRoot: string;
9
+ } | null;