cj-tauri 2.0.0 → 2.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 (157) hide show
  1. package/README.md +206 -139
  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 +643 -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 +638 -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 +3745 -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/src/api_system.cj +67 -67
  77. package/src/app.cj +335 -969
  78. package/src/app_events.cj +166 -0
  79. package/src/app_host_ops.cj +287 -0
  80. package/src/app_run.cj +283 -0
  81. package/src/app_spec.cj +76 -76
  82. package/src/arkweb_menu.cj +216 -0
  83. package/src/arkweb_script.cj +142 -142
  84. package/src/capability.cj +409 -409
  85. package/src/clipboard_bridge.cj +178 -0
  86. package/src/dialog_spec.cj +79 -0
  87. package/src/dragdrop.cj +26 -26
  88. package/src/embedded_assets.cj +98 -98
  89. package/src/host.cj +752 -607
  90. package/src/host_arkweb.cj +924 -778
  91. package/src/host_webkit.cj +663 -570
  92. package/src/host_webview2.cj +663 -570
  93. package/src/hotkey.cj +66 -66
  94. package/src/ipc_hub.cj +401 -337
  95. package/src/ipc_message.cj +286 -286
  96. package/src/launch.cj +62 -62
  97. package/src/logging.cj +127 -127
  98. package/src/menu.cj +197 -197
  99. package/src/platform/platform_app_context.cj +77 -77
  100. package/src/platform/platform_assets.cj +33 -33
  101. package/src/platform/platform_discover.cj +176 -176
  102. package/src/platform/platform_env.cj +29 -29
  103. package/src/platform/platform_feature.cj +31 -31
  104. package/src/platform/platform_page_source.cj +87 -87
  105. package/src/platform/platform_target_info.cj +32 -32
  106. package/src/platform_host_provider.cj +87 -87
  107. package/src/plugin.cj +333 -333
  108. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  109. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  110. package/src/plugins/fs/plugin_fs.cj +105 -1
  111. package/src/plugins/http/http_backend.cj +131 -0
  112. package/src/plugins/http/http_desktop.cj +195 -0
  113. package/src/plugins/http/http_ohos.cj +204 -0
  114. package/src/plugins/http/plugin_http.cj +310 -459
  115. package/src/plugins/log/plugin_log.cj +105 -105
  116. package/src/plugins/menu/plugin_menu.cj +158 -158
  117. package/src/plugins/notification/plugin_notification.cj +154 -154
  118. package/src/plugins/serial/plugin_serial.cj +458 -454
  119. package/src/plugins/serial/serial_bridge.cj +194 -194
  120. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  121. package/src/plugins/serial/serial_ohos.cj +529 -0
  122. package/src/plugins/shell/plugin_shell.cj +258 -258
  123. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  124. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  125. package/src/plugins/ws/plugin_ws.cj +488 -470
  126. package/src/process_hidden.cj +174 -174
  127. package/src/scope.cj +391 -391
  128. package/src/shell_event.cj +69 -69
  129. package/src/tests/app_spec_test.cj +130 -127
  130. package/src/tests/capability_test.cj +243 -243
  131. package/src/tests/dialog_spec_test.cj +73 -0
  132. package/src/tests/fake_host.cj +201 -201
  133. package/src/tests/ipc_hub_test.cj +563 -487
  134. package/src/tests/logging_test.cj +62 -62
  135. package/src/tests/platform_contract_test.cj +264 -251
  136. package/src/tests/plugin_clipboard_test.cj +222 -222
  137. package/src/tests/plugin_dialog_test.cj +237 -237
  138. package/src/tests/plugin_fs_test.cj +207 -0
  139. package/src/tests/plugin_http_test.cj +124 -124
  140. package/src/tests/plugin_notification_test.cj +205 -205
  141. package/src/tests/plugin_serial_test.cj +48 -0
  142. package/src/tests/plugin_test.cj +605 -585
  143. package/src/tests/plugin_ws_test.cj +40 -40
  144. package/src/tests/process_hidden_test.cj +129 -129
  145. package/src/tests/scope_test.cj +350 -350
  146. package/src/tests/system_host_test.cj +99 -81
  147. package/src/tests/urlutil_test.cj +53 -53
  148. package/src/tests/version_test.cj +75 -75
  149. package/src/tests/window_registry_test.cj +123 -123
  150. package/src/tray.cj +90 -90
  151. package/src/urlutil.cj +159 -159
  152. package/src/version.cj +44 -44
  153. package/src/window.cj +51 -51
  154. package/src/window_registry.cj +116 -116
  155. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  156. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  157. 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
+ }