cj-tauri 2.0.0 → 2.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 (158) hide show
  1. package/README.md +113 -99
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +63 -52
  4. package/cli/cjpm.toml +22 -22
  5. package/cli/src/doctor.cj +308 -306
  6. package/cli/src/main.cj +129 -129
  7. package/cli/src/manifest.cj +632 -558
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -770
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +369 -369
  12. package/cli/src/sync.cj +526 -526
  13. package/cli/src/util.cj +168 -168
  14. package/cli/templates/app/README.md +51 -48
  15. package/cli/templates/app/assets/capabilities/default.json +7 -7
  16. package/cli/templates/app/assets/ui/index.html +120 -118
  17. package/cli/templates/app/cj-tauri.toml +32 -22
  18. package/cli/templates/app/cjpm.toml +34 -34
  19. package/cli/templates/app/src/main.cj +101 -90
  20. package/cli/templates/app-react/README.md +56 -56
  21. package/cli/templates/app-react/assets/capabilities/default.json +7 -7
  22. package/cli/templates/app-react/assets/ui/index.html +25 -25
  23. package/cli/templates/app-react/assets/ui/package.json +19 -19
  24. package/cli/templates/app-react/assets/ui/src/App.css +67 -67
  25. package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
  26. package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
  27. package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
  28. package/cli/templates/app-react/cj-tauri.toml +33 -23
  29. package/cli/templates/app-react/cjpm.toml +34 -34
  30. package/cli/templates/app-react/src/main.cj +91 -91
  31. package/cli/templates/app-vue/README.md +56 -56
  32. package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
  33. package/cli/templates/app-vue/assets/ui/index.html +25 -25
  34. package/cli/templates/app-vue/assets/ui/package.json +18 -18
  35. package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
  36. package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
  37. package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
  38. package/cli/templates/app-vue/cj-tauri.toml +33 -23
  39. package/cli/templates/app-vue/cjpm.toml +34 -34
  40. package/cli/templates/app-vue/src/main.cj +91 -91
  41. package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
  42. package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
  43. package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
  44. package/cli/templates/ohos-skeleton/README.md +84 -79
  45. package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
  46. package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
  47. package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
  48. package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
  49. package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
  50. package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
  51. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
  52. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
  53. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  54. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
  55. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
  56. package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
  57. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
  58. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
  59. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
  60. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
  61. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
  62. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
  63. package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
  64. package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
  65. package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
  66. package/native/build_win.bat +62 -62
  67. package/native/core/bridge_core.c +1681 -1626
  68. package/native/core/bridge_core.h +675 -618
  69. package/native/core/bridge_js.h +108 -108
  70. package/native/platforms/harmonyos/README.md +115 -6
  71. package/native/platforms/linux/bridge_linux.c +2787 -2485
  72. package/native/platforms/windows/bridge_win.c +3740 -3248
  73. package/native/tests/gtk_threading_probe.c +143 -143
  74. package/native/tests/test_bridge_core.c +1339 -1228
  75. package/package.json +53 -53
  76. package/prebuilt/linux-x64/cj-tauri +0 -0
  77. package/src/api_system.cj +67 -67
  78. package/src/app.cj +325 -969
  79. package/src/app_events.cj +166 -0
  80. package/src/app_host_ops.cj +287 -0
  81. package/src/app_run.cj +283 -0
  82. package/src/app_spec.cj +76 -76
  83. package/src/arkweb_menu.cj +216 -0
  84. package/src/arkweb_script.cj +142 -142
  85. package/src/capability.cj +409 -409
  86. package/src/clipboard_bridge.cj +178 -0
  87. package/src/dialog_spec.cj +79 -0
  88. package/src/dragdrop.cj +26 -26
  89. package/src/embedded_assets.cj +98 -98
  90. package/src/host.cj +752 -607
  91. package/src/host_arkweb.cj +924 -778
  92. package/src/host_webkit.cj +663 -570
  93. package/src/host_webview2.cj +663 -570
  94. package/src/hotkey.cj +66 -66
  95. package/src/ipc_hub.cj +337 -337
  96. package/src/ipc_message.cj +286 -286
  97. package/src/launch.cj +62 -62
  98. package/src/logging.cj +127 -127
  99. package/src/menu.cj +197 -197
  100. package/src/platform/platform_app_context.cj +77 -77
  101. package/src/platform/platform_assets.cj +33 -33
  102. package/src/platform/platform_discover.cj +176 -176
  103. package/src/platform/platform_env.cj +29 -29
  104. package/src/platform/platform_feature.cj +31 -31
  105. package/src/platform/platform_page_source.cj +87 -87
  106. package/src/platform/platform_target_info.cj +32 -32
  107. package/src/platform_host_provider.cj +87 -87
  108. package/src/plugin.cj +333 -333
  109. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  110. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  111. package/src/plugins/fs/plugin_fs.cj +105 -1
  112. package/src/plugins/http/http_backend.cj +131 -0
  113. package/src/plugins/http/http_desktop.cj +195 -0
  114. package/src/plugins/http/http_ohos.cj +204 -0
  115. package/src/plugins/http/plugin_http.cj +310 -459
  116. package/src/plugins/log/plugin_log.cj +105 -105
  117. package/src/plugins/menu/plugin_menu.cj +158 -158
  118. package/src/plugins/notification/plugin_notification.cj +154 -154
  119. package/src/plugins/serial/plugin_serial.cj +458 -454
  120. package/src/plugins/serial/serial_bridge.cj +194 -194
  121. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  122. package/src/plugins/serial/serial_ohos.cj +529 -0
  123. package/src/plugins/shell/plugin_shell.cj +258 -258
  124. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  125. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  126. package/src/plugins/ws/plugin_ws.cj +488 -470
  127. package/src/process_hidden.cj +174 -174
  128. package/src/scope.cj +391 -391
  129. package/src/shell_event.cj +69 -69
  130. package/src/tests/app_spec_test.cj +126 -127
  131. package/src/tests/capability_test.cj +243 -243
  132. package/src/tests/dialog_spec_test.cj +73 -0
  133. package/src/tests/fake_host.cj +201 -201
  134. package/src/tests/ipc_hub_test.cj +487 -487
  135. package/src/tests/logging_test.cj +62 -62
  136. package/src/tests/platform_contract_test.cj +251 -251
  137. package/src/tests/plugin_clipboard_test.cj +222 -222
  138. package/src/tests/plugin_dialog_test.cj +237 -237
  139. package/src/tests/plugin_fs_test.cj +207 -0
  140. package/src/tests/plugin_http_test.cj +124 -124
  141. package/src/tests/plugin_notification_test.cj +205 -205
  142. package/src/tests/plugin_serial_test.cj +48 -0
  143. package/src/tests/plugin_test.cj +605 -585
  144. package/src/tests/plugin_ws_test.cj +40 -40
  145. package/src/tests/process_hidden_test.cj +129 -129
  146. package/src/tests/scope_test.cj +350 -350
  147. package/src/tests/system_host_test.cj +99 -81
  148. package/src/tests/urlutil_test.cj +53 -53
  149. package/src/tests/version_test.cj +75 -75
  150. package/src/tests/window_registry_test.cj +123 -123
  151. package/src/tray.cj +90 -90
  152. package/src/urlutil.cj +159 -159
  153. package/src/version.cj +44 -44
  154. package/src/window.cj +51 -51
  155. package/src/window_registry.cj +116 -116
  156. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  157. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  158. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
