cj-tauri 1.1.2 → 2.0.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 (101) hide show
  1. package/README.md +64 -18
  2. package/cjpm.toml +52 -52
  3. package/cli/cjpm.toml +10 -1
  4. package/cli/src/doctor.cj +4 -2
  5. package/cli/src/main.cj +11 -3
  6. package/cli/src/manifest.cj +558 -0
  7. package/cli/src/project.cj +38 -22
  8. package/cli/src/scaffold.cj +369 -361
  9. package/cli/src/sync.cj +526 -0
  10. package/cli/src/util.cj +168 -159
  11. package/cli/templates/app/README.md +48 -80
  12. package/cli/templates/app/{capabilities → assets/capabilities}/default.json +7 -7
  13. package/cli/templates/app/{ui → assets/ui}/index.html +118 -118
  14. package/cli/templates/app/cj-tauri.toml +22 -0
  15. package/cli/templates/app/cjpm.toml +34 -17
  16. package/cli/templates/app/src/main.cj +90 -88
  17. package/cli/templates/app-react/README.md +56 -67
  18. package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +7 -7
  19. package/cli/templates/app-react/{ui → assets/ui}/package.json +19 -19
  20. package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +6 -6
  21. package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +24 -24
  22. package/cli/templates/app-react/cj-tauri.toml +23 -0
  23. package/cli/templates/app-react/cjpm.toml +34 -17
  24. package/cli/templates/app-react/src/main.cj +91 -102
  25. package/cli/templates/app-vue/README.md +56 -60
  26. package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +7 -7
  27. package/cli/templates/app-vue/{ui → assets/ui}/package.json +18 -18
  28. package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +4 -4
  29. package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +24 -24
  30. package/cli/templates/app-vue/cj-tauri.toml +23 -0
  31. package/cli/templates/app-vue/cjpm.toml +34 -17
  32. package/cli/templates/app-vue/src/main.cj +91 -102
  33. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +13 -13
  34. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +8 -8
  35. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +6 -6
  36. package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +79 -79
  37. package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +46 -46
  38. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +26 -26
  39. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +43 -43
  40. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +6 -6
  41. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +9 -9
  42. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +17 -17
  43. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +94 -94
  44. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/index.cj +167 -167
  45. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +139 -139
  46. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +49 -49
  47. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +8 -8
  48. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +16 -16
  49. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +6 -6
  50. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +2 -2
  51. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +6 -6
  52. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +92 -92
  53. package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +5 -5
  54. package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +6 -6
  55. package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +10 -10
  56. package/package.json +4 -2
  57. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  58. package/src/app.cj +49 -20
  59. package/src/app_spec.cj +76 -0
  60. package/src/arkweb_script.cj +142 -154
  61. package/src/host.cj +20 -17
  62. package/src/host_arkweb.cj +778 -740
  63. package/src/host_webkit.cj +22 -0
  64. package/src/host_webview2.cj +22 -0
  65. package/src/launch.cj +62 -0
  66. package/src/platform/platform_app_context.cj +77 -0
  67. package/src/platform/platform_assets.cj +33 -0
  68. package/src/platform/platform_discover.cj +176 -0
  69. package/src/platform/platform_env.cj +29 -0
  70. package/src/platform/platform_feature.cj +31 -0
  71. package/src/platform/platform_page_source.cj +87 -0
  72. package/src/platform/platform_target_info.cj +32 -0
  73. package/src/platform_host_provider.cj +87 -0
  74. package/src/plugin.cj +36 -2
  75. package/src/plugins/clipboard/plugin_clipboard.cj +8 -0
  76. package/src/plugins/menu/plugin_menu.cj +158 -150
  77. package/src/plugins/notification/plugin_notification.cj +8 -0
  78. package/src/plugins/serial/plugin_serial.cj +454 -442
  79. package/src/plugins/serial/serial_bridge.cj +194 -186
  80. package/src/plugins/serial/serial_ohos_stub.cj +61 -0
  81. package/src/plugins/shell/plugin_shell.cj +258 -230
  82. package/src/plugins/sqlite/plugin_sqlite.cj +710 -697
  83. package/src/plugins/sqlite/sqlite_ffi.cj +211 -206
  84. package/src/plugins/sqlite/sqlite_ohos_stub.cj +91 -0
  85. package/src/tests/app_spec_test.cj +127 -0
  86. package/src/tests/fake_host.cj +23 -8
  87. package/src/tests/platform_contract_test.cj +251 -0
  88. package/src/tests/plugin_test.cj +585 -542
  89. package/src/version.cj +1 -1
  90. package/src/host_factory.cj +0 -48
  91. /package/cli/templates/app-react/{ui → assets/ui}/index.html +0 -0
  92. /package/cli/templates/app-react/{ui → assets/ui}/src/App.css +0 -0
  93. /package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +0 -0
  94. /package/cli/templates/app-vue/{ui → assets/ui}/index.html +0 -0
  95. /package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +0 -0
  96. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
  97. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
  98. /package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +0 -0
  99. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
  100. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
  101. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
