cj-tauri 0.8.0 → 1.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 (124) hide show
  1. package/README.md +3 -3
  2. package/cjpm.toml +52 -25
  3. package/cli/cjpm.toml +1 -1
  4. package/cli/src/doctor.cj +304 -0
  5. package/cli/src/main.cj +33 -7
  6. package/cli/src/project.cj +59 -1
  7. package/cli/src/resolve.cj +348 -304
  8. package/cli/src/scaffold.cj +139 -22
  9. package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
  10. package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
  11. package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
  12. package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
  13. package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
  14. package/cli/templates/ohos-pc/README.md +79 -0
  15. package/cli/templates/ohos-pc/build-hap.sh +190 -0
  16. package/cli/templates/ohos-pc/build-profile.json5 +46 -0
  17. package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
  18. package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
  19. package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
  20. package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
  21. package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
  22. package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
  23. package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
  24. package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
  25. package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
  26. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
  27. package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
  28. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
  29. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
  30. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
  31. package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
  32. package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
  33. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
  34. package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
  35. package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
  36. package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
  37. package/cli/templates/ohos-pc/oh-package.json5 +10 -0
  38. package/native/build_linux.sh +30 -2
  39. package/native/build_win.bat +62 -61
  40. package/native/core/bridge_core.c +1626 -0
  41. package/native/{bridge_core.h → core/bridge_core.h} +246 -6
  42. package/native/platforms/harmonyos/README.md +106 -0
  43. package/native/platforms/linux/bridge_linux.c +2485 -0
  44. package/native/platforms/windows/bridge_win.c +3248 -0
  45. package/native/tests/test_bridge_core.c +528 -1
  46. package/package.json +12 -3
  47. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  48. package/src/api_system.cj +10 -2
  49. package/src/app.cj +248 -85
  50. package/src/arkweb_proxy.cj +56 -0
  51. package/src/arkweb_script.cj +154 -0
  52. package/src/capability.cj +140 -0
  53. package/src/clipboard_bridge.cj +55 -0
  54. package/src/dragdrop.cj +26 -0
  55. package/src/embedded_assets.cj +98 -0
  56. package/src/hexutil.cj +53 -0
  57. package/src/host.cj +197 -26
  58. package/src/host_arkweb.cj +740 -0
  59. package/src/host_factory.cj +48 -0
  60. package/src/host_webkit.cj +202 -26
  61. package/src/host_webview2.cj +176 -3
  62. package/src/hotkey.cj +66 -0
  63. package/src/ipc_hub.cj +49 -0
  64. package/src/ipc_message.cj +2 -1
  65. package/src/jsutil.cj +33 -0
  66. package/src/log_bridge.cj +107 -0
  67. package/src/logging.cj +127 -0
  68. package/src/menu.cj +197 -196
  69. package/src/plugin.cj +40 -0
  70. package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
  71. package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
  72. package/src/plugins/fs/plugin_fs.cj +194 -0
  73. package/src/plugins/http/plugin_http.cj +459 -0
  74. package/src/plugins/log/plugin_log.cj +105 -0
  75. package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
  76. package/src/plugins/mqtt/mqtt_codec.cj +661 -0
  77. package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
  78. package/src/plugins/notification/plugin_notification.cj +146 -0
  79. package/src/plugins/plugins.cj +5 -0
  80. package/src/plugins/redis/plugin_redis.cj +2409 -0
  81. package/src/plugins/redis/redis_codec.cj +556 -0
  82. package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
  83. package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
  84. package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
  85. package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
  86. package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
  87. package/src/plugins/store/plugin_store.cj +287 -0
  88. package/src/plugins/ws/plugin_ws.cj +470 -0
  89. package/src/process_hidden.cj +174 -0
  90. package/src/scope.cj +391 -0
  91. package/src/shell_event.cj +69 -68
  92. package/src/tests/capability_test.cj +53 -0
  93. package/src/tests/fake_host.cj +85 -20
  94. package/src/tests/ipc_hub_test.cj +13 -0
  95. package/src/tests/jsutil_test.cj +62 -0
  96. package/src/tests/logging_test.cj +62 -0
  97. package/src/tests/mqtt_codec_test.cj +405 -0
  98. package/src/tests/plugin_clipboard_test.cj +222 -0
  99. package/src/tests/plugin_dialog_test.cj +98 -4
  100. package/src/tests/plugin_http_test.cj +124 -0
  101. package/src/tests/plugin_menu_test.cj +1 -0
  102. package/src/tests/plugin_notification_test.cj +205 -0
  103. package/src/tests/plugin_serial_test.cj +269 -267
  104. package/src/tests/plugin_shell_test.cj +189 -188
  105. package/src/tests/plugin_sqlite_test.cj +230 -0
  106. package/src/tests/plugin_store_test.cj +260 -0
  107. package/src/tests/plugin_test.cj +542 -524
  108. package/src/tests/plugin_ws_test.cj +40 -0
  109. package/src/tests/process_hidden_test.cj +129 -0
  110. package/src/tests/redis_codec_test.cj +396 -0
  111. package/src/tests/scope_test.cj +350 -0
  112. package/src/tests/shell_event_test.cj +0 -1
  113. package/src/tests/system_host_test.cj +81 -79
  114. package/src/tests/urlutil_test.cj +53 -0
  115. package/src/tests/version_test.cj +75 -65
  116. package/src/tray.cj +90 -0
  117. package/src/urlutil.cj +159 -0
  118. package/src/version.cj +23 -2
  119. package/src/window.cj +51 -42
  120. package/native/bridge_core.c +0 -797
  121. package/native/bridge_linux.c +0 -1268
  122. package/native/bridge_win.c +0 -1633
  123. package/src/plugin_fs.cj +0 -118
  124. /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