@@ -1,348 +1,348 @@
1
- /**
2
- * cj-tauri CLI:宿主平台 / 框架根目录 / stdx / 仓颉工具链 解析
3
- */
4
- package cli
5
-
6
- import std.env.*
7
- import std.fs.*
8
- import std.collection.*
9
-
10
- // ===== 宿主平台 =====
11
-
12
- func hostIsWindows(): Bool {
13
- if (let Some(os) <- getVariable("OS")) {
14
- if (os.contains("Windows")) {
15
- return true
16
- }
17
- }
18
- if (getVariable("WINDIR").isSome()) {
19
- return true
20
- }
21
- return false
22
- }
23
-
24
- func hostOsName(): String {
25
- if (hostIsWindows()) {
26
- return "Windows"
27
- }
28
- return "Linux"
29
- }
30
-
31
- func hostTriple(): String {
32
- if (hostIsWindows()) {
33
- return "x86_64-w64-mingw32"
34
- }
35
- return "x86_64-unknown-linux-gnu"
36
- }
37
-
38
- func bridgeLibName(): String {
39
- if (hostIsWindows()) {
40
- return "libcjtbridge.dll"
41
- }
42
- return "libcjtbridge.so"
43
- }
44
-
45
- func pathListSep(): String {
46
- if (hostIsWindows()) {
47
- return ";"
48
- }
49
- return ":"
50
- }
51
-
52
- func exeSuffix(): String {
53
- if (hostIsWindows()) {
54
- return ".exe"
55
- }
56
- return ""
57
- }
58
-
59
- /** 生成项目 cjpm.toml 里的 cjc-version(最低要求,非精确版本) */
60
- func minCjcVersion(): String {
61
- return "1.0.5"
62
- }
63
-
64
- // ===== 环境 / 工具链 =====
65
-
66
- func envOr(key: String, def: String): String {
67
- if (let Some(v) <- getVariable(key)) {
68
- return v
69
- }
70
- return def
71
- }
72
-
73
- func currentDir(): String {
74
- try {
75
- return stripTrailingSep(getWorkingDirectory().toString())
76
- } catch (e: Exception) {
77
- return "."
78
- }
79
- }
80
-
81
- /** 当前可执行文件路径(Windows 支持 getCommandLine,其它平台可能抛异常) */
82
- func currentExePath(): Option<String> {
83
- try {
84
- let cmd = getCommandLine()
85
- if (cmd.size > 0) {
86
- return Some(cmd[0])
87
- }
88
- } catch (e: Exception) {
89
- return None
90
- }
91
- return None
92
- }
93
-
94
- /** 在 PATH 中查找可执行文件(Windows 自动补 .exe) */
95
- func findOnPath(name: String): Option<String> {
96
- let path = envOr("PATH", "")
97
- for (dir in path.split(pathListSep())) {
98
- if (dir.size == 0) {
99
- continue
100
- }
101
- let cand = joinPath(stripTrailingSep(dir), name)
102
- if (exists(cand)) {
103
- return Some(cand)
104
- }
105
- if (hostIsWindows()) {
106
- let candExe = cand + ".exe"
107
- if (exists(candExe)) {
108
- return Some(candExe)
109
- }
110
- }
111
- }
112
- return None
113
- }
114
-
115
- /** 仓颉 SDK 根目录:CANGJIE_HOME → PATH 中的 cjc */
116
- func resolveCangjieHome(): Option<String> {
117
- if (let Some(h0) <- getVariable("CANGJIE_HOME")) {
118
- let h = stripTrailingSep(h0)
119
- if (exists(joinPath(h, "bin"))) {
120
- return Some(h)
121
- }
122
- }
123
- if (let Some(cjc) <- findOnPath("cjc")) {
124
- if (let Some(binDir) <- parentDir(cjc)) {
125
- if (let Some(home) <- parentDir(binDir)) {
126
- return Some(home)
127
- }
128
- }
129
- }
130
- return None
131
- }
132
-
133
- /** cjpm:PATH → <SDK>/tools/bin/cjpm[.exe] */
134
- func resolveCjpm(): Option<String> {
135
- if (let Some(p) <- findOnPath("cjpm")) {
136
- return Some(p)
137
- }
138
- if (let Some(home) <- resolveCangjieHome()) {
139
- let cand = joinPath(joinPath(joinPath(home, "tools"), "bin"), "cjpm" + exeSuffix())
140
- if (exists(cand)) {
141
- return Some(cand)
142
- }
143
- }
144
- return None
145
- }
146
-
147
- /** 仓颉运行时库目录(含 libcangjie-runtime) */
148
- func findRuntimeLibDir(home: String): Option<String> {
149
- let libRoot = joinPath(joinPath(home, "runtime"), "lib")
150
- if (!exists(libRoot)) {
151
- return None
152
- }
153
- var prefix = "linux"
154
- if (hostIsWindows()) {
155
- prefix = "windows"
156
- }
157
- for (info in listDir(libRoot)) {
158
- if (!info.isDirectory()) {
159
- continue
160
- }
161
- let nm = baseName(info.path.toString())
162
- if (nm.startsWith(prefix)) {
163
- return Some(joinPath(libRoot, nm))
164
- }
165
- }
166
- return None
167
- }
168
-
169
- // ===== 框架根目录 =====
170
-
171
- func looksLikeFrameworkRoot(dir: String): Bool {
172
- if (!exists(joinPath(dir, "cjpm.toml"))) {
173
- return false
174
- }
175
- return exists(joinPath(joinPath(joinPath(dir, "cli"), "templates"), "app"))
176
- }
177
-
178
- func findFrameworkRootFrom(start: String): Option<String> {
179
- var cur: Option<String> = Some(stripTrailingSep(start))
180
- var guard = 0
181
- while (guard < 64) {
182
- guard++
183
- if (let Some(d) <- cur) {
184
- if (looksLikeFrameworkRoot(d)) {
185
- return Some(d)
186
- }
187
- cur = parentDir(d)
188
- } else {
189
- return None
190
- }
191
- }
192
- return None
193
- }
194
-
195
- /** 框架根目录:CJ_TAURI_ROOT → 工作目录向上 → 可执行文件位置向上 */
196
- func resolveFrameworkRoot(): Option<String> {
197
- if (let Some(env) <- getVariable("CJ_TAURI_ROOT")) {
198
- let dir = stripTrailingSep(env)
199
- if (looksLikeFrameworkRoot(dir)) {
200
- return Some(dir)
201
- }
202
- eprintln("[cj-tauri] 告警: CJ_TAURI_ROOT=${env} 不是有效的 cj-tauri 框架根目录")
203
- }
204
- if (let Some(found) <- findFrameworkRootFrom(currentDir())) {
205
- return Some(found)
206
- }
207
- if (let Some(exe) <- currentExePath()) {
208
- if (let Some(dir) <- parentDir(exe)) {
209
- if (let Some(found) <- findFrameworkRootFrom(dir)) {
210
- return Some(found)
211
- }
212
- }
213
- }
214
- return None
215
- }
216
-
217
- // ===== TOML 取值 =====
218
-
219
- /** 取字符串中第一个 TOML 双引号串(并反转义 \\ 与 \") */
220
- func firstQuoted(s: String): Option<String> {
221
- let b = s.toArray()
222
- var i = 0
223
- while (i < b.size && b[i] != 0x22) {
224
- i++
225
- }
226
- if (i >= b.size) {
227
- return None
228
- }
229
- let out = ArrayList<UInt8>()
230
- var j = i + 1
231
- while (j < b.size) {
232
- if (b[j] == 0x5C && j + 1 < b.size) {
233
- out.add(b[j + 1])
234
- j += 2
235
- continue
236
- }
237
- if (b[j] == 0x22) {
238
- break
239
- }
240
- out.add(b[j])
241
- j++
242
- }
243
- return Some(String.fromUtf8(out.toArray()))
244
- }
245
-
246
- /** 从 cjpm.toml 文本取指定 target 段的 path-option 首项 */
247
- func pathOptionFor(content: String, triple: String): Option<String> {
248
- var inSection = false
249
- for (rawLine in content.split("\n")) {
250
- let line = trimAscii(rawLine)
251
- if (line.startsWith("[")) {
252
- inSection = line.contains(triple)
253
- continue
254
- }
255
- if (inSection && line.startsWith("path-option")) {
256
- if (let Some(v) <- firstQuoted(line)) {
257
- return Some(expandTomlEnv(v))
258
- }
259
- }
260
- }
261
- return None
262
- }
263
-
264
- /** cjpm.toml 里的 `${NAME}` 环境变量引用:与 cjpm 手册《环境变量配置》同款语义
265
- * (bin-dependencies 的 path-option 支持)。源码仓 24 个 toml 的 stdx 路径已统一改成
266
- * `${CANGJIE_STDX}`(issue #3),CLI 从 toml 读回 path-option 必须同款展开,
267
- * 否则拿到的是字面量 `…` 尾巴。逐字节扫描单遍展开(可含多个变量,不递归展开变量值——
268
- * 与 cjpm 行为一致)。String 本机 SDK 无 subString 成员,故按字节拼(镜像 firstQuoted)。 */
269
- func expandTomlEnv(s: String): String {
270
- let bytes = s.toArray()
271
- let n = bytes.size
272
- let out = ArrayList<UInt8>()
273
- var i = 0
274
- let DOLLAR: UInt8 = 0x24 // '$'
275
- let LBRACE: UInt8 = 0x7B // '{'
276
- let RBRACE: UInt8 = 0x7D // '}'
277
- while (i < n) {
278
- if (bytes[i] == DOLLAR && i + 1 < n && bytes[i + 1] == LBRACE) {
279
- var j = i + 2
280
- while (j < n && bytes[j] != RBRACE) {
281
- j++
282
- }
283
- if (j < n) {
284
- let nameBytes = ArrayList<UInt8>()
285
- var k = i + 2
286
- while (k < j) {
287
- nameBytes.add(bytes[k])
288
- k++
289
- }
290
- let name = String.fromUtf8(nameBytes.toArray())
291
- let value = envOr(name, "")
292
- let vbytes = value.toArray()
293
- var m = 0
294
- while (m < vbytes.size) {
295
- out.add(vbytes[m])
296
- m++
297
- }
298
- i = j + 1
299
- continue
300
- }
301
- }
302
- out.add(bytes[i])
303
- i++
304
- }
305
- return String.fromUtf8(out.toArray())
306
- }
307
-
308
- /** 项目 cjpm.toml 中 cjTauri 依赖路径 */
309
- func frameworkFromProject(projectRoot: String): Option<String> {
310
- let toml = joinPath(projectRoot, "cjpm.toml")
311
- if (!exists(toml)) {
312
- return None
313
- }
314
- for (rawLine in readText(toml).split("\n")) {
315
- let line = trimAscii(rawLine)
316
- if (line.startsWith("cjTauri")) {
317
- if (let Some(v) <- firstQuoted(line)) {
318
- return Some(stripTrailingSep(v))
319
- }
320
- }
321
- }
322
- return None
323
- }
324
-
325
- /** stdx 目录:CANGJIE_STDX → 项目 cjpm.toml → 框架 cjpm.toml */
326
- func resolveStdx(fwRoot: String, projectRoot: Option<String>): Option<String> {
327
- if (let Some(v0) <- getVariable("CANGJIE_STDX")) {
328
- let v = stripTrailingSep(v0)
329
- if (v.size > 0 && exists(v)) {
330
- return Some(v)
331
- }
332
- }
333
- if (let Some(proj) <- projectRoot) {
334
- let toml = joinPath(proj, "cjpm.toml")
335
- if (exists(toml)) {
336
- if (let Some(v) <- pathOptionFor(readText(toml), hostTriple())) {
337
- return Some(stripTrailingSep(v))
338
- }
339
- }
340
- }
341
- let fwToml = joinPath(fwRoot, "cjpm.toml")
342
- if (exists(fwToml)) {
343
- if (let Some(v) <- pathOptionFor(readText(fwToml), hostTriple())) {
344
- return Some(stripTrailingSep(v))
345
- }
346
- }
347
- return None
348
- }
1
+ /**
2
+ * cj-tauri CLI:宿主平台 / 框架根目录 / stdx / 仓颉工具链 解析
3
+ */
4
+ package cli
5
+
6
+ import std.env.*
7
+ import std.fs.*
8
+ import std.collection.*
9
+
10
+ // ===== 宿主平台 =====
11
+
12
+ func hostIsWindows(): Bool {
13
+ if (let Some(os) <- getVariable("OS")) {
14
+ if (os.contains("Windows")) {
15
+ return true
16
+ }
17
+ }
18
+ if (getVariable("WINDIR").isSome()) {
19
+ return true
20
+ }
21
+ return false
22
+ }
23
+
24
+ func hostOsName(): String {
25
+ if (hostIsWindows()) {
26
+ return "Windows"
27
+ }
28
+ return "Linux"
29
+ }
30
+
31
+ func hostTriple(): String {
32
+ if (hostIsWindows()) {
33
+ return "x86_64-w64-mingw32"
34
+ }
35
+ return "x86_64-unknown-linux-gnu"
36
+ }
37
+
38
+ func bridgeLibName(): String {
39
+ if (hostIsWindows()) {
40
+ return "libcjtbridge.dll"
41
+ }
42
+ return "libcjtbridge.so"
43
+ }
44
+
45
+ func pathListSep(): String {
46
+ if (hostIsWindows()) {
47
+ return ";"
48
+ }
49
+ return ":"
50
+ }
51
+
52
+ func exeSuffix(): String {
53
+ if (hostIsWindows()) {
54
+ return ".exe"
55
+ }
56
+ return ""
57
+ }
58
+
59
+ /** 生成项目 cjpm.toml 里的 cjc-version(最低要求,非精确版本) */
60
+ func minCjcVersion(): String {
61
+ return "1.0.5"
62
+ }
63
+
64
+ // ===== 环境 / 工具链 =====
65
+
66
+ func envOr(key: String, def: String): String {
67
+ if (let Some(v) <- getVariable(key)) {
68
+ return v
69
+ }
70
+ return def
71
+ }
72
+
73
+ func currentDir(): String {
74
+ try {
75
+ return stripTrailingSep(getWorkingDirectory().toString())
76
+ } catch (e: Exception) {
77
+ return "."
78
+ }
79
+ }
80
+
81
+ /** 当前可执行文件路径(Windows 支持 getCommandLine,其它平台可能抛异常) */
82
+ func currentExePath(): Option<String> {
83
+ try {
84
+ let cmd = getCommandLine()
85
+ if (cmd.size > 0) {
86
+ return Some(cmd[0])
87
+ }
88
+ } catch (e: Exception) {
89
+ return None
90
+ }
91
+ return None
92
+ }
93
+
94
+ /** 在 PATH 中查找可执行文件(Windows 自动补 .exe) */
95
+ func findOnPath(name: String): Option<String> {
96
+ let path = envOr("PATH", "")
97
+ for (dir in path.split(pathListSep())) {
98
+ if (dir.size == 0) {
99
+ continue
100
+ }
101
+ let cand = joinPath(stripTrailingSep(dir), name)
102
+ if (exists(cand)) {
103
+ return Some(cand)
104
+ }
105
+ if (hostIsWindows()) {
106
+ let candExe = cand + ".exe"
107
+ if (exists(candExe)) {
108
+ return Some(candExe)
109
+ }
110
+ }
111
+ }
112
+ return None
113
+ }
114
+
115
+ /** 仓颉 SDK 根目录:CANGJIE_HOME → PATH 中的 cjc */
116
+ func resolveCangjieHome(): Option<String> {
117
+ if (let Some(h0) <- getVariable("CANGJIE_HOME")) {
118
+ let h = stripTrailingSep(h0)
119
+ if (exists(joinPath(h, "bin"))) {
120
+ return Some(h)
121
+ }
122
+ }
123
+ if (let Some(cjc) <- findOnPath("cjc")) {
124
+ if (let Some(binDir) <- parentDir(cjc)) {
125
+ if (let Some(home) <- parentDir(binDir)) {
126
+ return Some(home)
127
+ }
128
+ }
129
+ }
130
+ return None
131
+ }
132
+
133
+ /** cjpm:PATH → <SDK>/tools/bin/cjpm[.exe] */
134
+ func resolveCjpm(): Option<String> {
135
+ if (let Some(p) <- findOnPath("cjpm")) {
136
+ return Some(p)
137
+ }
138
+ if (let Some(home) <- resolveCangjieHome()) {
139
+ let cand = joinPath(joinPath(joinPath(home, "tools"), "bin"), "cjpm" + exeSuffix())
140
+ if (exists(cand)) {
141
+ return Some(cand)
142
+ }
143
+ }
144
+ return None
145
+ }
146
+
147
+ /** 仓颉运行时库目录(含 libcangjie-runtime) */
148
+ func findRuntimeLibDir(home: String): Option<String> {
149
+ let libRoot = joinPath(joinPath(home, "runtime"), "lib")
150
+ if (!exists(libRoot)) {
151
+ return None
152
+ }
153
+ var prefix = "linux"
154
+ if (hostIsWindows()) {
155
+ prefix = "windows"
156
+ }
157
+ for (info in listDir(libRoot)) {
158
+ if (!info.isDirectory()) {
159
+ continue
160
+ }
161
+ let nm = baseName(info.path.toString())
162
+ if (nm.startsWith(prefix)) {
163
+ return Some(joinPath(libRoot, nm))
164
+ }
165
+ }
166
+ return None
167
+ }
168
+
169
+ // ===== 框架根目录 =====
170
+
171
+ func looksLikeFrameworkRoot(dir: String): Bool {
172
+ if (!exists(joinPath(dir, "cjpm.toml"))) {
173
+ return false
174
+ }
175
+ return exists(joinPath(joinPath(joinPath(dir, "cli"), "templates"), "app"))
176
+ }
177
+
178
+ func findFrameworkRootFrom(start: String): Option<String> {
179
+ var cur: Option<String> = Some(stripTrailingSep(start))
180
+ var guard = 0
181
+ while (guard < 64) {
182
+ guard++
183
+ if (let Some(d) <- cur) {
184
+ if (looksLikeFrameworkRoot(d)) {
185
+ return Some(d)
186
+ }
187
+ cur = parentDir(d)
188
+ } else {
189
+ return None
190
+ }
191
+ }
192
+ return None
193
+ }
194
+
195
+ /** 框架根目录:CJ_TAURI_ROOT → 工作目录向上 → 可执行文件位置向上 */
196
+ func resolveFrameworkRoot(): Option<String> {
197
+ if (let Some(env) <- getVariable("CJ_TAURI_ROOT")) {
198
+ let dir = stripTrailingSep(env)
199
+ if (looksLikeFrameworkRoot(dir)) {
200
+ return Some(dir)
201
+ }
202
+ eprintln("[cj-tauri] 告警: CJ_TAURI_ROOT=${env} 不是有效的 cj-tauri 框架根目录")
203
+ }
204
+ if (let Some(found) <- findFrameworkRootFrom(currentDir())) {
205
+ return Some(found)
206
+ }
207
+ if (let Some(exe) <- currentExePath()) {
208
+ if (let Some(dir) <- parentDir(exe)) {
209
+ if (let Some(found) <- findFrameworkRootFrom(dir)) {
210
+ return Some(found)
211
+ }
212
+ }
213
+ }
214
+ return None
215
+ }
216
+
217
+ // ===== TOML 取值 =====
218
+
219
+ /** 取字符串中第一个 TOML 双引号串(并反转义 \\ 与 \") */
220
+ func firstQuoted(s: String): Option<String> {
221
+ let b = s.toArray()
222
+ var i = 0
223
+ while (i < b.size && b[i] != 0x22) {
224
+ i++
225
+ }
226
+ if (i >= b.size) {
227
+ return None
228
+ }
229
+ let out = ArrayList<UInt8>()
230
+ var j = i + 1
231
+ while (j < b.size) {
232
+ if (b[j] == 0x5C && j + 1 < b.size) {
233
+ out.add(b[j + 1])
234
+ j += 2
235
+ continue
236
+ }
237
+ if (b[j] == 0x22) {
238
+ break
239
+ }
240
+ out.add(b[j])
241
+ j++
242
+ }
243
+ return Some(String.fromUtf8(out.toArray()))
244
+ }
245
+
246
+ /** 从 cjpm.toml 文本取指定 target 段的 path-option 首项 */
247
+ func pathOptionFor(content: String, triple: String): Option<String> {
248
+ var inSection = false
249
+ for (rawLine in content.split("\n")) {
250
+ let line = trimAscii(rawLine)
251
+ if (line.startsWith("[")) {
252
+ inSection = line.contains(triple)
253
+ continue
254
+ }
255
+ if (inSection && line.startsWith("path-option")) {
256
+ if (let Some(v) <- firstQuoted(line)) {
257
+ return Some(expandTomlEnv(v))
258
+ }
259
+ }
260
+ }
261
+ return None
262
+ }
263
+
264
+ /** cjpm.toml 里的 `${NAME}` 环境变量引用:与 cjpm 手册《环境变量配置》同款语义
265
+ * (bin-dependencies 的 path-option 支持)。源码仓 24 个 toml 的 stdx 路径已统一改成
266
+ * `${CANGJIE_STDX}`(issue #3),CLI 从 toml 读回 path-option 必须同款展开,
267
+ * 否则拿到的是字面量 `…` 尾巴。逐字节扫描单遍展开(可含多个变量,不递归展开变量值——
268
+ * 与 cjpm 行为一致)。String 本机 SDK 无 subString 成员,故按字节拼(镜像 firstQuoted)。 */
269
+ func expandTomlEnv(s: String): String {
270
+ let bytes = s.toArray()
271
+ let n = bytes.size
272
+ let out = ArrayList<UInt8>()
273
+ var i = 0
274
+ let DOLLAR: UInt8 = 0x24 // '$'
275
+ let LBRACE: UInt8 = 0x7B // '{'
276
+ let RBRACE: UInt8 = 0x7D // '}'
277
+ while (i < n) {
278
+ if (bytes[i] == DOLLAR && i + 1 < n && bytes[i + 1] == LBRACE) {
279
+ var j = i + 2
280
+ while (j < n && bytes[j] != RBRACE) {
281
+ j++
282
+ }
283
+ if (j < n) {
284
+ let nameBytes = ArrayList<UInt8>()
285
+ var k = i + 2
286
+ while (k < j) {
287
+ nameBytes.add(bytes[k])
288
+ k++
289
+ }
290
+ let name = String.fromUtf8(nameBytes.toArray())
291
+ let value = envOr(name, "")
292
+ let vbytes = value.toArray()
293
+ var m = 0
294
+ while (m < vbytes.size) {
295
+ out.add(vbytes[m])
296
+ m++
297
+ }
298
+ i = j + 1
299
+ continue
300
+ }
301
+ }
302
+ out.add(bytes[i])
303
+ i++
304
+ }
305
+ return String.fromUtf8(out.toArray())
306
+ }
307
+
308
+ /** 项目 cjpm.toml 中 cjTauri 依赖路径 */
309
+ func frameworkFromProject(projectRoot: String): Option<String> {
310
+ let toml = joinPath(projectRoot, "cjpm.toml")
311
+ if (!exists(toml)) {
312
+ return None
313
+ }
314
+ for (rawLine in readText(toml).split("\n")) {
315
+ let line = trimAscii(rawLine)
316
+ if (line.startsWith("cjTauri")) {
317
+ if (let Some(v) <- firstQuoted(line)) {
318
+ return Some(stripTrailingSep(v))
319
+ }
320
+ }
321
+ }
322
+ return None
323
+ }
324
+
325
+ /** stdx 目录:CANGJIE_STDX → 项目 cjpm.toml → 框架 cjpm.toml */
326
+ func resolveStdx(fwRoot: String, projectRoot: Option<String>): Option<String> {
327
+ if (let Some(v0) <- getVariable("CANGJIE_STDX")) {
328
+ let v = stripTrailingSep(v0)
329
+ if (v.size > 0 && exists(v)) {
330
+ return Some(v)
331
+ }
332
+ }
333
+ if (let Some(proj) <- projectRoot) {
334
+ let toml = joinPath(proj, "cjpm.toml")
335
+ if (exists(toml)) {
336
+ if (let Some(v) <- pathOptionFor(readText(toml), hostTriple())) {
337
+ return Some(stripTrailingSep(v))
338
+ }
339
+ }
340
+ }
341
+ let fwToml = joinPath(fwRoot, "cjpm.toml")
342
+ if (exists(fwToml)) {
343
+ if (let Some(v) <- pathOptionFor(readText(fwToml), hostTriple())) {
344
+ return Some(stripTrailingSep(v))
345
+ }
346
+ }
347
+ return None
348
+ }