cj-tauri 0.8.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +3 -3
  2. package/cjpm.toml +52 -25
  3. package/cli/cjpm.toml +1 -1
  4. package/cli/src/doctor.cj +304 -0
  5. package/cli/src/main.cj +33 -7
  6. package/cli/src/project.cj +59 -1
  7. package/cli/src/resolve.cj +348 -304
  8. package/cli/src/scaffold.cj +139 -22
  9. package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
  10. package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
  11. package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
  12. package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
  13. package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
  14. package/cli/templates/ohos-pc/README.md +79 -0
  15. package/cli/templates/ohos-pc/build-hap.sh +190 -0
  16. package/cli/templates/ohos-pc/build-profile.json5 +46 -0
  17. package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
  18. package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
  19. package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
  20. package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
  21. package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
  22. package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
  23. package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
  24. package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
  25. package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
  26. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
  27. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
  28. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
  29. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
  30. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
  31. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
  32. package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
  33. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
  34. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
  35. package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
  36. package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
  37. package/cli/templates/ohos-pc/oh-package.json5 +10 -0
  38. package/native/build_linux.sh +30 -2
  39. package/native/build_win.bat +62 -61
  40. package/native/core/bridge_core.c +1626 -0
  41. package/native/{bridge_core.h → core/bridge_core.h} +246 -6
  42. package/native/platforms/harmonyos/README.md +106 -0
  43. package/native/platforms/linux/bridge_linux.c +2485 -0
  44. package/native/platforms/windows/bridge_win.c +3248 -0
  45. package/native/tests/test_bridge_core.c +528 -1
  46. package/package.json +12 -3
  47. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  48. package/src/api_system.cj +10 -2
  49. package/src/app.cj +248 -85
  50. package/src/arkweb_proxy.cj +56 -0
  51. package/src/arkweb_script.cj +154 -0
  52. package/src/capability.cj +140 -0
  53. package/src/clipboard_bridge.cj +55 -0
  54. package/src/dragdrop.cj +26 -0
  55. package/src/embedded_assets.cj +98 -0
  56. package/src/hexutil.cj +53 -0
  57. package/src/host.cj +197 -26
  58. package/src/host_arkweb.cj +740 -0
  59. package/src/host_factory.cj +48 -0
  60. package/src/host_webkit.cj +202 -26
  61. package/src/host_webview2.cj +176 -3
  62. package/src/hotkey.cj +66 -0
  63. package/src/ipc_hub.cj +49 -0
  64. package/src/ipc_message.cj +2 -1
  65. package/src/jsutil.cj +33 -0
  66. package/src/log_bridge.cj +107 -0
  67. package/src/logging.cj +127 -0
  68. package/src/menu.cj +197 -196
  69. package/src/plugin.cj +40 -0
  70. package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
  71. package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
  72. package/src/plugins/fs/plugin_fs.cj +194 -0
  73. package/src/plugins/http/plugin_http.cj +459 -0
  74. package/src/plugins/log/plugin_log.cj +105 -0
  75. package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
  76. package/src/plugins/mqtt/mqtt_codec.cj +661 -0
  77. package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
  78. package/src/plugins/notification/plugin_notification.cj +146 -0
  79. package/src/plugins/plugins.cj +5 -0
  80. package/src/plugins/redis/plugin_redis.cj +2409 -0
  81. package/src/plugins/redis/redis_codec.cj +556 -0
  82. package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
  83. package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
  84. package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
  85. package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
  86. package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
  87. package/src/plugins/store/plugin_store.cj +287 -0
  88. package/src/plugins/ws/plugin_ws.cj +470 -0
  89. package/src/process_hidden.cj +174 -0
  90. package/src/scope.cj +391 -0
  91. package/src/shell_event.cj +69 -68
  92. package/src/tests/capability_test.cj +53 -0
  93. package/src/tests/fake_host.cj +85 -20
  94. package/src/tests/ipc_hub_test.cj +13 -0
  95. package/src/tests/jsutil_test.cj +62 -0
  96. package/src/tests/logging_test.cj +62 -0
  97. package/src/tests/mqtt_codec_test.cj +405 -0
  98. package/src/tests/plugin_clipboard_test.cj +222 -0
  99. package/src/tests/plugin_dialog_test.cj +98 -4
  100. package/src/tests/plugin_http_test.cj +124 -0
  101. package/src/tests/plugin_menu_test.cj +1 -0
  102. package/src/tests/plugin_notification_test.cj +205 -0
  103. package/src/tests/plugin_serial_test.cj +269 -267
  104. package/src/tests/plugin_shell_test.cj +189 -188
  105. package/src/tests/plugin_sqlite_test.cj +230 -0
  106. package/src/tests/plugin_store_test.cj +260 -0
  107. package/src/tests/plugin_test.cj +542 -524
  108. package/src/tests/plugin_ws_test.cj +40 -0
  109. package/src/tests/process_hidden_test.cj +129 -0
  110. package/src/tests/redis_codec_test.cj +396 -0
  111. package/src/tests/scope_test.cj +350 -0
  112. package/src/tests/shell_event_test.cj +0 -1
  113. package/src/tests/system_host_test.cj +81 -79
  114. package/src/tests/urlutil_test.cj +53 -0
  115. package/src/tests/version_test.cj +75 -65
  116. package/src/tray.cj +90 -0
  117. package/src/urlutil.cj +159 -0
  118. package/src/version.cj +23 -2
  119. package/src/window.cj +51 -42
  120. package/native/bridge_core.c +0 -797
  121. package/native/bridge_linux.c +0 -1268
  122. package/native/bridge_win.c +0 -1633
  123. package/src/plugin_fs.cj +0 -118
  124. /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * cj-tauri CLI:项目脚手架(create)