@@ -25,13 +25,26 @@ class FakeHost <: WebViewHost {
25
25
  // 对话框入参按「调一次记一次」存成字符串:桩不真弹窗,用例要断言的是「谁被调了、带了什么」
26
26
  public var fileDialogArgs: String = ""
27
27
  public var messageDialogArgs: String = ""
28
+ // 选目录与文件框分开记:用例要断言「走的是目录那条路」,不能与 fileDialogArgs 混为一谈
29
+ public var folderDialogTitle: String = ""
28
30
  // 菜单:线路文本 / 单项状态按原样记下(桩不建真菜单,用例断言的是投下来的内容)
29
31
  public var menuWire: String = ""
30
32
  public var menuItemStateArgs: String = ""
33
+ // 标题栏副标题:桩只记最后一次文本(用例断言的是「投下来的是什么」)
34
+ public var titlebarSubtitleText: String = ""
35
+ public var dragDropArgs: String = ""
36
+ public var hotkeyWire: String = ""
37
+ public var trayWire: String = ""
38
+ public var windowVisibleValue: Bool = true
39
+ public var minimizeToTrayValue: Bool = false
31
40
  // shell 事件回调(菜单点击等):用例可以自己调它,模拟「桥把事件送进来了」
32
41
  public var shellHandler: (String) -> Unit = { _ => }
33
42
  // 销毁回调:用例可以自己调它,模拟「桥通知本窗销毁了」
34
43
  public var destroyHandler: () -> Unit = { => }
44
+ // 剪贴板 / 通知:写与通知按「调一次记一次」,读值可注入(默认空,用例按需设)
45
+ public var clipboardWritten: String = ""
46
+ public var clipboardReadValue: Option<String> = None<String>
47
+ public var notifiedArgs: String = ""
35
48
  // 能力位:桩给「有菜单」,用例要测「平台没这个本事」时自行覆盖它
36
49
  public var caps: Int32 = CJ_CAP_MENU
37
50
 
@@ -67,26 +80,51 @@ class FakeHost <: WebViewHost {
67
80
  this.devToolsEnabled = enabled
68
81
  }
69
82
 
70
- public func openDevTools(): Unit {}
71
-
72
- public func setMenu(wire: String): Unit {
73
- this.menuWire = wire
74
- }
75
-
76
- public func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit {
77
- this.menuItemStateArgs = "${id}|${enabled}|${checked}"
78
- }
79
-
80
- public func hostCapabilities(): Int32 {
81
- return this.caps
82
- }
83
-
84
- public func setShellHandler(handler: (String) -> Unit): Unit {
85
- this.shellHandler = handler
86
- }
87
-
88
- public func setDestroyHandler(handler: () -> Unit): Unit {
89
- this.destroyHandler = handler
83
+ public func openDevTools(): Unit {}
84
+
85
+ public func setMenu(wire: String): Unit {
86
+ this.menuWire = wire
87
+ }
88
+
89
+ public func setMenuItemState(id: String, enabled: Bool, checked: Bool): Unit {
90
+ this.menuItemStateArgs = "${id}|${enabled}|${checked}"
91
+ }
92
+
93
+ public func setTitlebarSubtitle(text: String): Unit {
94
+ this.titlebarSubtitleText = text
95
+ }
96
+
97
+ public func setDragDropPolicy(enabled: Bool, files: Bool): Unit {
98
+ this.dragDropArgs = "${enabled}|${files}"
99
+ }
100
+
101
+ public func setGlobalHotkeys(wire: String): Unit {
102
+ this.hotkeyWire = wire
103
+ }
104
+
105
+ public func setTray(wire: String): Unit {
106
+ this.trayWire = wire
107
+ }
108
+
109
+ // 窗口可见性 / 最小化策略:桩只记最后一次设置(用例断言的是「谁被调了、带了什么」)
110
+ public func setWindowVisible(visible: Bool): Unit {
111
+ this.windowVisibleValue = visible
112
+ }
113
+
114
+ public func setMinimizeToTray(enabled: Bool): Unit {
115
+ this.minimizeToTrayValue = enabled
116
+ }
117
+
118
+ public func hostCapabilities(): Int32 {
119
+ return this.caps
120
+ }
121
+
122
+ public func setShellHandler(handler: (String) -> Unit): Unit {
123
+ this.shellHandler = handler
124
+ }
125
+
126
+ public func setDestroyHandler(handler: () -> Unit): Unit {
127
+ this.destroyHandler = handler
90
128
  }
91
129
 
92
130
  public func showFileDialog(save: Bool, title: String, filter: String): String {
@@ -94,11 +132,38 @@ class FakeHost <: WebViewHost {
94
132
  return ""
95
133
  }
96
134
 
135
+ public func showFileDialogPreset(save: Bool, title: String, filter: String,
136
+ defaultName: String): String {
137
+ // 记痕带预置名:多窗取证靠它断言「本窗拿到的是本窗预置的名字」
138
+ this.fileDialogArgs = "${save}|${title}|${filter}|${defaultName}"
139
+ return ""
140
+ }
141
+
142
+ public func showFolderDialog(title: String): String {
143
+ // 桩不真弹窗:记下标题即可(用例断言的是「谁被调了、带了什么」)
144
+ this.folderDialogTitle = title
145
+ return ""
146
+ }
147
+
97
148
  public func showMessageDialog(kind: String, title: String, message: String): Bool {
98
149
  this.messageDialogArgs = "${kind}|${title}|${message}"
99
150
  return false
100
151
  }
101
152
 
153
+ public func clipboardWriteText(text: String): Bool {
154
+ this.clipboardWritten = text
155
+ return true
156
+ }
157
+
158
+ public func clipboardReadText(): Option<String> {
159
+ return this.clipboardReadValue
160
+ }
161
+
162
+ public func notify(title: String, body: String): Bool {
163
+ this.notifiedArgs = "${title}|${body}"
164
+ return true
165
+ }
166
+
102
167
  public func runJs(js: String): Unit {
103
168
  this.jsCalls.add(js)
104
169
  }
@@ -15,6 +15,9 @@ import std.unittest.testmacro.*
15
15
  /** 固定返回 "ok" 的处理器 */
16
16
  class OkHandler <: CommandHandler {
17
17
  public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
18
+ let _ = cmd
19
+ let _ = args
20
+ let _ = ipc
18
21
  return JsonString("ok")
19
22
  }
20
23
  }
@@ -22,6 +25,9 @@ class OkHandler <: CommandHandler {
22
25
  /** 抛 CommandException 的处理器(预期内的失败) */
23
26
  class BoomHandler <: CommandHandler {
24
27
  public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
28
+ let _ = cmd
29
+ let _ = args
30
+ let _ = ipc
25
31
  throw CommandException("boom")
26
32
  }
27
33
  }
@@ -29,6 +35,9 @@ class BoomHandler <: CommandHandler {
29
35
  /** 抛普通异常的处理器(预期外的失败) */
30
36
  class CrashHandler <: CommandHandler {
31
37
  public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
38
+ let _ = cmd
39
+ let _ = args
40
+ let _ = ipc
32
41
  throw Exception("crash")
33
42
  }
34
43
  }
@@ -236,6 +245,9 @@ func testIpcMessageResolveKeepsPayload(): Unit {
236
245
  /** 睡 300ms 再回答:模拟 `shell:exec` 这类慢命令 */
237
246
  class SlowHandler <: CommandHandler {
238
247
  public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
248
+ let _ = cmd
249
+ let _ = args
250
+ let _ = ipc
239
251
  sleep(Duration.millisecond * 300)
240
252
  return JsonString("slow-ok")
241
253
  }
@@ -304,6 +316,7 @@ func testConcurrentInvokesEachResolveExactlyOnce(): Unit {
304
316
 
305
317
  /** 抛异常的监听器:验证「一个听众崩了不牵连别的听众与发送方」 */
306
318
  func boomListener(p: JsonValue): Unit {
319
+ let _ = p
307
320
  throw Exception("listener boom")
308
321
  }
309
322
 
@@ -0,0 +1,62 @@
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.unittest.*
5
+ import std.unittest.testmacro.*
6
+
7
+ /*
8
+ * JS 字符串字面量转义的用例(鸿蒙 ArkWeb 侧的 label 注入靠它)。
9
+ *
10
+ * 为什么值得单独测:这段逻辑的失效形状是**静默的**——生成的脚本当场语法错误,
11
+ * 页面里 `__CJ_TAURI_LABEL__` 停在默认值 `main`,于是「按 label 过滤的事件」全部失灵,
12
+ * 而宿主与页面两边都不报错(AGENTS §4 同族坑)。原先它带 `@When[env == "ohos"]`
13
+ * 门禁、桌面 `cjpm test` 编不到,所以下沉到共享层后必须在这里锁住。
14
+ *
15
+ * 断言取**字符层面**的期望值(`\"` 就是两个字符:反斜杠 + 引号),
16
+ * 不整体比对拼好的脚本——那样改一改前缀就得连带改一堆测试。
17
+ */
18
+
19
+ @Test
20
+ func testJsStringLiteralWrapsPlainText(): Unit {
21
+ @Expect(jsStringLiteral("main"), "\"main\"")
22
+ }
23
+
24
+ @Test
25
+ func testJsStringLiteralEscapesDoubleQuote(): Unit {
26
+ // 不转义的话 label 一变就提前收敛,脚本变成 `= "a";b";` —— 语法错误
27
+ @Expect(jsStringLiteral("a\"b"), "\"a\\\"b\"")
28
+ }
29
+
30
+ @Test
31
+ func testJsStringLiteralEscapesBackslash(): Unit {
32
+ // Windows 风格 label / 路径片段带反斜杠:不转义会把后一个字符一起吃掉
33
+ @Expect(jsStringLiteral("a\\b"), "\"a\\\\b\"")
34
+ }
35
+
36
+ @Test
37
+ func testJsStringLiteralKeepsUtf8Bytes(): Unit {
38
+ // 按字节扫,汉字不能被拆分或改写(三字节序列原样带过)
39
+ @Expect(jsStringLiteral("窗口"), "\"窗口\"")
40
+ }
41
+
42
+ @Test
43
+ func testJsStringLiteralHandlesEmptyString(): Unit {
44
+ @Expect(jsStringLiteral(""), "\"\"")
45
+ }
46
+
47
+ @Test
48
+ func testJsStringLiteralCombinesQuoteAndBackslash(): Unit {
49
+ // 两者相邻时最容易漏转义:期望 `"` `\` `\` `"` `\` `\` ...` 严格交错
50
+ @Expect(jsStringLiteral("\"\\"), "\"\\\"\\\\\"")
51
+ }
52
+
53
+ @Test
54
+ func testJsStringLiteralLeavesOtherControlCharsAlone(): Unit {
55
+ // 口径是「只转义反斜杠与双引号」:制表符 / 换行不属于 label 语义,
56
+ // 这里断言**不**做额外加工,避免将来有人把它改成全量 JSON 编码器而悄悄改变契约
57
+ let tab = "\t"
58
+ let got = jsStringLiteral(tab)
59
+ // 未转义 = 长度只多出两侧引号(2 + 1 字节的制表符)
60
+ @Expect(got.size, 3)
61
+ @Expect(got.contains("\t"), true)
62
+ }
@@ -0,0 +1,62 @@
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.unittest.*
5
+ import std.unittest.testmacro.*
6
+
7
+ /*
8
+ * 日志落盘的策略层用例(RFC-003 第 5 期)。
9
+ *
10
+ * 只测**纯函数**(默认目录推导、环境变量回退、路径拼接):真的开 fd 那一段归桩自检
11
+ * (`native/tests/test_bridge_core.c` 的 `log file` 用例)与实机 E2E——单测里调桥的
12
+ * `foreign func` 会踩「子包调不了 foreign func」那条(AGENTS §4,链接期报未定义符号)。
13
+ *
14
+ * 环境变量类的用例一律用**肯定不存在**的变量名:跑测试的机器上 `CJ_TAURI_LOG` 之类
15
+ * 可能真的设着,拿它当「未设置」来断言就是一条会随机器飘的红线。
16
+ */
17
+
18
+ /** 保证不存在的变量名(前缀统一,便于将来 grep) */
19
+ let ABSENT_LOG_ENV: String = "CJ_TAURI_LOG_TEST_ABSENT"
20
+
21
+ @Test
22
+ func testLogEnvFallsBackWhenVariableMissing(): Unit {
23
+ @Expect(logEnvOr(ABSENT_LOG_ENV, "dflt"), "dflt")
24
+ }
25
+
26
+ @Test
27
+ func testLogBudgetFallsBackWhenVariableMissing(): Unit {
28
+ @Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 7), 7)
29
+ // 0 与负数在调用方语义里是「不轮转」,不是「用 0 当默认」——回退值原样带回
30
+ @Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 0), 0)
31
+ }
32
+
33
+ @Test
34
+ func testDefaultLogPathUsesAppNameAndPlatformDir(): Unit {
35
+ let p = defaultLogPath("music")
36
+ // 分隔符按平台给,所以不整体比对路径;钉的是「文件名 = 应用名」、「落在 cj-tauri 目录下」
37
+ @Expect(p.endsWith("music.log"), true)
38
+ @Expect(p.contains("cj-tauri"), true)
39
+ // 应用没给名字时走兜底:不能拼出没有主名的 ".log"
40
+ @Expect(defaultLogPath("").endsWith("app.log"), true)
41
+ }
42
+
43
+ @Test
44
+ func testDefaultLogDirIsNonEmptyAndConventional(): Unit {
45
+ let d = defaultLogDir()
46
+ @Expect(d.size > 0, true)
47
+ @Expect(d.contains("cj-tauri"), true)
48
+ // 默认预算是个能落地的值:>0 才会轮转,keep>0 才有档案可留
49
+ @Expect(CJ_LOG_DEFAULT_MAX_KB > 0, true)
50
+ @Expect(CJ_LOG_DEFAULT_KEEP > 0, true)
51
+ }
52
+
53
+ @Test
54
+ func testLogPathOrDefaultPrefersEnvOverPlatformDir(): Unit {
55
+ // 报障路径优先:环境变量给了路径就用它,平台惯例目录被忽略(人家不必知道目录在哪)
56
+ @Expect(logPathOrDefault("/tmp/from-env.log", "music"), "/tmp/from-env.log")
57
+ // 空串=未设置,才落平台惯例目录,文件名跟应用名走(与 logEnvOr 同口径)
58
+ let d = logPathOrDefault("", "music")
59
+ @Expect(d.endsWith("music.log"), true)
60
+ @Expect(d.contains("cj-tauri"), true)
61
+ @Expect(d == defaultLogPath("music"), true)
62
+ }