@@ -6,6 +6,10 @@ package cjTauri
6
6
  @When[os == "Linux" && env != "ohos"]
7
7
  import std.collection.*
8
8
 
9
+ // 页面来源契约(load(source) 收敛,RFC §4.3):同款门禁,免得别的目标把这条当未使用清掉
10
+ @When[os == "Linux" && env != "ohos"]
11
+ import cjTauri.platform.*
12
+
9
13
  /**
10
14
  * C 桥 FFI 声明(native/libcjtbridge.so)
11
15
  * 所有 GTK/WebKit 调用在 C 桥的原生 pthread 内执行(见 native/bridge_linux.c 头注释)。
@@ -198,6 +202,8 @@ public class WebKitHost <: WebViewHost {
198
202
  // 本窗口的 label(字段名不叫 label:接口有同名方法)。单窗口阶段是常量,
199
203
  // 与 TauriApp 注册的 WindowRecord.label 一致;多窗口时由构造参数给定。
200
204
  private let windowLabel: String
205
+ // 资产解析器:装配层在 load 前注入(None = 按 cwd 相对路径读盘,与批次 1 行为等价)
206
+ private var assetResolver: Option<AssetResolver> = None
201
207
 
202
208
  public init() {
203
209
  this.windowLabel = DEFAULT_WINDOW_LABEL
@@ -482,6 +488,22 @@ public class WebKitHost <: WebViewHost {
482
488
  }
483
489
  }
484
490
 
491
+ /**
492
+ * 加载页面(唯一入口,RFC §4.3):Inline/Url/DevServer 直接映射;
493
+ * Asset 经注入的 resolver 读盘,未注入时按 cwd 相对路径读(批次 1 的桌面行为)
494
+ */
495
+ public func load(source: PageSource): Unit {
496
+ match (resolvePage(source, this.assetResolver)) {
497
+ case StartHtml(html) => this.start(html)
498
+ case StartUrl(url) => this.startUrl(url)
499
+ }
500
+ }
501
+
502
+ /** 注入资产解析器(装配层在 load 前调用) */
503
+ public func setAssetResolver(resolver: Option<AssetResolver>): Unit {
504
+ this.assetResolver = resolver
505
+ }
506
+
485
507
  public func start(html: String): Unit {
486
508
  this.prepareHost()
487
509
  unsafe {
@@ -4,6 +4,10 @@ package cjTauri
4
4
  @When[os == "Windows"]
5
5
  import std.collection.*
6
6
 
7
+ // 页面来源契约(load(source) 收敛,RFC §4.3):同款门禁,免得别的目标把这条当未使用清掉
8
+ @When[os == "Windows"]
9
+ import cjTauri.platform.*
10
+
7
11
  /**
8
12
  * C 桥 FFI 声明(native/libcjtbridge.dll)
9
13
  * Win32 窗口 + WebView2(Edge Chromium)在 C 桥的宿主线程内运行(见 native/bridge_win.c 头注释)。
@@ -200,6 +204,8 @@ public class WebView2Host <: WebViewHost {
200
204
  // 本窗口的 label(字段名不叫 label:接口有同名方法)。单窗口阶段是常量,
201
205
  // 与 TauriApp 注册的 WindowRecord.label 一致;多窗口时由构造参数给定。
202
206
  private let windowLabel: String
207
+ // 资产解析器:装配层在 load 前注入(None = 按 cwd 相对路径读盘,与批次 1 行为等价)
208
+ private var assetResolver: Option<AssetResolver> = None
203
209
 
204
210
  public init() {
205
211
  this.windowLabel = DEFAULT_WINDOW_LABEL
@@ -482,6 +488,22 @@ public class WebView2Host <: WebViewHost {
482
488
  }
483
489
  }
484
490
 
491
+ /**
492
+ * 加载页面(唯一入口,RFC §4.3):Inline/Url/DevServer 直接映射;
493
+ * Asset 经注入的 resolver 读盘,未注入时按 cwd 相对路径读(批次 1 的桌面行为)
494
+ */
495
+ public func load(source: PageSource): Unit {
496
+ match (resolvePage(source, this.assetResolver)) {
497
+ case StartHtml(html) => this.start(html)
498
+ case StartUrl(url) => this.startUrl(url)
499
+ }
500
+ }
501
+
502
+ /** 注入资产解析器(装配层在 load 前调用) */
503
+ public func setAssetResolver(resolver: Option<AssetResolver>): Unit {
504
+ this.assetResolver = resolver
505
+ }
506
+
485
507
  public func start(html: String): Unit {
486
508
  this.prepareHost()
487
509
  unsafe {
package/src/launch.cj ADDED
@@ -0,0 +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
+ }
@@ -0,0 +1,77 @@
1
+ package cjTauri.platform
2
+
3
+ import std.collection.ArrayList
4
+
5
+ /**
6
+ * 应用可写目录(RFC-002 §5.2)
7
+ *
8
+ * 默认跟应用 id 走平台规范目录(XDG / AppData / filesDir),不落可执行文件旁边;
9
+ * 便携版(数据跟 exe)是显式选项,由骨架构建期注入,见 RFC §5.2。
10
+ * Windows 下统一用 "/" 分隔符(Windows API 均接受),减少按端拼路径的分支。
11
+ */
12
+ public class AppPaths {
13
+ public let dataDir: String // 收藏库、状态等持久数据
14
+ public let cacheDir: String // 可清理缓存
15
+ public let tempDir: String // 本次运行临时文件
16
+
17
+ public init(dataDir: String, cacheDir: String, tempDir: String) {
18
+ this.dataDir = dataDir
19
+ this.cacheDir = cacheDir
20
+ this.tempDir = tempDir
21
+ }
22
+ }
23
+
24
+ /**
25
+ * 运行时上下文(RFC-002 §4.5)——框架在 launch() 里构建,业务只读
26
+ *
27
+ * supports() 是业务唯一的平台判断入口;target 字段仅供日志与埋点,
28
+ * code review 里出现「按 target.os 分支」应被拒绝(RFC 硬约束 1)。
29
+ */
30
+ public class AppContext {
31
+ public let paths: AppPaths
32
+ public let target: TargetInfo
33
+ /** 开发期:资产优先读盘、允许 DevServer */
34
+ public let dev: Bool
35
+ private let features: ArrayList<PlatformFeature>
36
+
37
+ public init(paths: AppPaths, target: TargetInfo, dev: Bool, features: ArrayList<PlatformFeature>) {
38
+ this.paths = paths
39
+ this.target = target
40
+ this.dev = dev
41
+ this.features = features
42
+ }
43
+
44
+ /** 特性探测:批次 0 用线性扫(特性位数 ≤ 十几,不值得上哈希表) */
45
+ public func supports(f: PlatformFeature): Bool {
46
+ for (item in features) {
47
+ if (PlatformFeature.same(item, f)) {
48
+ return true
49
+ }
50
+ }
51
+ return false
52
+ }
53
+
54
+ /** 供日志输出:本端支持哪些特性位(降级行为要可观测,见 RFC §8) */
55
+ public func describeFeatures(): String {
56
+ let sb = StringBuilder()
57
+ var first = true
58
+ for (item in features) {
59
+ if (!first) {
60
+ sb.append(", ")
61
+ }
62
+ first = false
63
+ sb.append(featureName(item))
64
+ }
65
+ return sb.toString()
66
+ }
67
+
68
+ private static func featureName(f: PlatformFeature): String {
69
+ match (f) {
70
+ case Tray => "Tray"
71
+ case MultiWindow => "MultiWindow"
72
+ case FilePicker => "FilePicker"
73
+ case ClipboardImage => "ClipboardImage"
74
+ case SoftKeyboardInset => "SoftKeyboardInset"
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,33 @@
1
+ package cjTauri.platform
2
+
3
+ import std.fs.*
4
+
5
+ /**
6
+ * 目录型资产解析器(RFC §6.2):桌面开发期 / 发布读盘路径的默认实现
7
+ *
8
+ * root 为资产根(清单 [assets] root),逻辑名按 <root>/<name> 相对寻址。
9
+ * 沙箱端实现(鸿蒙 rawfile / 内嵌常量)在各自适配层,批次 3 随清单派生接入。
10
+ */
11
+ public class DirectoryAssetResolver <: AssetResolver {
12
+ private let root: String
13
+
14
+ public init(root: String) {
15
+ this.root = root
16
+ }
17
+
18
+ public func readText(name: String): Option<String> {
19
+ try {
20
+ return Some(String.fromUtf8(File.readFrom(root + "/" + name)))
21
+ } catch (e: Exception) {
22
+ return None
23
+ }
24
+ }
25
+
26
+ /** 以「能否读到」为准(批次 2 只服务文本资产;图标等二进制走各端资源目录,不进常量表) */
27
+ public func exists(name: String): Bool {
28
+ match (readText(name)) {
29
+ case Some(_) => return true
30
+ case None => return false
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,176 @@
1
+ package cjTauri.platform
2
+
3
+ import std.env.*
4
+ import std.collection.ArrayList
5
+
6
+ /**
7
+ * 平台发现(RFC-002 §4.2 / §5.3)——全框架唯一的「端在哪儿」知识入口
8
+ *
9
+ * 按约定(RFC §5.3),os 名匹配的 @When 只允许出现在本文件与 targets 目录下的生成胶水;
10
+ * CI 用 `grep -r '@When\[os' src/ | grep -v src/platform/` 守这条线。
11
+ * @When 分支一律成对出现(本仓库惯例,参照 host_webview2.cj),保证任何目标下每个
12
+ * 符号恰好有一个定义;跨平台文件里只用短名。
13
+ *
14
+ * 批次 0 边界:本文件只提供**可编译的默认实现**(桌面 Linux / Windows 实测,
15
+ * 鸿蒙走 mobile(filesDir)),特性位清单是占位值,接端时按实测修正。
16
+ */
17
+ public class Platform {
18
+ /**
19
+ * 桌面端默认发现:paths 走平台规范目录(RFC §5.2),dev 看 CJ_TAURI_DEV_URL
20
+ *
21
+ * arch 默认 x86_64;ARM64 Linux 桌面请显式传 arch(它的 os 与 x86_64 Linux 相同,
22
+ * 这是仓库已知的「同 os 多宿主」来源,见 RFC §5.1)。
23
+ * appId 由 launch() 从清单传入(清单解析在批次 1 落地,见 RFC §3/§13.1 风险 1)。
24
+ */
25
+ public static func discover(appId: String, arch: String): AppContext {
26
+ return desktop(appId, arch)
27
+ }
28
+
29
+ public static func desktop(appId: String, arch: String): AppContext {
30
+ let data = "${dataRoot()}/${appId}"
31
+ let target = TargetInfo(desktopOs(), arch, desktopHostKind(), "desktop")
32
+ let fs = ArrayList<PlatformFeature>()
33
+ fs.add(PlatformFeature.MultiWindow)
34
+ fs.add(PlatformFeature.FilePicker)
35
+ fs.add(PlatformFeature.ClipboardImage)
36
+ fs.add(PlatformFeature.Tray)
37
+ return AppContext(
38
+ AppPaths(data, "${cacheRoot()}/${appId}", "${cacheRoot()}/${appId}/tmp"),
39
+ target, devMode(), fs)
40
+ }
41
+
42
+ /**
43
+ * 移动端发现:沙箱目录由系统能力入口传入(鸿蒙 Ability 的 filesDir / Android 的 filesDir)
44
+ *
45
+ * 沙箱端没有 dev server,dev 恒为 false;hostKind 是占位(鸿蒙 ArkWeb,
46
+ * Android 接入时由其 Provider 决定),特性位清单接端实测后修正。
47
+ */
48
+ public static func mobile(filesDir: String, arch: String): AppContext {
49
+ let target = TargetInfo("Linux", arch, "arkweb", "mobile")
50
+ let fs = ArrayList<PlatformFeature>()
51
+ fs.add(PlatformFeature.MultiWindow)
52
+ fs.add(PlatformFeature.ClipboardImage)
53
+ fs.add(PlatformFeature.SoftKeyboardInset)
54
+ return AppContext(
55
+ AppPaths(filesDir, "${filesDir}/cache", "${filesDir}/cache/tmp"),
56
+ target, false, fs)
57
+ }
58
+
59
+ // ---- 平台名与目录根:@When 成对门禁,只落在这几个函数(RFC §5.3)----
60
+
61
+ /** dev server 探测:子包不引用父包,直接读环境变量(与 TauriApp.devUrl() 同语义) */
62
+ private static func devMode(): Bool {
63
+ if (let Some(v) <- getVariable("CJ_TAURI_DEV_URL")) {
64
+ if (v.size > 0) {
65
+ return true
66
+ }
67
+ }
68
+ return false
69
+ }
70
+
71
+ /**
72
+ * 当前目标的 TargetInfo(批次 1:装配层 resolveHost 用它选宿主)
73
+ *
74
+ * ohos 报 HarmonyOS/arkweb/mobile(运行时 os 虽取 Linux,语义归鸿蒙);
75
+ * arch 从 CJ_TAURI_ARCH 环境变量取——编译期拿不到可靠的 arch,
76
+ * 由 hvigor / CLI 构建时注入(未注入为 unknown,宿主选择请勿依赖 arch,
77
+ * 用 hostKind/display 区分——它们正是为「同 os 多宿主」设计的)。
78
+ */
79
+ public static func guessTarget(): TargetInfo {
80
+ let arch = getVariable("CJ_TAURI_ARCH") ?? "unknown"
81
+ return TargetInfo(currentOs(), arch, currentHostKind(), currentDisplay())
82
+ }
83
+
84
+ @When[env == "ohos"]
85
+ private static func currentOs(): String {
86
+ return "HarmonyOS"
87
+ }
88
+
89
+ @When[env != "ohos"]
90
+ private static func currentOs(): String {
91
+ return desktopOs()
92
+ }
93
+
94
+ @When[env == "ohos"]
95
+ private static func currentHostKind(): String {
96
+ return "arkweb"
97
+ }
98
+
99
+ @When[env != "ohos"]
100
+ private static func currentHostKind(): String {
101
+ return desktopHostKind()
102
+ }
103
+
104
+ @When[env == "ohos"]
105
+ private static func currentDisplay(): String {
106
+ return "mobile"
107
+ }
108
+
109
+ @When[env != "ohos"]
110
+ private static func currentDisplay(): String {
111
+ return "desktop"
112
+ }
113
+
114
+ @When[os == "Windows"]
115
+ private static func desktopOs(): String {
116
+ return "Windows"
117
+ }
118
+
119
+ @When[os != "Windows"]
120
+ private static func desktopOs(): String {
121
+ return "Linux"
122
+ }
123
+
124
+ @When[os == "Windows"]
125
+ private static func desktopHostKind(): String {
126
+ return "webview2"
127
+ }
128
+
129
+ @When[os != "Windows"]
130
+ private static func desktopHostKind(): String {
131
+ return "webkitgtk"
132
+ }
133
+
134
+ @When[os == "Windows"]
135
+ private static func dataRoot(): String {
136
+ return getVariable("APPDATA") ?? "."
137
+ }
138
+
139
+ @When[os == "Windows"]
140
+ private static func cacheRoot(): String {
141
+ return getVariable("LOCALAPPDATA") ?? "."
142
+ }
143
+
144
+ @When[os == "Linux" && env != "ohos"]
145
+ private static func dataRoot(): String {
146
+ return getVariable("XDG_DATA_HOME") ?? "${getVariable("HOME") ?? "."}/.local/share"
147
+ }
148
+
149
+ @When[os == "Linux" && env != "ohos"]
150
+ private static func cacheRoot(): String {
151
+ return getVariable("XDG_CACHE_HOME") ?? "${getVariable("HOME") ?? "."}/.cache"
152
+ }
153
+
154
+ // ohos 目标上 desktop() 不会被调用,但符号必须存在才能编译;
155
+ // 返回占位值,真机路径由 mobile(filesDir) 提供。
156
+ @When[env == "ohos"]
157
+ private static func dataRoot(): String {
158
+ return "."
159
+ }
160
+
161
+ @When[env == "ohos"]
162
+ private static func cacheRoot(): String {
163
+ return "."
164
+ }
165
+
166
+ // 其余目标(macOS 等)暂按 HOME 兜底:接入对应端时在这里补实现并真机验证。
167
+ @When[os != "Windows" && os != "Linux"]
168
+ private static func dataRoot(): String {
169
+ return "${getVariable("HOME") ?? "."}/.local/share"
170
+ }
171
+
172
+ @When[os != "Windows" && os != "Linux"]
173
+ private static func cacheRoot(): String {
174
+ return "${getVariable("HOME") ?? "."}/.cache"
175
+ }
176
+ }
@@ -0,0 +1,29 @@
1
+ package cjTauri.platform
2
+
3
+ import std.collection.ArrayList
4
+
5
+ /**
6
+ * L0 适配层契约(RFC-002 §5.1)——每端各实现一份,框架只依赖这些接口
7
+ *
8
+ * 这些接口**不是给业务用的**:业务拿到的只有 AppContext。
9
+ * 批次 1 接入装配层时,Platform.discover() 会按端挑选实现。
10
+ */
11
+
12
+ /** 资产读取:按逻辑名取内容(读盘 / 内嵌常量 / rawfile 由实现决定) */
13
+ public interface AssetResolver {
14
+ func readText(name: String): Option<String>
15
+ func exists(name: String): Bool
16
+ }
17
+
18
+ /** 目录与特性发现:沙箱端必须实现,桌面端由 Platform 给默认 */
19
+ public interface PlatformEnv {
20
+ func paths(appId: String): AppPaths
21
+ func assets(root: String): AssetResolver
22
+ /** RFC 写 Set<PlatformFeature>;批次 0 用 ArrayList 近似,接口定型时统一替换 */
23
+ func features(): ArrayList<PlatformFeature>
24
+ }
25
+
26
+ /** 页面材化:把 Asset 逻辑名变成宿主可加载的形态(L0 内部细节) */
27
+ public interface PageMaterializer {
28
+ func resolve(asset: String, page: String): PageSource
29
+ }
@@ -0,0 +1,31 @@
1
+ package cjTauri.platform
2
+
3
+ /**
4
+ * 平台特性位(RFC-002 §4.5)——业务判断"这个端有没有某设施"的唯一手段
5
+ *
6
+ * 命名约定(评审定稿):**看到 PlatformFeature 只谈可用性,看到 Capability 只谈权限**。
7
+ * - 平台特性 = 本枚举:这个端的系统设施有没有(托盘、文件选择器……);
8
+ * - 安全能力 = CapabilitySet(capability.cj):哪条命令允许被前端调用。
9
+ * 两者中文都容易被叫成"能力",类型名必须分开,否则会退化回 os 分支。
10
+ *
11
+ * 治理规则(RFC §13.2 问题 4):新增一个特性位,必须同时写清三端语义与缺省降级行为,
12
+ * 否则业务会把它当成按 OS 名分支的马甲。
13
+ */
14
+ public enum PlatformFeature {
15
+ | Tray // 托盘
16
+ | MultiWindow // 多窗口(桌面)/ 多任务(移动端)
17
+ | FilePicker // 系统文件选择器
18
+ | ClipboardImage // 剪贴板图片读写
19
+ | SoftKeyboardInset // 软键盘遮挡可感知(移动端)
20
+
21
+ /** 枚举无内建 ==(1.2.0 实测),比较走模式匹配 */
22
+ public static func same(a: PlatformFeature, b: PlatformFeature): Bool {
23
+ match (a) {
24
+ case Tray => match (b) { case Tray => true case _ => false }
25
+ case MultiWindow => match (b) { case MultiWindow => true case _ => false }
26
+ case FilePicker => match (b) { case FilePicker => true case _ => false }
27
+ case ClipboardImage => match (b) { case ClipboardImage => true case _ => false }
28
+ case SoftKeyboardInset => match (b) { case SoftKeyboardInset => true case _ => false }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,87 @@
1
+ package cjTauri.platform
2
+
3
+ import std.fs.*
4
+
5
+ /**
6
+ * 页面来源(RFC-002 §4.3)——取代 run(html) / runUrl(url) 双入口的唯一表达
7
+ *
8
+ * 业务与清单只写 Asset("ui/index.html") 这一种;devMode 下由框架改写为
9
+ * DevServer;Asset 在各端怎么落地(读盘 / rawfile 材化 / assets base URL)
10
+ * 是 L0 适配层(PageMaterializer)的事,业务与骨架都不接触沙箱路径。
11
+ */
12
+ public enum PageSource {
13
+ /** 代码里直接给的 HTML 字符串(探针、错误页用) */
14
+ | Inline(String)
15
+ /** 资产逻辑名,如 "ui/index.html"(sync 生成期校验存在,见 RFC §6.1) */
16
+ | Asset(String)
17
+ /** http(s):// 或 file:// 直载 */
18
+ | Url(String)
19
+ /** 开发期前端 dev server(HMR),由 CJ_TAURI_DEV_URL 注入 */
20
+ | DevServer(String)
21
+ }
22
+
23
+ /** 解析结果:宿主最终执行的动作(纯数据,可测) */
24
+ public enum PageLoad {
25
+ | StartHtml(String) // host.start(html)
26
+ | StartUrl(String) // host.startUrl(url)
27
+ }
28
+
29
+ /**
30
+ * PageSource → PageLoad 的唯一决策点(批次 1,RFC §4.3)
31
+ *
32
+ * - Inline / Url / DevServer:直接映射
33
+ * - Asset:优先走注入的 AssetResolver;未注入时按 cwd 相对路径读盘——与现有模板
34
+ * textOrFile 的桌面行为等价,鸿蒙 rawfile / 内嵌常量的实现在批次 2 随清单接入
35
+ * - 找不到资产:抛可照做的错误,不静默白屏(与宿主装配「不静默回落」同约定)
36
+ */
37
+
38
+ /** 资产解析异常:message 即面向用户的措辞。子包不引用根包的 CommandException
39
+ * (那会构成父↔子互引),独立定义;传播路径与原先一致(run() 路径上无人
40
+ * 按具体类型捕获,message 原样抛到顶层) */
41
+ public class AssetResolutionException <: Exception {
42
+ public init(msg: String) {
43
+ super(msg)
44
+ }
45
+ }
46
+
47
+ public func resolvePage(source: PageSource, resolver: Option<AssetResolver>): PageLoad {
48
+ match (source) {
49
+ case Inline(html) => return PageLoad.StartHtml(html)
50
+ case Url(url) => return PageLoad.StartUrl(url)
51
+ case DevServer(url) => return PageLoad.StartUrl(url)
52
+ case Asset(name) =>
53
+ match (resolver) {
54
+ case Some(r) =>
55
+ match (r.readText(name)) {
56
+ case Some(html) => return PageLoad.StartHtml(html)
57
+ case None =>
58
+ throw AssetResolutionException("资产未找到:${name}(请检查注入的 AssetResolver 与清单 [assets] 配置)")
59
+ }
60
+ case None =>
61
+ var text = ""
62
+ try {
63
+ text = String.fromUtf8(File.readFrom(name))
64
+ } catch (e: Exception) {
65
+ throw AssetResolutionException("资产未找到:${name}(未注入 AssetResolver,按相对路径读盘失败:${e};鸿蒙 rawfile / 内嵌场景请注入对应解析器)")
66
+ }
67
+ return PageLoad.StartHtml(text)
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * devMode 改写(RFC §4.3):CJ_TAURI_DEV_URL 存在时,Asset 页面自动改走 dev server
74
+ *
75
+ * 只改 Asset:显式 Inline(探针)/ Url / DevServer 是业务明说的意图,不改写。
76
+ * 纯函数,可测。
77
+ */
78
+ public func rewriteForDev(source: PageSource, devUrl: Option<String>): PageSource {
79
+ match (devUrl) {
80
+ case Some(url) =>
81
+ match (source) {
82
+ case Asset(_) => return PageSource.DevServer(url)
83
+ case _ => return source
84
+ }
85
+ case None => return source
86
+ }
87
+ }
@@ -0,0 +1,32 @@
1
+ package cjTauri.platform
2
+
3
+ /**
4
+ * 平台自述(RFC-002 §5.1)
5
+ *
6
+ * 供宿主选择、资产策略、日志与埋点共用;**业务不得据此分支**——
7
+ * 业务判断"这个端有没有某设施"请走 AppContext.supports(PlatformFeature)。
8
+ *
9
+ * 为什么 os 是字符串而不是枚举:宿主实现由 L0 注册(HostProvider),框架核心
10
+ * 不 import 任何平台包;批次 1 接入 createHost 时会给出各端的取值常量。
11
+ */
12
+ public class TargetInfo {
13
+ /** "Windows" / "Linux" / "HarmonyOS" / "macOS" / "Android"(鸿蒙与 Linux 桌面都会报 Linux,靠 hostKind 区分) */
14
+ public let os: String
15
+ /** "x86_64" / "aarch64" */
16
+ public let arch: String
17
+ /** "webkitgtk" / "webview2" / "arkweb" / "wkwebview" / "android-webview" */
18
+ public let hostKind: String
19
+ /** "desktop" / "mobile":只决定窗口语义(桌面多窗、移动端多任务),不决定能力位 */
20
+ public let display: String
21
+
22
+ public init(os: String, arch: String, hostKind: String, display: String) {
23
+ this.os = os
24
+ this.arch = arch
25
+ this.hostKind = hostKind
26
+ this.display = display
27
+ }
28
+
29
+ public func describe(): String {
30
+ "${os}/${arch} host=${hostKind} display=${display}"
31
+ }
32
+ }