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
package/src/launch.cj CHANGED
@@ -1,62 +1,62 @@
1
- package cjTauri
2
-
3
- import cjTauri.platform.*
4
- import stdx.encoding.json.JsonValue
5
-
6
- /**
7
- * 三段式 API 的第二段(批次 3,RFC-002 §4.2)
8
- *
9
- * 业务入口全文就这几行:
10
- * ```
11
- * import cjTauri.*
12
- * main(): Int64 {
13
- * launch(build())
14
- * return 0
15
- * }
16
- * ```
17
- *
18
- * `Platform.discover()` 是唯一「端在哪儿」的知识入口(§5.1);
19
- * 桌面阻塞主循环,移动端的非 UI 线程 spawn 由宿主负责(§4.2 鸿蒙示例)。
20
- *
21
- * 与 RFC 示意写法的偏差(照实记录):两个重载代替默认参数
22
- * (当前工具链不用默认参数),`launchWith` 供测试注入假上下文、
23
- * 鸿蒙 Ability 触发时给移动端上下文。
24
- */
25
- public func launch(spec: AppSpec): Unit {
26
- let target = Platform.guessTarget()
27
- launchWith(spec, Platform.discover(spec.appId, target.arch))
28
- }
29
-
30
- /** 注入上下文的变体:测试给假上下文;鸿蒙侧给 Platform.mobile(filesDir) */
31
- public func launchWith(spec: AppSpec, context: AppContext): Unit {
32
- eprintln("[cj-tauri] launch:${spec.name}(os=${context.target.os} display=${context.target.display})")
33
- let app = assemble(spec)
34
- app.run(spec.pageSource)
35
- }
36
-
37
- /**
38
- * spec → TauriApp(纯装配、不 run):launch 与单测共用
39
- *
40
- * 装配顺序即框架契约顺序:窗口配置 → 插件 → 资产解析器 → 能力目录 → 业务命令(wire)。
41
- */
42
- public func assemble(spec: AppSpec): TauriApp {
43
- let app = TauriApp().window(spec.windowConfig)
44
- for (p in spec.plugins) {
45
- app.plugin(p)
46
- }
47
- if (spec.assetResolver.isSome()) {
48
- app.assets(spec.assetResolver.getOrThrow())
49
- }
50
- if (spec.capabilitiesDir.size > 0) {
51
- app.loadCapabilities(spec.capabilitiesDir)
52
- }
53
- // 能力清单 JSON 走与显式挂载同一入口:鸿蒙胶水把 rawfile 原文传进来(批次 3-PR3)
54
- for (j in spec.capabilityJsons) {
55
- app.addCapabilityJson(JsonValue.fromStr(j))
56
- }
57
- if (spec.wireFn.isSome()) {
58
- let f = spec.wireFn.getOrThrow()
59
- f(app)
60
- }
61
- return app
62
- }
1
+ package cjTauri
2
+
3
+ import cjTauri.platform.*
4
+ import stdx.encoding.json.JsonValue
5
+
6
+ /**
7
+ * 三段式 API 的第二段(批次 3,RFC-002 §4.2)
8
+ *
9
+ * 业务入口全文就这几行:
10
+ * ```
11
+ * import cjTauri.*
12
+ * main(): Int64 {
13
+ * launch(build())
14
+ * return 0
15
+ * }
16
+ * ```
17
+ *
18
+ * `Platform.discover()` 是唯一「端在哪儿」的知识入口(§5.1);
19
+ * 桌面阻塞主循环,移动端的非 UI 线程 spawn 由宿主负责(§4.2 鸿蒙示例)。
20
+ *
21
+ * 与 RFC 示意写法的偏差(照实记录):两个重载代替默认参数
22
+ * (当前工具链不用默认参数),`launchWith` 供测试注入假上下文、
23
+ * 鸿蒙 Ability 触发时给移动端上下文。
24
+ */
25
+ public func launch(spec: AppSpec): Unit {
26
+ let target = Platform.guessTarget()
27
+ launchWith(spec, Platform.discover(spec.appId, target.arch))
28
+ }
29
+
30
+ /** 注入上下文的变体:测试给假上下文;鸿蒙侧给 Platform.mobile(filesDir) */
31
+ public func launchWith(spec: AppSpec, context: AppContext): Unit {
32
+ eprintln("[cj-tauri] launch:${spec.name}(os=${context.target.os} display=${context.target.display})")
33
+ let app = assemble(spec)
34
+ app.run(spec.pageSource)
35
+ }
36
+
37
+ /**
38
+ * spec → TauriApp(纯装配、不 run):launch 与单测共用
39
+ *
40
+ * 装配顺序即框架契约顺序:窗口配置 → 插件 → 资产解析器 → 能力目录 → 业务命令(wire)。
41
+ */
42
+ public func assemble(spec: AppSpec): TauriApp {
43
+ let app = TauriApp().window(spec.windowConfig)
44
+ for (p in spec.plugins) {
45
+ app.plugin(p)
46
+ }
47
+ if (spec.assetResolver.isSome()) {
48
+ app.assets(spec.assetResolver.getOrThrow())
49
+ }
50
+ if (spec.capabilitiesDir.size > 0) {
51
+ app.loadCapabilities(spec.capabilitiesDir)
52
+ }
53
+ // 能力清单 JSON 走与显式挂载同一入口:鸿蒙胶水把 rawfile 原文传进来(批次 3-PR3)
54
+ for (j in spec.capabilityJsons) {
55
+ app.addCapabilityJson(JsonValue.fromStr(j))
56
+ }
57
+ if (spec.wireFn.isSome()) {
58
+ let f = spec.wireFn.getOrThrow()
59
+ f(app)
60
+ }
61
+ return app
62
+ }
package/src/logging.cj CHANGED
@@ -1,127 +1,127 @@
1
- package cjTauri
2
-
3
- import std.env.*
4
- import std.convert.*
5
-
6
- /**
7
- * 日志落盘的策略层(RFC-003 第 5 期):文件放哪、预算多少、环境变量怎么兜底。
8
- *
9
- * 落盘本身在 `src/log_bridge.cj`(进程级、无平台分支);这里只管策略,纯函数 + `@When` 目录推导。
10
- *
11
- * 为什么要环境变量兜底:单文件产物是 Windows 的 GUI 子系统、双击时没有控制台,而**已经分发的
12
- * exe 改不了代码**——让报障的人带上 `CJ_TAURI_LOG` 再启动一次,就能拿到凭证;
13
- * 不设这些变量时行为与之前**完全一致**(不落盘、stderr 照旧)。
14
- */
15
-
16
- /** 默认预算:256 KB 一份、留 2 份档案(最坏约 1 MB,常驻托盘类应用也吃得消) */
17
- public let CJ_LOG_DEFAULT_MAX_KB: Int64 = 256
18
- public let CJ_LOG_DEFAULT_KEEP: Int64 = 2
19
-
20
- /** 应用名缺省值(应用没给名字时用它当文件名) */
21
- public let CJ_LOG_DEFAULT_APP: String = "app"
22
-
23
- /**
24
- * 单行上限(字节):页面可控的字符串,超了只记一条说明
25
- *
26
- * 8 KiB 够放一段异常栈;不设上限的话,一行天文数字能同时把内存与日志文件撑爆,
27
- * 而这一行本身还未必有诊断价值。
28
- */
29
- public let CJ_LOG_MAX_LINE: Int64 = 8192
30
-
31
- /** 读环境变量(空串按未设置算)。公开只为子包用例可见(子包看不到父包非 public 符号) */
32
- public func logEnvOr(name: String, dflt: String): String {
33
- if (let Some(v) <- getVariable(name)) {
34
- if (v.size > 0) {
35
- return v
36
- }
37
- }
38
- return dflt
39
- }
40
-
41
- /**
42
- * 解析预算类环境变量(CJ_TAURI_LOG_MAX_KB / CJ_TAURI_LOG_KEEP)
43
- *
44
- * 非法值按未设置算,并留一行 stderr——「设了但没生效」比「没设」难查得多。
45
- * 公开只为子包用例可见(子包看不到父包非 public 符号)
46
- */
47
- public func logBudgetFromEnv(name: String, dflt: Int64): Int64 {
48
- if (let Some(v) <- getVariable(name)) {
49
- if (v.size > 0) {
50
- try {
51
- let n = Int64.parse(v)
52
- if (n > 0) {
53
- return n
54
- }
55
- eprintln("[cj-tauri] ${name}=\"${v}\" 不是正整数,按默认值 ${dflt} 处理")
56
- } catch (e: Exception) {
57
- eprintln("[cj-tauri] ${name}=\"${v}\" 解析失败,按默认值 ${dflt} 处理")
58
- }
59
- }
60
- }
61
- return dflt
62
- }
63
-
64
- /**
65
- * 平台惯例的日志目录(不含文件名)
66
- *
67
- * Linux 认 `XDG_STATE_HOME`(缺省 `~/.local/state`):日志属 state,不属 config——
68
- * 放 `~/.config` 会把「可丢弃的运行痕迹」混进配置备份里。
69
- */
70
- @When[os == "Linux"]
71
- public func defaultLogDir(): String {
72
- let base = logEnvOr("XDG_STATE_HOME", "")
73
- if (base.size > 0) {
74
- return "${base}/cj-tauri"
75
- }
76
- return "${logEnvOr("HOME", "/tmp")}/.local/state/cj-tauri"
77
- }
78
-
79
- /**
80
- * 平台惯例的日志目录(不含文件名)
81
- *
82
- * Windows 认 `%LOCALAPPDATA%`(`C:\Users\<用户>\AppData\Local`):单机可丢的数据就该落这里,
83
- * 漫游配置里不放日志。变量缺失时退到 USERPROFILE,别拼出个以 `\cj-tauri` 开头的绝对路径。
84
- */
85
- @When[os == "Windows"]
86
- public func defaultLogDir(): String {
87
- let base = logEnvOr("LOCALAPPDATA", "")
88
- if (base.size > 0) {
89
- return "${base}\\cj-tauri\\logs"
90
- }
91
- return "${logEnvOr("USERPROFILE", "C:")}\\AppData\\Local\\cj-tauri\\logs"
92
- }
93
-
94
- /** 目录 + 文件名:分隔符按平台给(C 侧整条路径直接用,不做拼接) */
95
- @When[os == "Linux"]
96
- func logPathJoin(dir: String, name: String): String {
97
- return "${dir}/${name}"
98
- }
99
-
100
- /** 目录 + 文件名:分隔符按平台给(C 侧整条路径直接用,不做拼接) */
101
- @When[os == "Windows"]
102
- func logPathJoin(dir: String, name: String): String {
103
- return "${dir}\\${name}"
104
- }
105
-
106
- /** 应用名 → 平台惯例目录下的日志全路径(`CJ_TAURI_LOG` 的覆写由调用方先判) */
107
- public func defaultLogPath(appName: String): String {
108
- let name = if (appName.size > 0) {
109
- appName
110
- } else {
111
- CJ_LOG_DEFAULT_APP
112
- }
113
- return logPathJoin(defaultLogDir(), "${name}.log")
114
- }
115
-
116
- /**
117
- * 「环境变量给了就用它,没给才落平台惯例目录」——`setLogToDefaultFile` 的选择器
118
- *
119
- * 抽成纯函数只为**可测**:单测里改环境变量会污染同进程的其它用例,所以把「读到什么」
120
- * 当参数传进来,判据钉在这个函数上(`envPath` 空串=未设置,与 `logEnvOr` 同口径)。
121
- */
122
- public func logPathOrDefault(envPath: String, appName: String): String {
123
- if (envPath.size > 0) {
124
- return envPath
125
- }
126
- return defaultLogPath(appName)
127
- }
1
+ package cjTauri
2
+
3
+ import std.env.*
4
+ import std.convert.*
5
+
6
+ /**
7
+ * 日志落盘的策略层(RFC-003 第 5 期):文件放哪、预算多少、环境变量怎么兜底。
8
+ *
9
+ * 落盘本身在 `src/log_bridge.cj`(进程级、无平台分支);这里只管策略,纯函数 + `@When` 目录推导。
10
+ *
11
+ * 为什么要环境变量兜底:单文件产物是 Windows 的 GUI 子系统、双击时没有控制台,而**已经分发的
12
+ * exe 改不了代码**——让报障的人带上 `CJ_TAURI_LOG` 再启动一次,就能拿到凭证;
13
+ * 不设这些变量时行为与之前**完全一致**(不落盘、stderr 照旧)。
14
+ */
15
+
16
+ /** 默认预算:256 KB 一份、留 2 份档案(最坏约 1 MB,常驻托盘类应用也吃得消) */
17
+ public let CJ_LOG_DEFAULT_MAX_KB: Int64 = 256
18
+ public let CJ_LOG_DEFAULT_KEEP: Int64 = 2
19
+
20
+ /** 应用名缺省值(应用没给名字时用它当文件名) */
21
+ public let CJ_LOG_DEFAULT_APP: String = "app"
22
+
23
+ /**
24
+ * 单行上限(字节):页面可控的字符串,超了只记一条说明
25
+ *
26
+ * 8 KiB 够放一段异常栈;不设上限的话,一行天文数字能同时把内存与日志文件撑爆,
27
+ * 而这一行本身还未必有诊断价值。
28
+ */
29
+ public let CJ_LOG_MAX_LINE: Int64 = 8192
30
+
31
+ /** 读环境变量(空串按未设置算)。公开只为子包用例可见(子包看不到父包非 public 符号) */
32
+ public func logEnvOr(name: String, dflt: String): String {
33
+ if (let Some(v) <- getVariable(name)) {
34
+ if (v.size > 0) {
35
+ return v
36
+ }
37
+ }
38
+ return dflt
39
+ }
40
+
41
+ /**
42
+ * 解析预算类环境变量(CJ_TAURI_LOG_MAX_KB / CJ_TAURI_LOG_KEEP)
43
+ *
44
+ * 非法值按未设置算,并留一行 stderr——「设了但没生效」比「没设」难查得多。
45
+ * 公开只为子包用例可见(子包看不到父包非 public 符号)
46
+ */
47
+ public func logBudgetFromEnv(name: String, dflt: Int64): Int64 {
48
+ if (let Some(v) <- getVariable(name)) {
49
+ if (v.size > 0) {
50
+ try {
51
+ let n = Int64.parse(v)
52
+ if (n > 0) {
53
+ return n
54
+ }
55
+ eprintln("[cj-tauri] ${name}=\"${v}\" 不是正整数,按默认值 ${dflt} 处理")
56
+ } catch (e: Exception) {
57
+ eprintln("[cj-tauri] ${name}=\"${v}\" 解析失败,按默认值 ${dflt} 处理")
58
+ }
59
+ }
60
+ }
61
+ return dflt
62
+ }
63
+
64
+ /**
65
+ * 平台惯例的日志目录(不含文件名)
66
+ *
67
+ * Linux 认 `XDG_STATE_HOME`(缺省 `~/.local/state`):日志属 state,不属 config——
68
+ * 放 `~/.config` 会把「可丢弃的运行痕迹」混进配置备份里。
69
+ */
70
+ @When[os == "Linux"]
71
+ public func defaultLogDir(): String {
72
+ let base = logEnvOr("XDG_STATE_HOME", "")
73
+ if (base.size > 0) {
74
+ return "${base}/cj-tauri"
75
+ }
76
+ return "${logEnvOr("HOME", "/tmp")}/.local/state/cj-tauri"
77
+ }
78
+
79
+ /**
80
+ * 平台惯例的日志目录(不含文件名)
81
+ *
82
+ * Windows 认 `%LOCALAPPDATA%`(`C:\Users\<用户>\AppData\Local`):单机可丢的数据就该落这里,
83
+ * 漫游配置里不放日志。变量缺失时退到 USERPROFILE,别拼出个以 `\cj-tauri` 开头的绝对路径。
84
+ */
85
+ @When[os == "Windows"]
86
+ public func defaultLogDir(): String {
87
+ let base = logEnvOr("LOCALAPPDATA", "")
88
+ if (base.size > 0) {
89
+ return "${base}\\cj-tauri\\logs"
90
+ }
91
+ return "${logEnvOr("USERPROFILE", "C:")}\\AppData\\Local\\cj-tauri\\logs"
92
+ }
93
+
94
+ /** 目录 + 文件名:分隔符按平台给(C 侧整条路径直接用,不做拼接) */
95
+ @When[os == "Linux"]
96
+ func logPathJoin(dir: String, name: String): String {
97
+ return "${dir}/${name}"
98
+ }
99
+
100
+ /** 目录 + 文件名:分隔符按平台给(C 侧整条路径直接用,不做拼接) */
101
+ @When[os == "Windows"]
102
+ func logPathJoin(dir: String, name: String): String {
103
+ return "${dir}\\${name}"
104
+ }
105
+
106
+ /** 应用名 → 平台惯例目录下的日志全路径(`CJ_TAURI_LOG` 的覆写由调用方先判) */
107
+ public func defaultLogPath(appName: String): String {
108
+ let name = if (appName.size > 0) {
109
+ appName
110
+ } else {
111
+ CJ_LOG_DEFAULT_APP
112
+ }
113
+ return logPathJoin(defaultLogDir(), "${name}.log")
114
+ }
115
+
116
+ /**
117
+ * 「环境变量给了就用它,没给才落平台惯例目录」——`setLogToDefaultFile` 的选择器
118
+ *
119
+ * 抽成纯函数只为**可测**:单测里改环境变量会污染同进程的其它用例,所以把「读到什么」
120
+ * 当参数传进来,判据钉在这个函数上(`envPath` 空串=未设置,与 `logEnvOr` 同口径)。
121
+ */
122
+ public func logPathOrDefault(envPath: String, appName: String): String {
123
+ if (envPath.size > 0) {
124
+ return envPath
125
+ }
126
+ return defaultLogPath(appName)
127
+ }