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
@@ -0,0 +1,260 @@
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import cjTauri.plugins.store.*
5
+ import std.collection.*
6
+ import std.fs.*
7
+ import std.sync.*
8
+ import stdx.encoding.json.*
9
+ import std.unittest.*
10
+ import std.unittest.testmacro.*
11
+
12
+ /*
13
+ * store 插件单元测试:StoreBackend 的读写删 / keys / 原子写残留 / 损坏文件回落 / 并发写
14
+ * + 命令面参数校验 + 命名权限集的闸门。
15
+ *
16
+ * 纯仓颉插件(零 C 桥),所以这里能测到「真实落盘」——比 serial 的单测多覆盖一层。
17
+ * 临时文件全部放 /tmp(按固定名复用),不在仓库里留测试产物。
18
+ */
19
+
20
+ const STORE_T_BASIC: String = "/tmp/store-unit-test-basic.json"
21
+ const STORE_T_CMD: String = "/tmp/store-unit-test-cmd.json"
22
+ const STORE_T_BAD: String = "/tmp/store-unit-test-bad.json"
23
+ const STORE_T_CONC: String = "/tmp/store-unit-test-conc.json"
24
+ // keys/clear 用例必须独享文件:basic 文件里留着前面用例写的键,共享会让 size 断言失真
25
+ const STORE_T_KEYS: String = "/tmp/store-unit-test-keys.json"
26
+
27
+ /** 取一条 store 命令处理器(插件带自定义路径,测试互不串文件) */
28
+ func storeCmd(short: String): CommandHandler {
29
+ return StorePlugin(STORE_T_CMD).commands().get(short).getOrThrow()
30
+ }
31
+
32
+ /** 直接调命令,把 CommandException 的原文取出来(没抛就返回空串)*/
33
+ func storeErrOf(short: String, args: JsonObject): String {
34
+ try {
35
+ storeCmd(short).handle("store:${short}", args, IpcContext("main", IpcHub()))
36
+ return ""
37
+ } catch (e: CommandException) {
38
+ return e.message
39
+ }
40
+ }
41
+
42
+ func storeArgs1(key: String): JsonObject {
43
+ var o = JsonObject()
44
+ o.put("key", JsonString(key))
45
+ return o
46
+ }
47
+
48
+ @Test
49
+ func testStorePluginSurface(): Unit {
50
+ let p = StorePlugin()
51
+ @Expect(p.name(), "store")
52
+ let names = pluginCommandNames(p)
53
+ @Expect(names.size, 5)
54
+ var found = 0
55
+ for (n in names) {
56
+ if (n == "store:get" || n == "store:set" || n == "store:delete" || n == "store:keys" || n == "store:clear") {
57
+ found++
58
+ }
59
+ }
60
+ @Expect(found, 5)
61
+ }
62
+
63
+ @Test
64
+ func testStorePluginShimTargetsItsOwnNamespace(): Unit {
65
+ let js = StorePlugin().jsShim()
66
+ @Expect(js.contains("t.store"), true)
67
+ @Expect(js.contains("'store:get'"), true)
68
+ @Expect(js.contains("'store:set'"), true)
69
+ @Expect(js.contains("'store:delete'"), true)
70
+ @Expect(js.contains("'store:clear'"), true)
71
+ }
72
+
73
+ @Test
74
+ func testStorePermissionSetsGateWrites(): Unit {
75
+ // 命名集要经 pluginPermissionSets 看(框架注册的是补全名,permissions() 只给短名)
76
+ let sets = pluginPermissionSets(StorePlugin())
77
+ @Expect(sets.size, 2)
78
+ let ro = sets.get("store:readonly").getOrThrow()
79
+ @Expect(ro.size, 2)
80
+ // readonly 不含写:只想读的应用引用它,写能力不会被顺带打开
81
+ var hasWrite = false
82
+ for (n in ro) {
83
+ if (n == "store:set" || n == "store:delete" || n == "store:clear") {
84
+ hasWrite = true
85
+ }
86
+ }
87
+ @Expect(hasWrite, false)
88
+ @Expect(sets.get("store:default").getOrThrow().size, 5)
89
+ }
90
+
91
+ @Test
92
+ func testStoreSetGetRoundtripPersistsToDisk(): Unit {
93
+ let path = STORE_T_BASIC
94
+ let s1 = StoreBackend(path)
95
+ let r1 = s1.set("theme", JsonString("dark"))
96
+ @Expect(r1, true)
97
+ let r2 = s1.set("count", JsonInt(3))
98
+ @Expect(r2, true)
99
+ // 新开一个 backend(=模拟应用重启):数据必须从盘上回来
100
+ let s2 = StoreBackend(path)
101
+ // get 的返回就是 JsonValue(缺失时是 JSON null 或给定的 default),直接用
102
+ @Expect(s2.get("theme", false, JsonBool(false)).asString().getValue(), "dark")
103
+ @Expect(s2.get("count", false, JsonBool(false)).asInt().getValue(), 3)
104
+ }
105
+
106
+ @Test
107
+ func testStoreGetMissingDefaultAndNull(): Unit {
108
+ let s = StoreBackend(STORE_T_BASIC)
109
+ // 没给 default:回 JSON null(不是抛错)。JsonKind 这个 enum 连 == 都没定义
110
+ // (invalid binary operator,实测 2026-10-09),比较一律用 match(music/serial 同款模式)
111
+ let missKind = s.get("no-such-key", false, JsonBool(false)).kind()
112
+ var missOk = false
113
+ match (missKind) {
114
+ case JsonKind.JsNull => missOk = true
115
+ case _ => ()
116
+ }
117
+ @Expect(missOk, true)
118
+ // 给了 default:原样返回
119
+ @Expect(s.get("no-such-key", true, JsonString("fallback")).asString().getValue(), "fallback")
120
+ }
121
+
122
+ @Test
123
+ func testStoreDeleteReportsExisted(): Unit {
124
+ let s = StoreBackend(STORE_T_BASIC)
125
+ let r = s.set("gone-soon", JsonBool(true))
126
+ @Expect(r, true)
127
+ let (existed1, saved1) = s.delete("gone-soon")
128
+ @Expect(existed1, true)
129
+ @Expect(saved1, true)
130
+ // 再删一次:本来就没有,existed=false(页面据此区分「删了」与「没有过」)
131
+ let (existed2, saved2) = s.delete("gone-soon")
132
+ @Expect(existed2, false)
133
+ @Expect(saved2, true)
134
+ }
135
+
136
+ @Test
137
+ func testStoreKeysSortedAndClearCounts(): Unit {
138
+ let s = StoreBackend(STORE_T_KEYS)
139
+ let r1 = s.set("zebra", JsonInt(1))
140
+ @Expect(r1, true)
141
+ let r2 = s.set("alpha", JsonInt(2))
142
+ @Expect(r2, true)
143
+ let r3 = s.set("mid", JsonInt(3))
144
+ @Expect(r3, true)
145
+ let ks = s.keys()
146
+ @Expect(ks.size, 3)
147
+ @Expect(ks[0], "alpha")
148
+ @Expect(ks[1], "mid")
149
+ @Expect(ks[2], "zebra")
150
+ let (cleared, saved) = s.clear()
151
+ @Expect(cleared, 3)
152
+ @Expect(saved, true)
153
+ @Expect(s.keys().size, 0)
154
+ // 清空后重启:仍是空的
155
+ @Expect(StoreBackend(STORE_T_KEYS).keys().size, 0)
156
+ }
157
+
158
+ @Test
159
+ func testStoreAtomicWriteLeavesNoTmpBehind(): Unit {
160
+ let path = STORE_T_BASIC
161
+ let s = StoreBackend(path)
162
+ let r = s.set("atomic", JsonBool(true))
163
+ @Expect(r, true)
164
+ // 原子写的中间产物必须已经被 rename 走,不能残留
165
+ @Expect(exists(path + ".tmp"), false)
166
+ // 文件本体是合法 JSON 对象(页面/人都能直接读)
167
+ let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
168
+ let roundKind = parsed.kind()
169
+ var roundOk = false
170
+ match (roundKind) {
171
+ case JsonKind.JsObject => roundOk = true
172
+ case _ => ()
173
+ }
174
+ @Expect(roundOk, true)
175
+ }
176
+
177
+ @Test
178
+ func testStoreCorruptedFileRecovers(): Unit {
179
+ let path = STORE_T_BAD
180
+ // 造一个坏文件(半截 JSON,模拟进程中途被杀的旧版落盘)
181
+ File.writeTo(path, "{\"theme\": \"da".toArray())
182
+ let s = StoreBackend(path)
183
+ // 读坏不是致命错误:从空开始,键为空
184
+ @Expect(s.keys().size, 0)
185
+ // 之后照常可用,且下一次写会覆盖掉坏文件
186
+ let r = s.set("ok", JsonBool(true))
187
+ @Expect(r, true)
188
+ let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
189
+ let badKind = parsed.kind()
190
+ var badOk = false
191
+ match (badKind) {
192
+ case JsonKind.JsObject => badOk = true
193
+ case _ => ()
194
+ }
195
+ @Expect(badOk, true)
196
+ }
197
+
198
+ @Test
199
+ func testStoreConcurrentWritersKeepFileValid(): Unit {
200
+ let path = STORE_T_CONC
201
+ let s = StoreBackend(path)
202
+ // 8 个 worker 各写 25 次,全部打同一个键:内存不丢更新、盘上始终是合法 JSON。
203
+ // spawn 写法照框架自己的命令分发(ipc_hub.cj 的 `spawn { => ... }`);
204
+ // SyncCounter 是倒数计数器:init(8) 起始为 8,每个 worker 结束 dec(),waitUntilZero 等齐。
205
+ let counter = SyncCounter(8)
206
+ var i = 0
207
+ while (i < 8) {
208
+ let tag = "w${i}"
209
+ let n = i
210
+ let _ = spawn { =>
211
+ var j = 0
212
+ while (j < 25) {
213
+ let r = s.set(tag, JsonInt(n * 100 + j))
214
+ if (!r) {
215
+ eprintln("[store-test] ${tag} 第 ${j} 轮写盘失败")
216
+ }
217
+ j++
218
+ }
219
+ counter.dec()
220
+ }
221
+ i++
222
+ }
223
+ counter.waitUntilZero()
224
+ // 8 个键都在;各自终值是 0..24 里的某一个(谁最后落盘就是谁的)
225
+ let ks = s.keys()
226
+ @Expect(ks.size, 8)
227
+ let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
228
+ let concKind = parsed.kind()
229
+ var concOk = false
230
+ match (concKind) {
231
+ case JsonKind.JsObject => concOk = true
232
+ case _ => ()
233
+ }
234
+ @Expect(concOk, true)
235
+ }
236
+
237
+ @Test
238
+ func testStoreCommandParamValidation(): Unit {
239
+ // 缺 key / 空 key / key 类型不对
240
+ let noKey = JsonObject()
241
+ @Expect(storeErrOf("get", noKey), "store: 缺少参数 \"key\"")
242
+ let emptyKey = storeArgs1("")
243
+ @Expect(storeErrOf("get", emptyKey), "store: key 不能为空字符串")
244
+ let badKey = JsonObject()
245
+ badKey.put("key", JsonInt(1))
246
+ @Expect(storeErrOf("get", badKey), "store: 参数 \"key\" 必须是字符串")
247
+ // set 缺 value(想存 null 要显式传)
248
+ let argsV = storeArgs1("k")
249
+ @Expect(storeErrOf("set", argsV), "store: 缺少参数 \"value\"(想存 null 就显式传 value: null)")
250
+ // delete 不存在的键也成功(existed=false),不是错误
251
+ let out = storeCmd("delete").handle("store:delete", storeArgs1("never-was"),
252
+ IpcContext("main", IpcHub()))
253
+ let existedKind = out.asObject().get("existed").getOrThrow().kind()
254
+ var existedOk = false
255
+ match (existedKind) {
256
+ case JsonKind.JsBool => existedOk = true
257
+ case _ => ()
258
+ }
259
+ @Expect(existedOk, true)
260
+ }