cj-tauri 0.8.0 → 1.1.1

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 +53 -7
  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
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # cj-tauri
2
2
 
3
- 用[仓颉语言](https://cangjie-lang.cn)写桌面应用的**类 Tauri 框架**脚手架 CLI:
4
- 仓颉后端(静态编译)+ 系统 WebView 前端(HTML/CSS/JS),三件套对标 Tauri ——
3
+ 用[仓颉语言](https://cangjie-lang.cn)写桌面应用的脚手架 CLI——把 Tauri 的三件套带到仓颉:
4
+ 仓颉后端(静态编译)+ 系统 WebView 前端(HTML/CSS/JS),三件套与 Tauri 一致 ——
5
5
  WebView 宿主、IPC 双向桥(`invoke` / `resolve` / `event`)、能力安全模型(capability 白名单)。
6
6
 
7
7
  - 仓库与完整文档:<https://atomgit.com/qq8864/cj-tauri>
8
- - 当前状态:**Windows(WebView2)与 Linux(WebKitGTK)均已实机跑通**;鸿蒙 ArkWeb 为架构预留位
8
+ - 当前状态:**Windows(WebView2)、Linux(WebKitGTK)与鸿蒙 PC(ArkWeb)均已实机跑通**
9
9
 
10
10
  ## 前置条件:仓颉 SDK
11
11
 
@@ -40,18 +40,64 @@ npx cj-tauri dev # 构建 C 桥 + cjpm build + 启动窗口
40
40
  npx cj-tauri create myapp-vue --template vue # Vue 3 + Vite
41
41
  npx cj-tauri create myapp-react --template react # React 18 + Vite
42
42
 
43
+ npx cj-tauri create myohos --template ohos # 鸿蒙 PC(2in1)工程:ArkWeb 宿主 + build-hap.sh
44
+ npx cj-tauri create myohos --template ohos --bundle com.example.myapp --label "我的应用"
45
+
43
46
  npm i -g cj-tauri # 也可以全局安装,之后直接用 cj-tauri <子命令>
44
47
  ```
45
48
 
46
49
  | 子命令 | 作用 |
47
50
  |---|---|
48
- | `create <项目名> [--template app\|vue\|react]` | 生成工程(`src/` + `ui/` + `capabilities/` + `cjpm.toml`) |
49
- | `dev` | 构建 C 桥 + `cjpm build` + 启动应用;Vite 模板会接管 dev server(HMR) |
50
- | `build` | 构建 C 桥 + `cjpm build` |
51
- | `run` | 运行已构建产物 |
51
+ | `create <项目名> [--template app\|vue\|react\|ohos]` | 生成工程(桌面模板:`src/` + `ui/` + `capabilities/` + `cjpm.toml`;`ohos` 见下一节) |
52
+ | `dev` | 构建 C 桥 + `cjpm build` + 启动应用;Vite 模板会接管 dev server(HMR);鸿蒙工程转交 `build-hap.sh` |
53
+ | `build` | 构建 C 桥 + `cjpm build`;鸿蒙工程转交 `build-hap.sh` |
54
+ | `run` | 运行已构建产物;鸿蒙工程转交 `build-hap.sh --install`(构建 + 装机 + 启动) |
52
55
  | `info` | 环境自检(排障先跑它) |
53
56
  | `help` | 帮助 |
54
57
 
58
+ ## 鸿蒙 PC(`--template ohos`)
59
+
60
+ 同一套框架换个宿主:桌面用 WebView2 / WebKitGTK,鸿蒙 PC 用 **ArkWeb**,上层页面、命令层、能力校验不用改。
61
+
62
+ ```bash
63
+ npx cj-tauri create myohos --template ohos
64
+ cd myohos
65
+ bash build-hap.sh --install # 构建 hap + hdc 装机 + aa start;等价于 npx cj-tauri run
66
+ ```
67
+
68
+ 生成的是标准 DevEco 工程(`entry/` + `AppScope/` + `build-profile.json5` + `hvigorfile.ts`),
69
+ 鸿蒙相关的那层很薄:
70
+
71
+ | 路径 | 作用 |
72
+ |---|---|
73
+ | `entry/src/main/cangjie/main_ability.cj` | UIAbility:`ArkWebRuntime.configure(filesDir)` → 非 UI 线程 `spawn { run() }` → 等宿主登记再 `loadContent` |
74
+ | `entry/src/main/cangjie/index.cj` | `@Component` 的 ArkWeb 宿主 + 双向桥(**必须住应用模块**,框架包引用 ArkUI 会启动即崩) |
75
+ | `entry/src/main/resources/rawfile/` | `index.html`(页面真文件)与 `capabilities.json`(能力清单) |
76
+ | `build-hap.sh` | 唯一构建入口:探测 DevEco / stdx、`--no-daemon` 跑 hvigor、可选 `--install` |
77
+
78
+ 前置两件事:
79
+
80
+ - **DevEco Studio 6.1**(含仓颉插件)+ 已配好签名。`build-hap.sh` 用 `DEVECO_WIN` 找安装根
81
+ (默认 `D:\oh\DevEcoStudio`),用 `CANGJIE_STDX_OHOS` 找鸿蒙版 stdx 动态库;两者给不出时脚本会直接说清楚缺哪个。
82
+ - 真机开着**开发者模式 + USB 调试**,`hdc` 能列到设备;`aa start` 需要**屏幕已解锁**。
83
+
84
+ 工程默认是桌面形态:`module.json5` 的 `deviceTypes` 写 `2in1`,并在 `entry/build-profile.json5` 里
85
+ `cangjieOptions.checkDeviceTypes = false`(插件默认只放行 `phone` / `tablet`,写 `2in1` 会判 `01103012`);
86
+ 窗口**不干预尺寸**,沿用系统给的默认矩形(实测本机 `2090×1394`,约屏宽 2/3 且居中)。
87
+
88
+ 已知限制:签名要在 DevEco 里配(命令行不做自动签名,换 `bundleName` 需重新签);同一份调试签名下两个
89
+ 工程会互相顶掉安装位;单窗口、暂无原生对话框(那两条能力位如实返回 0);没有可用的 C 桥,
90
+ `launchHidden` / 串口 / 托盘这类宿主能力在鸿蒙上不提供。
91
+
92
+ 真机上怎么自证它通了(hilog,`CjTauri` tag):
93
+
94
+ ```
95
+ host registered after N polls label=main
96
+ host pageUrl label=main url="file:///.../files/cj-tauri-main.html"
97
+ page begin label=main
98
+ main window keeps system default 2090x1394 px at density 190% on screen 3120x2080
99
+ ```
100
+
55
101
  ## 这个包装层做了什么
56
102
 
57
103
  - **跨平台入口**:Windows / Linux / Git Bash 都是同一条 `cj-tauri` 命令,不再分 `.bat` 与 `.sh`;
package/cjpm.toml CHANGED
@@ -1,25 +1,52 @@
1
- [package]
2
- cjc-version = "1.0.5"
3
- name = "cjTauri"
4
- description = "仓颉版 Tauri:轻量跨端混合开发框架(WebView 宿主 + IPC 桥 + 能力安全模型)"
5
- version = "0.8.0"
6
- target-dir = ""
7
- output-type = "static"
8
- compile-option = ""
9
- override-compile-option = ""
10
- link-option = ""
11
- package-configuration = {}
12
-
13
- [dependencies]
14
-
15
- [target.x86_64-unknown-linux-gnu]
16
- link-option = "-Lnative -lcjtbridge -lwebkit2gtk-4.1 -lgtk-3 -lgdk-3 -lglib-2.0 -lgio-2.0 -lsoup-3.0 -ljavascriptcoregtk-4.1 -lgobject-2.0"
17
-
18
- [target.x86_64-unknown-linux-gnu.bin-dependencies]
19
- path-option = ["/root/.cangjie/stdx/cangjie-stdx-linux-x64-1.0.5.1/dynamic/stdx"]
20
-
21
- [target.x86_64-w64-mingw32]
22
- link-option = "-Lnative -lcjtbridge"
23
-
24
- [target.x86_64-w64-mingw32.bin-dependencies]
25
- path-option = ["D:\\cangjie-stdx\\windows_x86_64_cjnative\\dynamic\\stdx"]
1
+ [package]
2
+ cjc-version = "1.0.5"
3
+ name = "cjTauri"
4
+ description = "仓颉版 Tauri:轻量跨端混合开发框架(WebView 宿主 + IPC 桥 + 能力安全模型)"
5
+ version = "1.1.1"
6
+ target-dir = ""
7
+ output-type = "static"
8
+ compile-option = ""
9
+ override-compile-option = ""
10
+ link-option = ""
11
+ package-configuration = {}
12
+
13
+ [dependencies]
14
+
15
+ [target.x86_64-unknown-linux-gnu]
16
+ # -lsqlite3:sqlite 插件直连发行版系统库(D-018 第 8 条);Windows 侧未落地,故 mingw 段不加
17
+ link-option = "-Lnative -lcjtbridge -lsqlite3 -lwebkit2gtk-4.1 -lgtk-3 -lgdk-3 -lglib-2.0 -lgio-2.0 -lsoup-3.0 -ljavascriptcoregtk-4.1 -lgobject-2.0"
18
+
19
+ [target.x86_64-unknown-linux-gnu.bin-dependencies]
20
+ path-option = ["${CANGJIE_STDX}"]
21
+
22
+ [target.x86_64-w64-mingw32]
23
+ # -lsqlite3:sqlite 插件子包需要(导入库 libsqlite3.a 由 sqlite3.def 生成,DLL 本身不入库——
24
+ # 跑 sqlite 相关测试/应用时本机自备 native/sqlite3.dll;不碰 sqlite 的应用不引用它就不链接)
25
+ link-option = "-Lnative -lcjtbridge -lsqlite3"
26
+
27
+ [target.x86_64-w64-mingw32.bin-dependencies]
28
+ path-option = ["${CANGJIE_STDX}"]
29
+
30
+ [target.aarch64-linux-ohos]
31
+ # 鸿蒙目标(真机 / 鸿蒙 PC):交叉编译工具链与 sysroot 由 DevEco 仓颉套件提供,
32
+ # 四个路径全部走环境变量展开——hvigor 构建时由插件注入前两个,命令行交叉编译时自行 export 同名变量。
33
+ # 照抄 DevEco 生成的工程(TestCj2)同一串参数,避免两套口径。
34
+ # 末尾的 `--dy-std` 不可省:鸿蒙上应用模块(entry)是 `output-type = "dynamic"` 且用动态 std,
35
+ # 框架包若按默认的静态 std 编,两者混进同一个 .so 就会出现**两套 std 运行时**——表象是
36
+ # 启动即崩在框架包全局初始化(`cjTauri::package_global_init` → 跨包 `_CGP*` 包初始化符号
37
+ # 解析到模块内偏移、跳到未映射地址 0x50xxx),窗口起不来且 hilog 只有一行 SIGSEGV。
38
+ # 只加在鸿蒙段:桌面两平台仍是静态 std,产物形态不变。
39
+ 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\" --dy-std"
40
+
41
+ [target.aarch64-linux-ohos.bin-dependencies]
42
+ # stdx 必须用鸿蒙版(官方发布 cangjie-stdx-ohos-aarch64-<cjc 版本>.zip,桌面版 ABI 不通);
43
+ # CANGJIE_OHOS_LIBS 指 DevEco 套件的 api/lib/linux_ohos_aarch64_cjnative/ohos(仓颉标准库 + ohos API 绑定)。
44
+ #
45
+ # 后两项是 ArkUI 的**宏库**与 kit 绑定,hvigor 构建时由 cangjie-build-support 注入
46
+ # (cangjie-path-impl.js 的 getBuildEnvByAbi):AARCH64_MACRO_LIBS = <sdk>/api/macro/ohos
47
+ # (`lib-macro_ohos.arkui.state_macro_manage.dll` 就在里面),AARCH64_KIT_LIBS =
48
+ # .../linux_ohos_aarch64_cjnative/kit。**宏库目录漏挂的症状极具误导性**:`@Component` 的
49
+ # 声明能解析到(.cjo 在 ohos 目录里),但宏实现库找不到,报的是
50
+ # `macro evaluation has failed for macro call 'Component'`——看着像「宏不能用在框架包里」。
51
+ # 命令行交叉编译时这两个变量要自己 export,取值见上。
52
+ path-option = ["D:/cangjie-stdx-ohos/linux_ohos_aarch64_cjnative/dynamic/stdx", "C:/Users/yang/.cangjie-sdk/6.1/cangjie/api/lib/linux_ohos_aarch64_cjnative/ohos", "${AARCH64_MACRO_LIBS}", "${AARCH64_KIT_LIBS}"]
package/cli/cjpm.toml CHANGED
@@ -2,7 +2,7 @@
2
2
  cjc-version = "1.0.5"
3
3
  name = "cli"
4
4
  description = "cj-tauri CLI:脚手架 create / dev / build / run / info"
5
- version = "0.8.0"
5
+ version = "1.1.1"
6
6
  target-dir = ""
7
7
  output-type = "executable"
8
8
  compile-option = ""
@@ -0,0 +1,304 @@
1
+ /**
2
+ * cj-tauri CLI:环境自检(doctor)
3
+ *
4
+ * 为什么要有它:`info` 打印的是「解析结果」(路径是什么),能用但看不出「这条到底算不算过」——
5
+ * 每条都只是一行文本,人得自己对照 skill 里的判据。doctor 反过来:**每条检查自带结论**,
6
+ * 失败的给出下一步该干什么,让用户拿到包第一件事不用读文档。
7
+ *
8
+ * 判据与坑(与 skills/cj-tauri-env 同口径,两处必须一起改):
9
+ * - 判「命令可用」一律以**命令自身退出码**为准,不能用 `command -v`:
10
+ * cjc / cjpm 本身是仓颉程序,二进制在 PATH 里但没设 LD_LIBRARY_PATH 时
11
+ * 照样加载不了运行时——stdout 空、exit=127,而路径查得到。
12
+ * - Linux 上仓颉运行时 .so **不查 PATH**,只设 PATH 的表现是「找不到 libcangjie-runtime.so」。
13
+ * - `CANGJIE_STDX` 必须指 stdx 的**动态库目录**(<stdx>/<三元组>/dynamic/stdx),
14
+ * 指到包根也能过 create,要到新工程里 cjpm build 才报 dependency info is missing。
15
+ */
16
+ package cli
17
+
18
+ import std.fs.*
19
+ import std.process.*
20
+ import std.time.*
21
+
22
+ /** 一条检查的结论:ok / warn / fail(只有 fail 影响退出码) */
23
+ enum CheckLevel {
24
+ | Ok
25
+ | Warn
26
+ | Fail
27
+ }
28
+
29
+ /** doctor 的自检结果统计 */
30
+ class DoctorReport {
31
+ var okCount = 0
32
+ var warnCount = 0
33
+ var failCount = 0
34
+
35
+ func record(level: CheckLevel): Unit {
36
+ match (level) {
37
+ case Ok => this.okCount++
38
+ case Warn => this.warnCount++
39
+ case Fail => this.failCount++
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * 打印一行检查结论并计入统计:` ok 项目名 : 说明`;失败时另起一行给「怎么办」。
46
+ * 计数与打印绑在一起,避免「打了 ok 却没记账」这类统计与输出不一致的假象
47
+ * (第一版就是各调用点手写 record,结果 ok 行全没计数,汇总报成 0 项通过)。
48
+ */
49
+ func doctorLine(rep: DoctorReport, level: CheckLevel, label: String, detail: String, hint: String): Unit {
50
+ rep.record(level)
51
+ let tag = match (level) {
52
+ case Ok => "ok "
53
+ case Warn => "warn"
54
+ case Fail => "FAIL"
55
+ }
56
+ println(" ${tag} ${label} : ${detail}")
57
+ if (hint.size > 0) {
58
+ println(" → ${hint}")
59
+ }
60
+ }
61
+
62
+ /** 跑一条命令取 (退出码, 首行非空 stdout),命令不存在时返回 (-1, "") */
63
+ func doctorProbe(exe: String, argv: Array<String>): (Int64, String) {
64
+ try {
65
+ // executeWithOutput 的 stdout 是 Array<UInt8>,按 UTF-8 解回字符串再按行取首行
66
+ let (code, rawOut, _) = executeWithOutput(exe, argv)
67
+ for (line in String.fromUtf8(rawOut).split("\n")) {
68
+ let t = trimAscii(line)
69
+ if (t.size > 0) {
70
+ return (code, t)
71
+ }
72
+ }
73
+ return (code, "")
74
+ } catch (e: Exception) {
75
+ let _ = e
76
+ return (-1, "")
77
+ }
78
+ }
79
+
80
+ /**
81
+ * 环境自检逐行输出。返回 0 = 必需项全过(warn 不算失败);1 = 有必需项失败。
82
+ *
83
+ * 必需项:cjc、cjpm、stdx、框架根、C 桥;其余是 warn(如项目不在、产物未构建、
84
+ * Windows 的 WebView2 提示)——那些是「还没到那一步」,不是环境坏。
85
+ */
86
+ func cmdDoctor(): Int64 {
87
+ let rep = DoctorReport()
88
+ println("cj-tauri doctor — 环境自检(CLI ${cliVersion()})")
89
+ println("")
90
+
91
+ // ---- 基本环境 ----
92
+ doctorLine(rep, CheckLevel.Ok, "宿主平台", "${hostOsName()} (${hostTriple()})", "")
93
+ doctorLine(rep, CheckLevel.Ok, "工作目录", currentDir(), "")
94
+
95
+ // ---- 仓颉 SDK ----
96
+ let home = resolveCangjieHome()
97
+ match (home) {
98
+ case Some(h) =>
99
+ doctorLine(rep, CheckLevel.Ok, "CANGJIE_HOME", h, "")
100
+ case None =>
101
+ doctorLine(rep, CheckLevel.Fail, "CANGJIE_HOME", "未找到",
102
+ "设置 CANGJIE_HOME,或把 SDK 的 bin 加进 PATH(doctor 从 cjc 反推)")
103
+ }
104
+
105
+ let (cjcCode, cjcVer) = doctorProbe("cjc", ["-v"])
106
+ if (cjcCode == 0) {
107
+ doctorLine(rep, CheckLevel.Ok, "cjc", cjcVer, "")
108
+ } else {
109
+ // 退出码 127 = 二进制在 PATH 里但运行时加载不了(Linux 上就是 LD_LIBRARY_PATH 没设)
110
+ let why = if (cjcCode == 127) {
111
+ "在 PATH 里但运行不了(exit=127,通常是 LD_LIBRARY_PATH 缺仓颉运行时)"
112
+ } else {
113
+ "不可用(exit=${cjcCode})"
114
+ }
115
+ doctorLine(rep, CheckLevel.Fail, "cjc", why,
116
+ "把 SDK 的 bin 加进 PATH;Linux 另需把 runtime/lib/<平台> 加进 LD_LIBRARY_PATH")
117
+ }
118
+
119
+ let (cjpmCode, cjpmVer) = doctorProbe("cjpm", ["-v"])
120
+ if (cjpmCode == 0) {
121
+ doctorLine(rep, CheckLevel.Ok, "cjpm", cjpmVer, "")
122
+ } else {
123
+ doctorLine(rep, CheckLevel.Fail, "cjpm", "不可用(exit=${cjpmCode})",
124
+ "把 SDK 的 tools/bin 加进 PATH(cjpm 还需要 tools/lib 与运行时)")
125
+ }
126
+
127
+ match (home) {
128
+ case Some(h) =>
129
+ match (findRuntimeLibDir(h)) {
130
+ case Some(rt) =>
131
+ // Linux 上「目录存在」不等于「已设进环境」——见文件头判据
132
+ if (hostIsWindows()) {
133
+ doctorLine(rep, CheckLevel.Ok, "仓颉运行时", rt, "")
134
+ } else {
135
+ let ld = envOr("LD_LIBRARY_PATH", "")
136
+ if (ld.contains(rt)) {
137
+ doctorLine(rep, CheckLevel.Ok, "仓颉运行时", "${rt} [已进 LD_LIBRARY_PATH]", "")
138
+ } else {
139
+ doctorLine(rep, CheckLevel.Warn, "仓颉运行时",
140
+ "${rt} [不在 LD_LIBRARY_PATH]",
141
+ "export LD_LIBRARY_PATH=\"${rt}:${'$'}LD_LIBRARY_PATH\"(CLI 自身会把子进程补上,手敲 cjc 才需要)")
142
+ }
143
+ }
144
+ case None =>
145
+ doctorLine(rep, CheckLevel.Fail, "仓颉运行时", "在 SDK 下未找到 runtime/lib/<平台>",
146
+ "检查 SDK 是否解压完整(runtime/lib/linux_x86_64_cjnative 等)")
147
+ }
148
+ case None => ()
149
+ }
150
+
151
+ // ---- 框架根 / stdx / C 桥 ----
152
+ var fwRoot = ""
153
+ match (resolveFrameworkRoot()) {
154
+ case Some(fw) =>
155
+ fwRoot = fw
156
+ doctorLine(rep, CheckLevel.Ok, "框架根目录", fw, "")
157
+ case None =>
158
+ doctorLine(rep, CheckLevel.Fail, "框架根目录", "未找到",
159
+ "在框架仓库内运行,或设置 CJ_TAURI_ROOT 指向框架根")
160
+ }
161
+
162
+ let projRoot = findProjectRoot()
163
+ // 显式标注:if 两分支各自返回 Option<String> / None,不标注会让类型推断拿不到实参
164
+ let stdx: Option<String> = if (fwRoot.size > 0) {
165
+ resolveStdx(fwRoot, projRoot)
166
+ } else {
167
+ None
168
+ }
169
+ match (stdx) {
170
+ case Some(s) =>
171
+ // 指到包根时 stdx 目录本身也存在,但 build 会报 dependency info is missing:
172
+ // 这里按「里面有没有 .so / 目录是否叫 stdx」粗判一次,把坑提前暴露
173
+ doctorLine(rep, CheckLevel.Ok, "stdx", s, "")
174
+ case None =>
175
+ doctorLine(rep, CheckLevel.Fail, "stdx", "未找到",
176
+ "设置 CANGJIE_STDX 指向 <stdx>/<三元组>/dynamic/stdx(动态库目录,不是包根)")
177
+ }
178
+
179
+ if (fwRoot.size > 0) {
180
+ let nativeDir = joinPath(fwRoot, "native")
181
+ let lib = joinPath(nativeDir, bridgeLibName())
182
+ if (exists(lib)) {
183
+ // 桥是本地产物、不入库:源码新过产物说明改了 C 桥没重建,链接期会 undefined reference
184
+ let newestSrc = newestBridgeSource(nativeDir)
185
+ if (newestSrc.size > 0 && FileInfo(lib).lastModificationTime < FileInfo(newestSrc).lastModificationTime) {
186
+ doctorLine(rep, CheckLevel.Warn, "C 桥", "${lib} [比源码旧]",
187
+ "改了 C 桥要重建:Linux `bash native/build_linux.sh`、Windows `native\\build_win.bat`")
188
+ } else {
189
+ doctorLine(rep, CheckLevel.Ok, "C 桥", "${lib} [已构建]", "")
190
+ }
191
+ } else {
192
+ doctorLine(rep, CheckLevel.Fail, "C 桥", "${lib} 不存在",
193
+ "`cj-tauri build` / `dev` 会自动构建;手敲则跑 native/build_linux.sh 或 build_win.bat")
194
+ }
195
+ }
196
+
197
+ // ---- 项目(可选:不在项目里不算环境问题) ----
198
+ match (projRoot) {
199
+ case Some(proj) =>
200
+ doctorLine(rep, CheckLevel.Ok, "项目根目录", proj, "")
201
+ match (findAppBinary(proj)) {
202
+ case Some(bin) =>
203
+ if (fwRoot.size > 0 && !appBinaryIsFresh(bin, fwRoot)) {
204
+ doctorLine(rep, CheckLevel.Warn, "可执行产物", "${bin} [比框架源码旧]",
205
+ "改了框架要重建应用:`cj-tauri build`(旧产物认不出新环境变量,会让 info 卡住)")
206
+ } else {
207
+ doctorLine(rep, CheckLevel.Ok, "可执行产物", bin, "")
208
+ }
209
+ case None =>
210
+ doctorLine(rep, CheckLevel.Warn, "可执行产物", "未构建", "`cj-tauri build`")
211
+ }
212
+ case None =>
213
+ doctorLine(rep, CheckLevel.Warn, "项目根目录", "当前不在 cj-tauri 项目内",
214
+ "cd 到项目根(有 cjpm.toml 的那层)再跑 build / dev")
215
+ }
216
+
217
+ // ---- 图形栈(Linux 必需;Windows 由 WebView2 承担) ----
218
+ if (hostIsWindows()) {
219
+ doctorLine(rep, CheckLevel.Ok, "WebView2", "由系统提供(Win11 自带 Runtime)",
220
+ "构建桥时 WebView2 SDK 版本不得高于本机 Runtime 版本")
221
+ } else {
222
+ doctorCheckLinuxGraphics(rep)
223
+ }
224
+
225
+ // ---- 汇总 ----
226
+ println("")
227
+ println("检查完毕:${rep.okCount} 项通过,${rep.warnCount} 项告警,${rep.failCount} 项失败")
228
+ if (rep.failCount > 0) {
229
+ println("按上面每条 FAIL 的 → 提示逐条补齐;只到 warn 说明环境够跑,只是还没到那一步。")
230
+ return 1
231
+ }
232
+ println("环境齐了(warn 项按需处理)。")
233
+ return 0
234
+ }
235
+
236
+ /**
237
+ * Linux 图形栈检查:C 桥的链接与运行都依赖 WebKitGTK / GTK3,缺 pkg-config 或开发包
238
+ * 会在链接期(找不到 -lwebkit2gtk-4.1)或运行期(找不到 .so)炸,提前在这里暴露。
239
+ */
240
+ func doctorCheckLinuxGraphics(rep: DoctorReport): Unit {
241
+ let (pkgCode, _) = doctorProbe("pkg-config", ["--version"])
242
+ if (pkgCode != 0) {
243
+ doctorLine(rep, CheckLevel.Fail, "pkg-config", "不可用",
244
+ "sudo apt install pkg-config gcc libwebkit2gtk-4.1-dev libgtk-3-dev")
245
+ return
246
+ }
247
+ let (wCode, wVer) = doctorProbe("pkg-config", ["--modversion", "webkit2gtk-4.1"])
248
+ if (wCode == 0 && wVer.size > 0) {
249
+ doctorLine(rep, CheckLevel.Ok, "webkit2gtk-4.1", wVer, "")
250
+ } else {
251
+ doctorLine(rep, CheckLevel.Fail, "webkit2gtk-4.1", "未找到",
252
+ "sudo apt install libwebkit2gtk-4.1-dev(C 桥链接它)")
253
+ }
254
+ let (gCode, gVer) = doctorProbe("pkg-config", ["--modversion", "gtk+-3.0"])
255
+ if (gCode == 0 && gVer.size > 0) {
256
+ doctorLine(rep, CheckLevel.Ok, "gtk+-3.0", gVer, "")
257
+ } else {
258
+ doctorLine(rep, CheckLevel.Fail, "gtk+-3.0", "未找到",
259
+ "sudo apt install libgtk-3-dev(C 桥链接它)")
260
+ }
261
+ let (cCode, cVer) = doctorProbe("gcc", ["--version"])
262
+ if (cCode == 0) {
263
+ doctorLine(rep, CheckLevel.Ok, "gcc", cVer, "")
264
+ } else {
265
+ doctorLine(rep, CheckLevel.Fail, "gcc", "不可用",
266
+ "sudo apt install gcc(构建 C 桥)")
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 框架 native/ 下最新的 C 源文件(含 core/ 与 platforms/),用于判桥产物新不新。
272
+ * 找不到任何源文件时返回空串(调用方跳过新旧比较)。
273
+ */
274
+ func newestBridgeSource(nativeDir: String): String {
275
+ var newest = ""
276
+ // FileInfo.lastModificationTime 是 DateTime(不是 epoch 整数),比较用 Option 装上一个值;
277
+ // 带泛型实参的 None 必须写全,裸 None 推不出 Option<T> 的实参
278
+ var newestTime: Option<DateTime> = Option<DateTime>.None
279
+ let dirs = [joinPath(nativeDir, "core"), joinPath(joinPath(nativeDir, "platforms"), hostOsName())]
280
+ for (d in dirs) {
281
+ if (!exists(d)) {
282
+ continue
283
+ }
284
+ for (f in Directory.readFrom(d)) {
285
+ let name = f.name
286
+ if (!(name.endsWith(".c") || name.endsWith(".h"))) {
287
+ continue
288
+ }
289
+ let full = joinPath(d, name)
290
+ let t = FileInfo(full).lastModificationTime
291
+ match (newestTime) {
292
+ case None =>
293
+ newestTime = Some(t)
294
+ newest = full
295
+ case Some(prev) =>
296
+ if (t > prev) {
297
+ newestTime = Some(t)
298
+ newest = full
299
+ }
300
+ }
301
+ }
302
+ }
303
+ return newest
304
+ }
package/cli/src/main.cj CHANGED
@@ -7,13 +7,16 @@ func usage(): Unit {
7
7
  println("cj-tauri ${cliVersion()} — 仓颉版 Tauri CLI(脚手架 + 构建 + 运行)")
8
8
  println("")
9
9
  println("用法:")
10
- println(" cj-tauri create <项目名> [--template <t>]")
10
+ println(" cj-tauri create <项目名> [--template <t>] [--bundle <bundleName>] [--label <应用名>]")
11
11
  println(" 创建新项目(src/ + ui/ + capabilities/ + cjpm.toml)")
12
12
  println(" 模板: app(内联单文件,缺省)/ vue / react")
13
- println(" cj-tauri dev 构建 C 桥 + cjpm build + 启动应用")
14
- println(" cj-tauri build 构建 C 桥 + cjpm build")
15
- println(" cj-tauri run 运行已构建产物")
13
+ println(" ohos(鸿蒙 PC / 2in1:ArkWeb 宿主 + build-hap.sh)")
14
+ println(" --bundle / --label 只对 ohos 模板有意义(缺省由项目名推导)")
15
+ println(" cj-tauri dev 构建 C 桥 + cjpm build + 启动应用(鸿蒙工程改走 build-hap.sh)")
16
+ println(" cj-tauri build 构建 C 桥 + cjpm build(鸿蒙工程 = 编 hap)")
17
+ println(" cj-tauri run 运行已构建产物(鸿蒙工程 = 装到设备并拉起)")
16
18
  println(" cj-tauri info [--json] 环境自检(排障用);--json 只输出插件清单(机器可读)")
19
+ println(" cj-tauri doctor 环境自检逐行给结论(每条自带「缺了就怎么办」)")
17
20
  println(" cj-tauri help 显示本帮助")
18
21
  println("")
19
22
  println("环境变量:")
@@ -45,28 +48,48 @@ main(args: Array<String>): Int64 {
45
48
  if (sub == "create") {
46
49
  var name = ""
47
50
  var tpl = "app"
51
+ var bundle = ""
52
+ var label = ""
48
53
  var i = 1
49
54
  while (i < args.size) {
50
55
  let a = args[i]
51
56
  if (a == "--template" || a == "-t") {
52
57
  if (i + 1 >= args.size) {
53
- eprintln("用法: cj-tauri create <项目名> [--template app|vue|react]")
58
+ eprintln("用法: cj-tauri create <项目名> [--template app|vue|react|ohos]")
54
59
  return 1
55
60
  }
56
61
  tpl = args[i + 1]
57
62
  i = i + 2
58
63
  continue
59
64
  }
65
+ if (a == "--bundle") {
66
+ if (i + 1 >= args.size) {
67
+ eprintln("用法: cj-tauri create <项目名> --template ohos [--bundle <bundleName>]")
68
+ return 1
69
+ }
70
+ bundle = args[i + 1]
71
+ i = i + 2
72
+ continue
73
+ }
74
+ if (a == "--label") {
75
+ if (i + 1 >= args.size) {
76
+ eprintln("用法: cj-tauri create <项目名> --template ohos [--label <应用名>]")
77
+ return 1
78
+ }
79
+ label = args[i + 1]
80
+ i = i + 2
81
+ continue
82
+ }
60
83
  if (name.size == 0) {
61
84
  name = a
62
85
  }
63
86
  i = i + 1
64
87
  }
65
88
  if (name.size == 0) {
66
- eprintln("用法: cj-tauri create <项目名> [--template app|vue|react]")
89
+ eprintln("用法: cj-tauri create <项目名> [--template app|vue|react|ohos]")
67
90
  return 1
68
91
  }
69
- return cmdCreate(name, tpl)
92
+ return cmdCreate(name, tpl, bundle, label)
70
93
  }
71
94
  if (sub == "dev") {
72
95
  return cmdDev()
@@ -88,6 +111,9 @@ main(args: Array<String>): Int64 {
88
111
  }
89
112
  return cmdInfo(asJson)
90
113
  }
114
+ if (sub == "doctor") {
115
+ return cmdDoctor()
116
+ }
91
117
  eprintln("未知子命令: ${sub}")
92
118
  println("")
93
119
  usage()
@@ -10,7 +10,7 @@ import std.collection.*
10
10
 
11
11
  /** CLI 版本:须与 cli/cjpm.toml 的 version、CHANGELOG 顶部版本一致(scripts/check-version.sh 校验) */
12
12
  func cliVersion(): String {
13
- return "0.8.0"
13
+ return "1.1.1"
14
14
  }
15
15
 
16
16
  /** 一次命令所需的路径上下文 */
@@ -443,7 +443,56 @@ func runProject(c: AppContext): Int64 {
443
443
  return code
444
444
  }
445
445
 
446
+ /** 某个目录下是不是鸿蒙脚手架工程(判据:build-hap.sh + entry/cjpm.toml 同时在) */
447
+ func isOhosProjectAt(dir: String): Bool {
448
+ return exists(joinPath(dir, "build-hap.sh")) && exists(joinPath(joinPath(dir, "entry"), "cjpm.toml"))
449
+ }
450
+
451
+ /**
452
+ * 当前工作目录属于哪个鸿蒙工程(也认「站在 entry/ 里」这种跑法)
453
+ *
454
+ * 不能复用 `findProjectRoot`:鸿蒙工程根**没有** cjpm.toml(那份在 entry/ 下),
455
+ * 按「向上找 cjpm.toml」会在工程内一路找不到、反而跑到外面某个仓颉工程上去。
456
+ */
457
+ func findOhosProjectRoot(): Option<String> {
458
+ let cur = stripTrailingSep(currentDir())
459
+ if (isOhosProjectAt(cur)) {
460
+ return Some(cur)
461
+ }
462
+ if (let Some(p) <- parentDir(cur)) {
463
+ if (isOhosProjectAt(p)) {
464
+ return Some(p)
465
+ }
466
+ }
467
+ return None
468
+ }
469
+
470
+ /**
471
+ * 鸿蒙工程:构建入口就是工程自带的 `build-hap.sh`
472
+ *
473
+ * 跑它而不是自己拼 hvigor 命令:DevEco 探测、cjc/opt 同代、stdx 联接、`--no-daemon`
474
+ * 这些坑全在脚本里(见模板 build-hap.sh 的注释),CLI 只负责传参。
475
+ * `--install` 走 `run`:编完装到设备并 `aa start`。
476
+ */
477
+ func buildOhos(root: String, install: Bool): Int64 {
478
+ var argv = ArrayList<String>()
479
+ argv.add("build-hap.sh")
480
+ if (install) {
481
+ argv.add("--install")
482
+ }
483
+ println("[cj-tauri] 鸿蒙工程:bash build-hap.sh${if (install) { " --install" } else { "" }}(${root})")
484
+ let code = execute("bash", argv.toArray(), workingDirectory: Path(root))
485
+ if (code != 0) {
486
+ eprintln("[cj-tauri] 构建失败(exit=${code})")
487
+ eprintln(" 日志: ${joinPath(root, ".buildlog")}/last-build.log")
488
+ }
489
+ return code
490
+ }
491
+
446
492
  func cmdBuild(): Int64 {
493
+ if (let Some(root) <- findOhosProjectRoot()) {
494
+ return buildOhos(root, false)
495
+ }
447
496
  if (let Some(c) <- loadContext()) {
448
497
  if (buildProject(c, true)) {
449
498
  return 0
@@ -454,6 +503,12 @@ func cmdBuild(): Int64 {
454
503
  }
455
504
 
456
505
  func cmdDev(): Int64 {
506
+ if (let Some(root) <- findOhosProjectRoot()) {
507
+ // 鸿蒙没有「dev server 接管页面」这条路:页面是 hap 里的 rawfile,
508
+ // 改一次要重编一次;dev 就等同于 build(再加上装到设备更常见,留给 run)。
509
+ println("[cj-tauri] 鸿蒙工程:dev 等同于 build(页面在 hap 的 rawfile 里,没有 dev server)")
510
+ return buildOhos(root, false)
511
+ }
457
512
  if (let Some(c) <- loadContext()) {
458
513
  // 有 ui/package.json 才走 Node 路径:dev 模式页面来自 dev server,不必先打发布包
459
514
  var withUi = true
@@ -475,6 +530,9 @@ func cmdDev(): Int64 {
475
530
  }
476
531
 
477
532
  func cmdRun(): Int64 {
533
+ if (let Some(root) <- findOhosProjectRoot()) {
534
+ return buildOhos(root, true)
535
+ }
478
536
  if (let Some(c) <- loadContext()) {
479
537
  return runProject(c)
480
538
  }