3
3
  *
4
- * 模板位于 <框架根>/cli/templates/app,占位符形如 {{PROJECT_NAME}};
5
- * 递归拷贝 + 文本渲染,生成一个可直接 `cj-tauri dev` 的应用。
4
+ * 模板位于 <框架根>/cli/templates/<模板目录>,占位符形如 {{PROJECT_NAME}};
5
+ * 递归拷贝 + 文本渲染,生成一个可直接 `cj-tauri dev`(桌面)或 `bash build-hap.sh`(鸿蒙)的应用。
6
6
  */
7
7
  package cli
8
8
 
@@ -17,14 +17,21 @@ public class TemplateVars {
17
17
  let nativeDir: String
18
18
  let stdxDir: String
19
19
  let targetSection: String
20
+ /** 鸿蒙应用的安装 ID(bundleName),桌面模板不用 */
21
+ let bundleName: String
22
+ /** 应用显示名(桌面模板用项目名,鸿蒙写进 string.json) */
23
+ let appLabel: String
20
24
 
21
- public init(projectName: String, packageName: String, frameworkRoot: String, stdxDir: String) {
25
+ public init(projectName: String, packageName: String, frameworkRoot: String, stdxDir: String,
26
+ bundleName: String, appLabel: String) {
22
27
  this.projectName = projectName
23
28
  this.packageName = packageName
24
29
  this.frameworkRoot = frameworkRoot
25
30
  this.nativeDir = joinPath(frameworkRoot, "native")
26
31
  this.stdxDir = stdxDir
27
32
  this.targetSection = buildTargetSection(joinPath(frameworkRoot, "native"), stdxDir)
33
+ this.bundleName = bundleName
34
+ this.appLabel = appLabel
28
35
  }
29
36
  }
30
37
 
@@ -62,9 +69,35 @@ func renderTemplate(content: String, v: TemplateVars): String {
62
69
  out = out.replace("{{FRAMEWORK_ROOT}}", v.frameworkRoot)
63
70
  out = out.replace("{{NATIVE_DIR}}", v.nativeDir)
64
71
  out = out.replace("{{STDX_DIR}}", v.stdxDir)
72
+ out = out.replace("{{BUNDLE_NAME}}", v.bundleName)
73
+ out = out.replace("{{APP_LABEL}}", v.appLabel)
65
74
  return out
66
75
  }
67
76
 
77
+ /**
78
+ * 模板里哪些文件必须**按字节原样拷**
79
+ *
80
+ * 模板要带图标(`resources/base/media/` 下的 png),而 `readText` 走 UTF-8——二进制过一遍
81
+ * 文本渲染会当场损坏(就算侥幸没崩,png 也变得打不开,要到装到设备上才看得见白方块)。
82
+ * 判据只看扩展名:模板里不需要「文本渲染的二进制」,真需要再另说。
83
+ */
84
+ func isBinaryTemplateFile(name: String): Bool {
85
+ let b = name.toArray()
86
+ let out = ArrayList<UInt8>()
87
+ for (i in 0..b.size) {
88
+ out.add(toLowerByte(b[i]))
89
+ }
90
+ let lower = String.fromUtf8(out.toArray())
91
+ let exts = ["png", "jpg", "jpeg", "gif", "webp", "ico", "ttf", "otf", "woff", "woff2",
92
+ "so", "dll", "a", "lib", "hap", "zip", "gz", "pdf", "wav", "mp3", "mp4"]
93
+ for (ext in exts) {
94
+ if (lower.endsWith("." + ext)) {
95
+ return true
96
+ }
97
+ }
98
+ return false
99
+ }
100
+
68
101
  /** 项目名 → 仓颉包名(lowerCamelCase):my-app → myApp */
69
102
  func packageNameFrom(name: String): Option<String> {
70
103
  let b = name.toArray()
@@ -103,7 +136,22 @@ func packageNameFrom(name: String): Option<String> {
103
136
  return Some(String.fromUtf8(out.toArray()))
104
137
  }
105
138
 
106
- /** 递归拷贝模板目录:目录建目录、文件渲染后写出 */
139
+ /**
140
+ * 按字节覆盖写
141
+ *
142
+ * 模板里的图片不能走 `writeText`:`readText` 是 UTF-8 解码,读 png 当场抛
143
+ * `IllegalArgumentException: Invalid unicode scalar value`(create 直接崩、目录留半成品)。
144
+ */
145
+ func writeBytes(p: String, data: Array<UInt8>): Unit {
146
+ if (exists(p)) {
147
+ remove(p)
148
+ }
149
+ let f = File.create(p)
150
+ f.write(data)
151
+ f.close()
152
+ }
153
+
154
+ /** 递归拷贝模板目录:目录建目录、文本渲染后写出、二进制原样拷 */
107
155
  func copyTree(src: String, dst: String, v: TemplateVars): Int64 {
108
156
  var count = 0
109
157
  for (info in listDir(src)) {
@@ -116,6 +164,9 @@ func copyTree(src: String, dst: String, v: TemplateVars): Int64 {
116
164
  if (info.isDirectory()) {
117
165
  ensureDir(d)
118
166
  count = count + copyTree(s, d, v)
167
+ } else if (isBinaryTemplateFile(nm)) {
168
+ writeBytes(d, File.readFrom(s))
169
+ count = count + 1
119
170
  } else {
120
171
  writeText(d, renderTemplate(readText(s), v))
121
172
  count = count + 1
@@ -126,13 +177,51 @@ func copyTree(src: String, dst: String, v: TemplateVars): Int64 {
126
177
 
127
178
  /** 模板别名 → 仓库里的模板目录名(缺省 app,0.3.x 的老用法不受影响) */
128
179
  func templateDirName(tpl: String): Option<String> {
129
- match (tpl) {
130
- case "app" => Some("app") // 内联 HTML 单文件,零 Node 依赖
131
- case "vanilla" => Some("app")
132
- case "vue" => Some("app-vue") // Vue 3 + Vite:dev 走 HMR,发布产单文件
133
- case "react" => Some("app-react") // React 18 + Vite
134
- case _ => None
180
+ if (tpl == "app" || tpl == "vanilla") {
181
+ return Some("app") // 内联 HTML 单文件,零 Node 依赖
182
+ }
183
+ if (tpl == "vue") {
184
+ return Some("app-vue") // Vue 3 + Vite:dev 走 HMR,发布产单文件
135
185
  }
186
+ if (tpl == "react") {
187
+ return Some("app-react") // React 18 + Vite
188
+ }
189
+ if (tpl == "ohos" || tpl == "ohos-pc" || tpl == "harmonyos" || tpl == "harmonyos-pc") {
190
+ return Some("ohos-pc") // 鸿蒙 PC(2in1):ArkWeb 宿主 + build-hap.sh
191
+ }
192
+ return None
193
+ }
194
+
195
+ /** 该项目是不是鸿蒙脚手架(模板目录名即判据,别名在 templateDirName 里已归一到 ohos-pc) */
196
+ func isOhosScaffoldTemplate(tplDir: String): Bool {
197
+ return baseName(tplDir) == "ohos-pc"
198
+ }
199
+
200
+ /**
201
+ * 项目名 → 缺省 bundleName(`com.example.<小写下划线名>`)
202
+ *
203
+ * 鸿蒙要求 bundleName 每一段以字母开头、只含字母数字下划线,且至少三段;
204
+ * 项目名里的 `-` `.` 之类一律折成 `_`,数字开头补 `app` 前缀。用户可以用 `--bundle` 覆盖。
205
+ */
206
+ func bundleNameFrom(projectName: String): String {
207
+ let b = projectName.toArray()
208
+ let out = ArrayList<UInt8>()
209
+ for (i in 0..b.size) {
210
+ let c = toLowerByte(b[i])
211
+ if (isAsciiAlpha(c) || isAsciiDigit(c)) {
212
+ out.add(c)
213
+ } else {
214
+ out.add(0x5F) // '_'
215
+ }
216
+ }
217
+ var s = String.fromUtf8(out.toArray())
218
+ if (s.size > 0 && isAsciiDigit(s.toArray()[0])) {
219
+ s = "app" + s
220
+ }
221
+ if (s.size == 0) {
222
+ s = "app"
223
+ }
224
+ return "com.example." + s
136
225
  }
137
226
 
138
227
  /** 解析模板目录:先查别名表,查不到就把参数当模板目录名直接用(方便自己往仓库里加模板) */
@@ -171,10 +260,10 @@ func availableTemplates(fwRoot: String): String {
171
260
  return out
172
261
  }
173
262
 
174
- func cmdCreate(nameArg: String, tpl: String): Int64 {
263
+ func cmdCreate(nameArg: String, tpl: String, bundleArg: String, labelArg: String): Int64 {
175
264
  let targetDir = stripTrailingSep(nameArg)
176
265
  if (targetDir.size == 0) {
177
- eprintln("用法: cj-tauri create <项目名>")
266
+ eprintln("用法: cj-tauri create <项目名> [--template app|vue|react|ohos]")
178
267
  return 1
179
268
  }
180
269
  let projectName = baseName(targetDir)
@@ -198,26 +287,38 @@ func cmdCreate(nameArg: String, tpl: String): Int64 {
198
287
  eprintln(" 请设置环境变量 CJ_TAURI_ROOT 指向 cj-tauri 仓库根目录")
199
288
  return 1
200
289
  }
201
- var stdx = ""
202
- if (let Some(s) <- resolveStdx(fwRoot, None)) {
203
- stdx = s
204
- } else {
205
- eprintln("[cj-tauri] 错误: 找不到 stdx 目录")
206
- eprintln(" 请设置环境变量 CANGJIE_STDX 指向 stdx 动态库目录(含 stdx.encoding.json)")
207
- return 1
208
- }
290
+ // 模板先解析:鸿蒙模板**不吃桌面 stdx**(编 hap 时由 build-hap.sh 用 CANGJIE_STDX_OHOS),
291
+ // 所以它不能因为本机没配 CANGJIE_STDX 就被拦下。
209
292
  var tplDir = ""
210
293
  if (let Some(d) <- resolveTemplateDir(fwRoot, tpl)) {
211
294
  tplDir = d
212
295
  } else {
213
296
  eprintln("[cj-tauri] 错误: 未知模板 '${tpl}'")
214
297
  eprintln(" 可用模板: ${availableTemplates(fwRoot)}")
215
- eprintln(" 别名: app(内联单文件,缺省)/ vue / react")
298
+ eprintln(" 别名: app(内联单文件,缺省)/ vue / react / ohos")
299
+ return 1
300
+ }
301
+ let ohos = isOhosScaffoldTemplate(tplDir)
302
+ var stdx = ""
303
+ if (let Some(s) <- resolveStdx(fwRoot, None)) {
304
+ stdx = s
305
+ } else if (!ohos) {
306
+ eprintln("[cj-tauri] 错误: 找不到 stdx 目录")
307
+ eprintln(" 请设置环境变量 CANGJIE_STDX 指向 stdx 动态库目录(含 stdx.encoding.json)")
216
308
  return 1
217
309
  }
218
310
  warnIfSpacey("框架", fwRoot)
219
311
 
220
- let v = TemplateVars(projectName, pkg, fwRoot, stdx)
312
+ var bundle = bundleArg
313
+ if (bundle.size == 0) {
314
+ bundle = bundleNameFrom(projectName)
315
+ }
316
+ var label = labelArg
317
+ if (label.size == 0) {
318
+ label = projectName
319
+ }
320
+
321
+ let v = TemplateVars(projectName, pkg, fwRoot, stdx, bundle, label)
221
322
  ensureDir(targetDir)
222
323
  let count = copyTree(tplDir, targetDir, v)
223
324
 
@@ -225,6 +326,22 @@ func cmdCreate(nameArg: String, tpl: String): Int64 {
225
326
  println("")
226
327
  println(" 项目名 : ${projectName}")
227
328
  println(" 模板 : ${baseName(tplDir)}")
329
+ if (ohos) {
330
+ println(" 应用名 : ${label}")
331
+ println(" bundle : ${bundle}")
332
+ println(" 框架依赖 : ${fwRoot}")
333
+ println("")
334
+ println("下一步:")
335
+ println(" cd ${targetDir}")
336
+ println(" export CANGJIE_STDX_OHOS=<鸿蒙版 stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx")
337
+ println(" bash build-hap.sh # 编 hap")
338
+ println(" bash build-hap.sh --install # 编完装到设备并拉起")
339
+ println("")
340
+ println("提示: 模板不带签名证书——首次在 DevEco 里 File → Project Structure → Signing Configs")
341
+ println(" 勾「Automatically generate signature」(要登录华为账号);不签名只能编出")
342
+ println(" *-unsigned.hap,装不进设备。")
343
+ return 0
344
+ }
228
345
  println(" 仓颉包名 : ${pkg}")
229
346
  println(" 框架依赖 : ${fwRoot}")
230
347
  println(" stdx : ${stdx}")
@@ -0,0 +1,13 @@
1
+ {
2
+ "app": {
3
+ /* bundleName 是应用在设备上的唯一 ID:两个应用写同一个 bundleName 会互相覆盖安装。
4
+ 改它必须重新签名——DevEco 的「自动签名」会按新包名重新申请 profile。 */
5
+ "bundleName": "{{BUNDLE_NAME}}",
6
+ "vendor": "{{PROJECT_NAME}}",
7
+ "versionCode": 1000000,
8
+ "versionName": "1.0.0",
9
+ "buildVersion": "1",
10
+ "icon": "$media:layered_image",
11
+ "label": "$string:app_name"
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "string": [
3
+ {
4
+ "name": "app_name",
5
+ "value": "{{APP_LABEL}}"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "layered-image": {
3
+ "background": "$media:background",
4
+ "foreground": "$media:foreground"
5
+ }
6
+ }
@@ -0,0 +1,79 @@
1
+ # {{PROJECT_NAME}} — 仓颉 + ArkWeb 鸿蒙 PC 应用
2
+
3
+ 用 [cj-tauri](https://atomgit.com/qq8864/cj-tauri) 生成的鸿蒙 PC(2in1)应用工程:
4
+ **仓颉后端(静态编译)+ 系统 ArkWeb 前端(HTML/CSS/JS)**,IPC、能力校验、命令分发与桌面版同一套框架代码。
5
+
6
+ ## 目录
7
+
8
+ ```
9
+ {{PROJECT_NAME}}/
10
+ ├─ AppScope/app.json5 应用级配置(bundleName = {{BUNDLE_NAME}})
11
+ ├─ build-profile.json5 工程级构建配置(签名留空,见下)
12
+ ├─ build-hap.sh 构建入口(编 hap / 装到设备)
13
+ ├─ entry/
14
+ │ ├─ cjpm.toml cjTauri 依赖 + aarch64-linux-ohos 目标段
15
+ │ ├─ build-profile.json5 cangjieOptions.checkDeviceTypes=false(放行 2in1)
16
+ │ └─ src/main/
17
+ │ ├─ module.json5 deviceTypes: 2in1 / tablet / phone
18
+ │ ├─ cangjie/
19
+ │ │ ├─ ability_stage.cj AbilityStage
20
+ │ │ ├─ main_ability.cj UIAbility:沙箱、rawfile 读取、窗口尺寸、loadContent
21
+ │ │ ├─ index.cj ArkWeb 宿主组件(javaScriptProxy 双向桥)
22
+ │ │ └─ app_host.cj 框架装配 + demo 命令(greet / timer)
23
+ │ └─ resources/rawfile/
24
+ │ ├─ index.html 页面(改这里)
25
+ │ └─ capabilities.json 能力清单(改这里)
26
+ ```
27
+
28
+ 页面与能力清单是 **hap 里的真文件**(rawfile),运行期由 Ability 读成字符串交给框架——
29
+ 所以 DevEco 里直接点 Build 也能编过,不需要先跑生成脚本。
30
+
31
+ ## 构建 / 装到设备
32
+
33
+ ```bash
34
+ # 1) 鸿蒙版 stdx(构建期要链它)
35
+ export CANGJIE_STDX_OHOS=<stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx
36
+
37
+ # 2) DevEco 安装根(脚本会自动探测几个常见位置,探测不到就自己给)
38
+ export DEVECO_WIN='D:\Program Files\Huawei\DevEco Studio'
39
+
40
+ # 3) 编 hap
41
+ bash build-hap.sh
42
+
43
+ # 4) 编完装到设备并拉起(需要设备已连上、应用已签名)
44
+ bash build-hap.sh --install
45
+ ```
46
+
47
+ 改了鸿蒙侧仓颉代码却没生效时:`bash build-hap.sh --clean`(hvigor 与框架 cjpm 各有一份增量缓存)。
48
+
49
+ ## 签名
50
+
51
+ 模板**不带证书**(调试证书由每台机器的 DevEco 本机生成、与华为账号绑定,不该入库)。
52
+ 首次打开请在 DevEco 里 File → Project Structure → Signing Configs,
53
+ 勾上「Automatically generate signature」。不签名也能编(产物是 `*-unsigned.hap`),但装不进设备。
54
+
55
+ ## 改应用
56
+
57
+ - **加命令**:在 `app_host.cj` 里写 `CommandHandler` → `.register("名字", XxxCommand())` →
58
+ 在 `entry/src/main/resources/rawfile/capabilities.json` 的 `commands` 里声明。三处缺一处就用不了
59
+ (只缺注册 → `command not registered`;只缺清单 → `command not allowed`)。
60
+ - **改页面**:`entry/src/main/resources/rawfile/index.html`,前端只经 `window.__CJ_TAURI__`
61
+ 的 `invoke` / `listen`,并且要**等桥出现**(ArkWeb 没有 document-start 注入)。
62
+ - **联网**:`module.json5` 已声明 `ohos.permission.INTERNET`;用不到就删掉。
63
+ - **窗口尺寸**:`main_ability.cj` **默认不动窗口**——鸿蒙 PC 按桌面形态给的那扇窗(本机 3120×2080 屏上
64
+ `2090×1394 px`,约屏宽 2/3)就是合适的大小,`logDefaultWindowRect()` 只把它读出来打一行 hilog 当凭证。
65
+ 别在 `onWindowStageCreate` 里 `resize()`:系统先按默认尺寸建窗、你的 resize 紧接着改它,用户看到的是
66
+ 「先弹一个尺寸、随即变成另一个」,观感就是窗口莫名跳一下(这是本模板撤掉两版自定尺寸的原因)。
67
+ **真要固定尺寸**就自己调 `windowStage.getMainWindow().resize(w, h)`,两个坑记着:`resize()` 收的是**像素**
68
+ 不是 vp(写 vp 量级会得到一扇很小的窗);`densityPixels` 是 **Float32**,跟 `Float64` 直接相乘编不过,
69
+ 先化成整数百分比再算。
70
+ 上述「系统给的就是桌面窗」的前提是 `entry/build-profile.json5` 的 `deviceTypes` 为 `2in1` 且
71
+ `cangjieOptions.checkDeviceTypes=false`——写成 phone/tablet 时系统按手机应用塞进竖屏兼容窗(约 600 宽)。
72
+
73
+ ## 已知边界
74
+
75
+ - 只做了单窗口:`index.cj` 的 `EntryView` 挂一扇窗(label = `main`)。多窗口要按 label 各挂一个宿主。
76
+ - 鸿蒙侧 `javaScriptProxy` 的**返回值传不回页面**,回投靠页面心跳 + `runJavaScript` 推送
77
+ (已在 `index.cj` 的 `onProxyPost` 里做好,业务代码不用管)。
78
+ - `app.run(html)` 的 `quit()` 只置退出意图,进程仍归系统管(需要真退就 `hdc shell aa force-stop`)。
79
+ - 更多平台细节与踩坑:`docs/鸿蒙ArkWeb接入踩坑.md`(框架仓库内)。
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env bash
2
+ # {{PROJECT_NAME}} —— 鸿蒙 PC(2in1)HAP 构建入口
3
+ #
4
+ # 用法:
5
+ # bash build-hap.sh 编 hap(产物 entry/build/default/outputs/default/*.hap)
6
+ # bash build-hap.sh --install 编完后装到设备并拉起(需要设备已连上且应用已签名)
7
+ # bash build-hap.sh --clean 先删 entry/build 再编(改了 CJ 代码却没生效时用)
8
+ #
9
+ # 前置(三道,缺哪道都只会得到一句误导性的报错,所以脚本里逐条前置检查):
10
+ # 1. DevEco Studio 6.1+(自带 hvigor 与 node)。安装根默认探测几个常见位置,
11
+ # 不在那儿就 export DEVECO_WIN=<安装根,Windows 形式>。
12
+ # 2. 鸿蒙版 stdx:export CANGJIE_STDX_OHOS=<stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx
13
+ # —— 缺它 cjpm 会报成「框架缺依赖」,看着像 cjpm.toml 少写了一条。
14
+ # 3. 签名:模板不随包分发证书。**在 DevEco 里** File → Project Structure → Signing Configs
15
+ # 勾「Automatically generate signature」(要登录华为账号)。不签也能 assembleHap,
16
+ # 产物是 *-unsigned.hap,但装不进设备(hdc install 会报 9568289 之类的签名错误)。
17
+ set -e
18
+
19
+ ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
20
+ do_install=0
21
+ do_clean=0
22
+ for arg in "$@"; do
23
+ case "$arg" in
24
+ --install) do_install=1 ;;
25
+ --clean) do_clean=1 ;;
26
+ -h|--help) sed -n '2,20p' "$0"; exit 0 ;;
27
+ *) echo "[ohos] 未知参数:$arg(支持 --install / --clean / --help)" >&2; exit 2 ;;
28
+ esac
29
+ done
30
+
31
+ cd "$ROOT_DIR"
32
+
33
+ # 环境里可能预置着另一份 DevEco 的路径(有的机器留着 D:\Program Files\Huawei\DevEco Studio\sdk
34
+ # 这种不存在的旧值),hvigor 会直接判「Invalid value of 'DEVECO_SDK_HOME'」——
35
+ # 给了但目录不存在就回退到 DEVECO_WIN 下,并说明忽略了谁。
36
+ pick_deveco_path() {
37
+ local cand="$1" fb="$2"
38
+ if [ -n "$cand" ] && [ -d "$(cygpath -u "$cand")" ]; then
39
+ printf '%s' "$cand"
40
+ return 0
41
+ fi
42
+ [ -n "$cand" ] && echo "[ohos] 忽略无效路径:$cand,改用 $fb" >&2
43
+ printf '%s' "$fb"
44
+ }
45
+
46
+ # DevEco 安装根探测:DEVECO_WIN 优先,否则试几个常见位置,都不行就报一句能照做的错。
47
+ find_deveco() {
48
+ if [ -n "${DEVECO_WIN:-}" ]; then
49
+ printf '%s' "$DEVECO_WIN"
50
+ return 0
51
+ fi
52
+ local cand
53
+ for cand in \
54
+ "D:\\oh\\DevEcoStudio" \
55
+ "C:\\Program Files\\Huawei\\DevEco Studio" \
56
+ "D:\\Program Files\\Huawei\\DevEco Studio" \
57
+ "C:\\Program Files (x86)\\Huawei\\DevEco Studio"; do
58
+ if [ -d "$(cygpath -u "$cand")/tools/hvigor" ]; then
59
+ printf '%s' "$cand"
60
+ return 0
61
+ fi
62
+ done
63
+ return 1
64
+ }
65
+
66
+ if ! DEVECO_WIN="$(find_deveco)"; then
67
+ echo "[ohos] 找不到 DevEco Studio。请 export DEVECO_WIN=<安装根,Windows 形式>" >&2
68
+ echo " 例:export DEVECO_WIN='D:\\Program Files\\Huawei\\DevEco Studio'" >&2
69
+ exit 1
70
+ fi
71
+ export DEVECO_WIN
72
+ # 注意分隔符:hvigor 会校验这两个变量,混合斜杠会被判「Invalid value」。
73
+ export DEVECO_SDK_HOME="$(pick_deveco_path "${DEVECO_SDK_HOME:-}" "$DEVECO_WIN\\sdk")"
74
+ export NODE_HOME="$(pick_deveco_path "${NODE_HOME:-}" "$DEVECO_WIN\\tools\\node")"
75
+
76
+ : "${CANGJIE_STDX_OHOS:?请先 export CANGJIE_STDX_OHOS=<鸿蒙版 stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx}"
77
+
78
+ # hvigor 拿的是 Windows 形式的环境变量,bash 这边起 node 用 POSIX 形式(MSYS 只认后者)
79
+ dev_posix="$(cygpath -u "$DEVECO_WIN")"
80
+ node_exe="$dev_posix/tools/node/node.exe"
81
+ hvigor_js="$dev_posix/tools/hvigor/bin/hvigorw.js"
82
+ if [ ! -f "$hvigor_js" ]; then
83
+ echo "[ohos] $hvigor_js 不存在——DEVECO_WIN 指对了没?" >&2
84
+ exit 1
85
+ fi
86
+
87
+ # 仓颉插件的位置由 hvigorw 自己按顺序找,找到后它自己把工程 node_modules/@ohos 下的 junction
88
+ # 建起来(不用手工 mklink)。headless 从命令行起必须自己给,否则报的是
89
+ # 「property name must be valid: cangjieOptions」——看着像工程配置写错,其实是插件没被加载。
90
+ if [ -z "${DEVECO_HVIGOR_CANGJIE_PLUGIN:-}" ]; then
91
+ appdata_posix="$(cygpath -u "${APPDATA:-$USERPROFILE\\AppData\\Roaming}")"
92
+ cjb="$(ls -d "$appdata_posix"/Huawei/DevEcoStudio*/plugins/devecostudio-cangjie-plugin-*/lib/hvigor/cangjie-build-support 2>/dev/null | head -1)"
93
+ if [ -n "$cjb" ]; then
94
+ export DEVECO_HVIGOR_CANGJIE_PLUGIN="$(cygpath -w "$cjb")"
95
+ else
96
+ echo "[ohos] 找不到仓颉构建插件(DevEco 用户目录 plugins/devecostudio-cangjie-plugin-*/lib/hvigor/cangjie-build-support)" >&2
97
+ echo " 请 export DEVECO_HVIGOR_CANGJIE_PLUGIN=<该目录>" >&2
98
+ exit 1
99
+ fi
100
+ fi
101
+
102
+ # 插件「能 require 到」≠「被判定为已启用」:未启用时它会主动把 cangjie 的 schema 删掉且不报错
103
+ # (终端只剩 00303038 一行)。两个开关都给上。
104
+ export DEVECO_CANGJIE_PLUGIN_ENABLED=true
105
+ export NODE_PATH="$(cygpath -w "$(pwd)/node_modules")"
106
+
107
+ # SDK 自带的 cjpm.exe 硬依赖 libcangjie-runtime.dll(不在 cjpm.exe 旁边,在 runtime/lib 下):
108
+ # 缺它 hvigor 只报 01101000,底层退出码 0xC0000139,21ms 就「跑完」。
109
+ CANGJIE_SDK="${CANGJIE_SDK:-$(ls -d "$HOME"/.cangjie-sdk/*/cangjie 2>/dev/null | sort -V | tail -1)}"
110
+ cjsdk_rt="$CANGJIE_SDK/build-tools/runtime/lib/windows_x86_64_cjnative"
111
+ cjsdk_bin="$CANGJIE_SDK/build-tools/bin"
112
+ cjsdk_tools_bin="$CANGJIE_SDK/build-tools/tools/bin"
113
+ if [ -d "$cjsdk_rt" ]; then
114
+ export PATH="$cjsdk_rt:$PATH"
115
+ else
116
+ echo "[ohos] 找不到仓颉运行库目录($cjsdk_rt),cjpm 会以 0xC0000139 退出" >&2
117
+ echo " 请 export CANGJIE_SDK=<.cangjie-sdk/<版本>/cangjie>" >&2
118
+ fi
119
+
120
+ # cjc 与 opt 必须来自**同一套** SDK:cjc 按 CANGJIE_HOME 找 opt,PATH 上钉 cjc 而 CANGJIE_HOME
121
+ # 指向桌面 SDK 时会出现「1.1.3 的 cjc + 1.2.0 的 opt」→ EnumReflection format: operand numbers error!
122
+ if [ -d "$cjsdk_bin" ]; then
123
+ export PATH="$cjsdk_bin:$cjsdk_tools_bin:$PATH"
124
+ export CANGJIE_HOME="$(cygpath -w "$CANGJIE_SDK/build-tools")"
125
+ echo "[ohos] cjc = $(command -v cjc 2>/dev/null) ($(cjc --version 2>/dev/null | head -1))"
126
+ echo "[ohos] CANGJIE_HOME = $CANGJIE_HOME"
127
+ fi
128
+
129
+ # entry/cjpm.toml 里 stdx 那条写相对路径 "../stdx",指向**工程根上的 stdx 联接**:
130
+ # hvigor 只替换它自己的模板变量(${AARCH64_LIBS} 那组),写别的 ${...} 会变空串,
131
+ # 而空串让整条 path-option 一起失效(报错却是「not added as a dependency」,像漏配)。
132
+ stdx_link="$ROOT_DIR/stdx"
133
+ if [ ! -e "$stdx_link" ]; then
134
+ stdx_src="$(cygpath -u "$CANGJIE_STDX_OHOS")"
135
+ if [ ! -d "$stdx_src" ]; then
136
+ echo "[ohos] CANGJIE_STDX_OHOS 指向的目录不存在:$CANGJIE_STDX_OHOS" >&2
137
+ exit 1
138
+ fi
139
+ if command -v cmd >/dev/null 2>&1; then
140
+ cmd //c mklink //J "$(cygpath -w "$stdx_link")" "$(cygpath -w "$stdx_src")" >/dev/null || {
141
+ echo "[ohos] 建 stdx 联接失败:$stdx_link -> $stdx_src" >&2
142
+ exit 1
143
+ }
144
+ else
145
+ ln -s "$stdx_src" "$stdx_link"
146
+ fi
147
+ echo "[ohos] 已建 stdx 联接:$stdx_link -> $stdx_src"
148
+ fi
149
+
150
+ # 改了鸿蒙侧 CJ 代码却没生效时先怀疑产物:hvigor 的 CompileCangjie 与框架侧 cjpm **各有一份**
151
+ # 增量缓存,只清一边会把旧 .so 喂回来,对照实验当场失真(判据看产物尺寸,不看 build success)。
152
+ if [ "$do_clean" = "1" ]; then
153
+ echo "[ohos] 清理 entry/build 与框架 target/(--clean)"
154
+ rm -rf "$ROOT_DIR/entry/build"
155
+ fi
156
+
157
+ # **必须 `--no-daemon`**:hvigor 的常驻 daemon 里 CompileCangjie 用的是它启动时那份 env,
158
+ # 本脚本 export 的 PATH / CANGJIE_HOME 根本进不去(表现是「手跑 cjpm 成功、hvigor 一跑就 opt 崩」)。
159
+ mkdir -p .buildlog
160
+ "$node_exe" "$hvigor_js" --no-daemon --mode module -p product=default -p module=entry@default assembleHap 2>&1 | tee .buildlog/last-build.log
161
+
162
+ hap="$(ls -t "$ROOT_DIR"/entry/build/default/outputs/default/*.hap 2>/dev/null | head -1)"
163
+ if [ -z "$hap" ]; then
164
+ echo "[ohos] 构建结束但没有 hap 产物,请查 .buildlog/last-build.log" >&2
165
+ exit 1
166
+ fi
167
+ echo "[ohos] HAP = $hap ($(wc -c < "$hap") 字节)"
168
+ case "$hap" in
169
+ *unsigned*)
170
+ echo "[ohos] 注意:这是未签名包,不能装进设备。在 DevEco 里配好自动签名后重编。" >&2
171
+ do_install=0
172
+ ;;
173
+ esac
174
+
175
+ if [ "$do_install" = "1" ]; then
176
+ hdc="$dev_posix/sdk/default/openharmony/toolchains/hdc.exe"
177
+ if [ ! -f "$hdc" ]; then
178
+ echo "[ohos] 找不到 hdc:$hdc(--install 需要它;也可 export PATH 自带 hdc)" >&2
179
+ exit 1
180
+ fi
181
+ echo "[ohos] 已连设备:"
182
+ "$hdc" list targets
183
+ echo "[ohos] 安装 $hap"
184
+ # 路径交给 hdc 前一律 MSYS_NO_PATHCONV:Git Bash 会把 Windows 路径改写成 /d/… 形式,
185
+ # hdc 拿到就找不到文件。
186
+ MSYS_NO_PATHCONV=1 "$hdc" install -r "$(cygpath -w "$hap")"
187
+ echo "[ohos] 启动 EntryAbility"
188
+ # -a 要用 module.json5 里的 ability 名(EntryAbility),不是仓颉类名 MainAbility。
189
+ "$hdc" shell aa start -a EntryAbility -b {{BUNDLE_NAME}}
190
+ fi
@@ -0,0 +1,46 @@
1
+ {
2
+ "app": {
3
+ /* 签名留空:模板不随包分发证书(调试证书由每台机器的 DevEco 本机生成、与华为账号绑定,
4
+ 既不该入库也无法跨机器通用)。
5
+ 在 DevEco 里 File → Project Structure → Signing Configs 勾上
6
+ 「Automatically generate signature」,那一步会把 signingConfigs 填满。
7
+ 不签名也能 `assembleHap`(产物是 *-unsigned.hap),但装不进设备。 */
8
+ "signingConfigs": [],
9
+ "products": [
10
+ {
11
+ "name": "default",
12
+ "targetSdkVersion": "6.1.1(24)",
13
+ "compatibleSdkVersion": "6.1.1(24)",
14
+ "runtimeOS": "HarmonyOS",
15
+ "buildOption": {
16
+ "strictMode": {
17
+ "caseSensitiveCheck": true,
18
+ "useNormalizedOHMUrl": true
19
+ }
20
+ }
21
+ }
22
+ ],
23
+ "buildModeSet": [
24
+ {
25
+ "name": "debug",
26
+ },
27
+ {
28
+ "name": "release"
29
+ }
30
+ ]
31
+ },
32
+ "modules": [
33
+ {
34
+ "name": "entry",
35
+ "srcPath": "./entry",
36
+ "targets": [
37
+ {
38
+ "name": "default",
39
+ "applyToProducts": [
40
+ "default"
41
+ ]
42
+ }
43
+ ]
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "apiType": "stageMode",
3
+ "buildOption": {
4
+ "cangjieOptions": {
5
+ "path": "./cjpm.toml",
6
+ /* 关掉「deviceTypes 只能写 phone/tablet」这条门禁(DevEco 仓颉插件的
7
+ cangjie-pre-build.js 里 `cangjieOptions?.checkDeviceTypes !== false` 才校验)。
8
+ 不关的话 module.json5 写 "2in1" 会直接判 01103012(Only phone and tablet devices
9
+ are supported)——而只有声明 2in1,鸿蒙 PC 才按桌面应用开窗,否则塞进手机形态的
10
+ 竖屏兼容窗(实测约 600 宽)。 */
11
+ "checkDeviceTypes": false
12
+ },
13
+ "nativeLib": {
14
+ "filter": {
15
+ "enableOverride": true
16
+ }
17
+ }
18
+ },
19
+ "buildOptionSet": [
20
+ ],
21
+ "targets": [
22
+ {
23
+ "name": "default"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,43 @@
1
+ [package]
2
+ cjc-version = "1.1.3"
3
+ compile-option = "--dy-std --cfg=\"${COMPILE_CONDITION_ENTRY}\""
4
+ description = "{{PROJECT_NAME}} - cj-tauri 鸿蒙入 module"
5
+ name = "ohos_app_cangjie_entry"
6
+ output-type = "dynamic"
7
+ src-dir = "./src/main/cangjie"
8
+ target-dir = ""
9
+ version = "1.0.0"
10
+
11
+ [dependencies]
12
+ # 源码依赖:hvigor 的 CompileCangjie 会把框架各包按本模块(dynamic)需要的形态编进来。
13
+ # 路径由 `cj-tauri create --template ohos` 写成**本机绝对路径**;换机器 / 挪仓库后重跑一次
14
+ # create,或手改这一行(与桌面模板同一条规矩)。
15
+ cjTauri = { path = "{{FRAMEWORK_ROOT_TOML}}" }
16
+
17
+ [profile]
18
+ [profile.build]
19
+ incremental = true
20
+ lto = ""
21
+ [profile.customized-option]
22
+ debug = "-g -Woff all -Won apilevel-check"
23
+ release = "--fast-math -O2 -s -Woff all -Won apilevel-check"
24
+ [profile.test]
25
+
26
+ [target.aarch64-linux-ohos]
27
+ compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/aarch64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\""
28
+ [target.aarch64-linux-ohos.bin-dependencies]
29
+ # 鸿蒙版 stdx:这里写**相对路径**,指向工程根上的 `stdx` 联接(build-hap.sh 用
30
+ # CANGJIE_STDX_OHOS 建,不入库)。两条实测依据:
31
+ # ① hvigor 只替换它自己的模板变量(${AARCH64_LIBS} 这组),写别的 ${...} 会变成空串,
32
+ # 而空串会让**整条 path-option 一起失效**,报错却是
33
+ # 「imports package 'stdx.encoding.json' ... not added as a dependency」,看着像漏配;
34
+ # ② 本机绝对路径能编过,但换机器就得改文件,而 DevEco 里点 Build 不经过脚本——
35
+ # 相对路径两处都通用。
36
+ path-option = ["${AARCH64_LIBS}", "${AARCH64_MACRO_LIBS}", "${AARCH64_KIT_LIBS}", "../stdx"]
37
+ package-option = {}
38
+
39
+ [target.x86_64-linux-ohos]
40
+ compile-option = "-B \"${DEVECO_CANGJIE_HOME}/build-tools/third_party/llvm/bin\" -B \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/sysroot/usr/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/clang/15.0.4/lib/x86_64-linux-ohos\" -L \"${DEVECO_OH_NATIVE_HOME}/llvm/lib/x86_64-linux-ohos\" --sysroot \"${DEVECO_OH_NATIVE_HOME}/sysroot\""
41
+ [target.x86_64-linux-ohos.bin-dependencies]
42
+ path-option = ["${X86_64_OHOS_LIBS}", "${X86_64_OHOS_MACRO_LIBS}", "${X86_64_OHOS_KIT_LIBS}", "../stdx"]
43
+ package-option = {}