cj-tauri 2.0.0 → 2.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 (158) hide show
  1. package/README.md +113 -99
  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 +632 -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 +526 -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 +3740 -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/prebuilt/linux-x64/cj-tauri +0 -0
  77. package/src/api_system.cj +67 -67
  78. package/src/app.cj +325 -969
  79. package/src/app_events.cj +166 -0
  80. package/src/app_host_ops.cj +287 -0
  81. package/src/app_run.cj +283 -0
  82. package/src/app_spec.cj +76 -76
  83. package/src/arkweb_menu.cj +216 -0
  84. package/src/arkweb_script.cj +142 -142
  85. package/src/capability.cj +409 -409
  86. package/src/clipboard_bridge.cj +178 -0
  87. package/src/dialog_spec.cj +79 -0
  88. package/src/dragdrop.cj +26 -26
  89. package/src/embedded_assets.cj +98 -98
  90. package/src/host.cj +752 -607
  91. package/src/host_arkweb.cj +924 -778
  92. package/src/host_webkit.cj +663 -570
  93. package/src/host_webview2.cj +663 -570
  94. package/src/hotkey.cj +66 -66
  95. package/src/ipc_hub.cj +337 -337
  96. package/src/ipc_message.cj +286 -286
  97. package/src/launch.cj +62 -62
  98. package/src/logging.cj +127 -127
  99. package/src/menu.cj +197 -197
  100. package/src/platform/platform_app_context.cj +77 -77
  101. package/src/platform/platform_assets.cj +33 -33
  102. package/src/platform/platform_discover.cj +176 -176
  103. package/src/platform/platform_env.cj +29 -29
  104. package/src/platform/platform_feature.cj +31 -31
  105. package/src/platform/platform_page_source.cj +87 -87
  106. package/src/platform/platform_target_info.cj +32 -32
  107. package/src/platform_host_provider.cj +87 -87
  108. package/src/plugin.cj +333 -333
  109. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  110. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  111. package/src/plugins/fs/plugin_fs.cj +105 -1
  112. package/src/plugins/http/http_backend.cj +131 -0
  113. package/src/plugins/http/http_desktop.cj +195 -0
  114. package/src/plugins/http/http_ohos.cj +204 -0
  115. package/src/plugins/http/plugin_http.cj +310 -459
  116. package/src/plugins/log/plugin_log.cj +105 -105
  117. package/src/plugins/menu/plugin_menu.cj +158 -158
  118. package/src/plugins/notification/plugin_notification.cj +154 -154
  119. package/src/plugins/serial/plugin_serial.cj +458 -454
  120. package/src/plugins/serial/serial_bridge.cj +194 -194
  121. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  122. package/src/plugins/serial/serial_ohos.cj +529 -0
  123. package/src/plugins/shell/plugin_shell.cj +258 -258
  124. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  125. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  126. package/src/plugins/ws/plugin_ws.cj +488 -470
  127. package/src/process_hidden.cj +174 -174
  128. package/src/scope.cj +391 -391
  129. package/src/shell_event.cj +69 -69
  130. package/src/tests/app_spec_test.cj +126 -127
  131. package/src/tests/capability_test.cj +243 -243
  132. package/src/tests/dialog_spec_test.cj +73 -0
  133. package/src/tests/fake_host.cj +201 -201
  134. package/src/tests/ipc_hub_test.cj +487 -487
  135. package/src/tests/logging_test.cj +62 -62
  136. package/src/tests/platform_contract_test.cj +251 -251
  137. package/src/tests/plugin_clipboard_test.cj +222 -222
  138. package/src/tests/plugin_dialog_test.cj +237 -237
  139. package/src/tests/plugin_fs_test.cj +207 -0
  140. package/src/tests/plugin_http_test.cj +124 -124
  141. package/src/tests/plugin_notification_test.cj +205 -205
  142. package/src/tests/plugin_serial_test.cj +48 -0
  143. package/src/tests/plugin_test.cj +605 -585
  144. package/src/tests/plugin_ws_test.cj +40 -40
  145. package/src/tests/process_hidden_test.cj +129 -129
  146. package/src/tests/scope_test.cj +350 -350
  147. package/src/tests/system_host_test.cj +99 -81
  148. package/src/tests/urlutil_test.cj +53 -53
  149. package/src/tests/version_test.cj +75 -75
  150. package/src/tests/window_registry_test.cj +123 -123
  151. package/src/tray.cj +90 -90
  152. package/src/urlutil.cj +159 -159
  153. package/src/version.cj +44 -44
  154. package/src/window.cj +51 -51
  155. package/src/window_registry.cj +116 -116
  156. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  157. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  158. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
@@ -1,81 +1,99 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import std.collection.*
5
- import stdx.encoding.json.*
6
- import std.unittest.*
7
- import std.unittest.testmacro.*
8
-
9
- /*
10
- * 内置命令 system:host 的用例。
11
- *
12
- * 覆盖的是「能力位 → 具名布尔 JSON」这一段:能力位从调用窗口的宿主来,经 hub 注入的取值通道
13
- * (生产环境里由 TauriApp 接到 hostCapabilities())变成 HostCapabilities。用例不建窗口,
14
- * 替身就是那条注入通道——顺带钉住「system:host 走的是通道,不是 hub 自己去摸窗口注册表」。
15
- *
16
- * 断言一律**解析 JSON 再取字段**(不靠子串):序列化格式(空白、键序)不是契约,字段值是。
17
- */
18
-
19
- /** 造一个「通道给 bits」的 hub + 会话上下文(window = main) */
20
- func hostCtxWith(bits: Int32, asked: ArrayList<String>): IpcContext {
21
- let hub = IpcHub()
22
- hub.hostCapabilitiesSink = { label =>
23
- asked.add(label)
24
- bits
25
- }
26
- return IpcContext(DEFAULT_WINDOW_LABEL, hub)
27
- }
28
-
29
- @Test
30
- func testSystemHostReportsNamedBooleansForInjectedBits(): Unit {
31
- let asked = ArrayList<String>()
32
- // 有菜单 + 有托盘、没有拖入:三位各自独立翻名,不能整体按 true/false 一刀切
33
- let ctx = hostCtxWith(CJ_CAP_MENU | CJ_CAP_TRAY, asked)
34
- let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
35
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
36
- @Expect(data.get("tray").getOrThrow().asBool().getValue(), true)
37
- @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
38
- // platform 由仓颉侧条件编译给(windows / linux),不该是空串
39
- @Expect(data.get("platform").getOrThrow().asString().getValue().size > 0, true)
40
- // logFile(RFC-003 第 5 期):进程级字段,用例环境没开日志 → 空串(字段在、值才空)
41
- @Expect(data.get("logFile").getOrThrow().asString().getValue(), "")
42
- // 取的是**发起命令那个窗口**的能力位
43
- @Expect(asked[0], DEFAULT_WINDOW_LABEL)
44
- }
45
-
46
- @Test
47
- func testSystemHostReportsAllFalseWhenPlatformHasNothing(): Unit {
48
- let asked = ArrayList<String>()
49
- let ctx = hostCtxWith(0, asked)
50
- let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
51
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
52
- @Expect(data.get("tray").getOrThrow().asBool().getValue(), false)
53
- @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
54
- }
55
-
56
- @Test
57
- func testSystemHostResolvesThroughHubWhenDeclared(): Unit {
58
- // 三处联动的第三处:清单里声明了 system:host,前端才调得到(走完整 IPC 链路 + 异步分发)
59
- let out = ArrayList<String>()
60
- let hub = hubWith(["system:host"], [], out)
61
- hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
62
- hub.register("system:host", SystemCommands())
63
- hub.handleRawMessage("{\"type\":\"invoke\",\"id\":21,\"cmd\":\"system:host\"}")
64
- waitSink(out, 1)
65
- let reply = JsonValue.fromStr(out[0]).asObject()
66
- @Expect(reply.get("ok").getOrThrow().asBool().getValue(), true)
67
- let data = reply.get("data").getOrThrow().asObject()
68
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
69
- }
70
-
71
- @Test
72
- func testSystemHostIsDeniedWithoutCapability(): Unit {
73
- // 三处联动缺了清单那一处:能力清单没声明 system:host → 调用线程上同步拒绝
74
- let out = ArrayList<String>()
75
- let hub = hubWith([], [], out)
76
- hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
77
- hub.register("system:host", SystemCommands())
78
- hub.handleRawMessage("{\"type\":\"invoke\",\"id\":23,\"cmd\":\"system:host\"}")
79
- waitSink(out, 1)
80
- @Expect(out[0].contains("command not allowed"), true)
81
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.collection.*
5
+ import stdx.encoding.json.*
6
+ import std.unittest.*
7
+ import std.unittest.testmacro.*
8
+
9
+ /*
10
+ * 内置命令 system:host 的用例。
11
+ *
12
+ * 覆盖的是「能力位 → 具名布尔 JSON」这一段:能力位从调用窗口的宿主来,经 hub 注入的取值通道
13
+ * (生产环境里由 TauriApp 接到 hostCapabilities())变成 HostCapabilities。用例不建窗口,
14
+ * 替身就是那条注入通道——顺带钉住「system:host 走的是通道,不是 hub 自己去摸窗口注册表」。
15
+ *
16
+ * 断言一律**解析 JSON 再取字段**(不靠子串):序列化格式(空白、键序)不是契约,字段值是。
17
+ */
18
+
19
+ /** 造一个「通道给 bits」的 hub + 会话上下文(window = main) */
20
+ func hostCtxWith(bits: Int32, asked: ArrayList<String>): IpcContext {
21
+ let hub = IpcHub()
22
+ hub.hostCapabilitiesSink = { label =>
23
+ asked.add(label)
24
+ bits
25
+ }
26
+ return IpcContext(DEFAULT_WINDOW_LABEL, hub)
27
+ }
28
+
29
+ @Test
30
+ func testSystemHostReportsNamedBooleansForInjectedBits(): Unit {
31
+ let asked = ArrayList<String>()
32
+ // 有菜单 + 有托盘、没有拖入:三位各自独立翻名,不能整体按 true/false 一刀切
33
+ let ctx = hostCtxWith(CJ_CAP_MENU | CJ_CAP_TRAY, asked)
34
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
35
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
36
+ @Expect(data.get("tray").getOrThrow().asBool().getValue(), true)
37
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
38
+ // notification(CJ_CAP_NOTIFICATION = 8):与 dragDrop(4) / hotkey(16) 是三个独立的位;
39
+ // 这一位曾整条漏在仓颉侧(C 桥置了、可读形态里没有字段),所以单独钉一条
40
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), false)
41
+ // platform 由仓颉侧条件编译给(windows / linux),不该是空串
42
+ @Expect(data.get("platform").getOrThrow().asString().getValue().size > 0, true)
43
+ // logFile(RFC-003 第 5 期):进程级字段,用例环境没开日志 → 空串(字段在、值才空)
44
+ @Expect(data.get("logFile").getOrThrow().asString().getValue(), "")
45
+ // 取的是**发起命令那个窗口**的能力位
46
+ @Expect(asked[0], DEFAULT_WINDOW_LABEL)
47
+ }
48
+
49
+ @Test
50
+ func testSystemHostReportsAllFalseWhenPlatformHasNothing(): Unit {
51
+ let asked = ArrayList<String>()
52
+ let ctx = hostCtxWith(0, asked)
53
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
54
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
55
+ @Expect(data.get("tray").getOrThrow().asBool().getValue(), false)
56
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
57
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), false)
58
+ }
59
+
60
+ @Test
61
+ func testSystemHostMapsNotificationBitIndependently(): Unit {
62
+ // 只置通知位(8):它左右两个邻居 dragDrop(4) / hotkey(16) 必须仍是 false。
63
+ // 这一条正是那位曾经的缺口——C 侧 `CJ_CAP_NOTIFICATION` 置了、仓颉侧没有对应的位与字段,
64
+ // `system:host` 少了 notification 键(不是值为 false,是根本没这个键),页面读到的就是 undefined。
65
+ let asked = ArrayList<String>()
66
+ let ctx = hostCtxWith(CJ_CAP_NOTIFICATION, asked)
67
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
68
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), true)
69
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
70
+ @Expect(data.get("hotkey").getOrThrow().asBool().getValue(), false)
71
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
72
+ }
73
+
74
+ @Test
75
+ func testSystemHostResolvesThroughHubWhenDeclared(): Unit {
76
+ // 三处联动的第三处:清单里声明了 system:host,前端才调得到(走完整 IPC 链路 + 异步分发)
77
+ let out = ArrayList<String>()
78
+ let hub = hubWith(["system:host"], [], out)
79
+ hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
80
+ hub.register("system:host", SystemCommands())
81
+ hub.handleRawMessage("{\"type\":\"invoke\",\"id\":21,\"cmd\":\"system:host\"}")
82
+ waitSink(out, 1)
83
+ let reply = JsonValue.fromStr(out[0]).asObject()
84
+ @Expect(reply.get("ok").getOrThrow().asBool().getValue(), true)
85
+ let data = reply.get("data").getOrThrow().asObject()
86
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
87
+ }
88
+
89
+ @Test
90
+ func testSystemHostIsDeniedWithoutCapability(): Unit {
91
+ // 三处联动缺了清单那一处:能力清单没声明 system:host → 调用线程上同步拒绝
92
+ let out = ArrayList<String>()
93
+ let hub = hubWith([], [], out)
94
+ hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
95
+ hub.register("system:host", SystemCommands())
96
+ hub.handleRawMessage("{\"type\":\"invoke\",\"id\":23,\"cmd\":\"system:host\"}")
97
+ waitSink(out, 1)
98
+ @Expect(out[0].contains("command not allowed"), true)
99
+ }
@@ -1,53 +1,53 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import std.unittest.*
5
- import std.unittest.testmacro.*
6
-
7
- /*
8
- * SNI 的目标怎么定:`urlHostOf` 取主机名,`isIpLiteralHost` 判「是不是 IP 字面量」——
9
- * 域名才带 `serverName`(stdx 默认不发 SNI,多租户 HTTPS 边缘会当场以 handshake failure 拒掉,
10
- * 实测 Cloudflare 后面的 api 域名,见 D-022);IP 字面量不带(RFC 6066 不许,且直连 IP 原本可用)。
11
- */
12
-
13
- @Test
14
- func testIpLiteralHostRecognizesIpv4AndIpv6(): Unit {
15
- @Expect(isIpLiteralHost("127.0.0.1"), true)
16
- @Expect(isIpLiteralHost("192.168.1.10"), true)
17
- @Expect(isIpLiteralHost("255.255.255.255"), true)
18
- @Expect(isIpLiteralHost("[::1]"), true)
19
- @Expect(isIpLiteralHost("::1"), true)
20
- @Expect(isIpLiteralHost("2606:4700:681a:e1a::1"), true)
21
- }
22
-
23
- @Test
24
- func testIpLiteralHostRejectsHostnames(): Unit {
25
- @Expect(isIpLiteralHost("ai.novacode.top"), false)
26
- @Expect(isIpLiteralHost("localhost"), false)
27
- // 全是十六进制字母的域名(含点)**不是** IP——按「四段十进制」判就不会误伤
28
- @Expect(isIpLiteralHost("dead.beef"), false)
29
- @Expect(isIpLiteralHost("127.0.0.1.evil.com"), false)
30
- @Expect(isIpLiteralHost("1.2.3"), false)
31
- @Expect(isIpLiteralHost("999.1.1.1"), false)
32
- @Expect(isIpLiteralHost("1.2.3.4.5"), false)
33
- @Expect(isIpLiteralHost(""), false)
34
- }
35
-
36
- @Test
37
- func testSniTargetDecisionFromUrl(): Unit {
38
- // 域名 → 带 SNI(这一条正是 ai.novacode.top 那轮修掉的东西)
39
- let named = match (urlHostOf("https://ai.novacode.top/v1/models")) {
40
- case Some(h) => h
41
- case None => ""
42
- }
43
- @Expect(named, "ai.novacode.top")
44
- @Expect(isIpLiteralHost(named), false)
45
-
46
- // 直连 IP(带端口)→ 不带 SNI,连接与验证照旧
47
- let byIp = match (urlHostOf("https://127.0.0.1:8765/api/health")) {
48
- case Some(h) => h
49
- case None => ""
50
- }
51
- @Expect(byIp, "127.0.0.1")
52
- @Expect(isIpLiteralHost(byIp), true)
53
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.unittest.*
5
+ import std.unittest.testmacro.*
6
+
7
+ /*
8
+ * SNI 的目标怎么定:`urlHostOf` 取主机名,`isIpLiteralHost` 判「是不是 IP 字面量」——
9
+ * 域名才带 `serverName`(stdx 默认不发 SNI,多租户 HTTPS 边缘会当场以 handshake failure 拒掉,
10
+ * 实测 Cloudflare 后面的 api 域名,见 D-022);IP 字面量不带(RFC 6066 不许,且直连 IP 原本可用)。
11
+ */
12
+
13
+ @Test
14
+ func testIpLiteralHostRecognizesIpv4AndIpv6(): Unit {
15
+ @Expect(isIpLiteralHost("127.0.0.1"), true)
16
+ @Expect(isIpLiteralHost("192.168.1.10"), true)
17
+ @Expect(isIpLiteralHost("255.255.255.255"), true)
18
+ @Expect(isIpLiteralHost("[::1]"), true)
19
+ @Expect(isIpLiteralHost("::1"), true)
20
+ @Expect(isIpLiteralHost("2606:4700:681a:e1a::1"), true)
21
+ }
22
+
23
+ @Test
24
+ func testIpLiteralHostRejectsHostnames(): Unit {
25
+ @Expect(isIpLiteralHost("ai.novacode.top"), false)
26
+ @Expect(isIpLiteralHost("localhost"), false)
27
+ // 全是十六进制字母的域名(含点)**不是** IP——按「四段十进制」判就不会误伤
28
+ @Expect(isIpLiteralHost("dead.beef"), false)
29
+ @Expect(isIpLiteralHost("127.0.0.1.evil.com"), false)
30
+ @Expect(isIpLiteralHost("1.2.3"), false)
31
+ @Expect(isIpLiteralHost("999.1.1.1"), false)
32
+ @Expect(isIpLiteralHost("1.2.3.4.5"), false)
33
+ @Expect(isIpLiteralHost(""), false)
34
+ }
35
+
36
+ @Test
37
+ func testSniTargetDecisionFromUrl(): Unit {
38
+ // 域名 → 带 SNI(这一条正是 ai.novacode.top 那轮修掉的东西)
39
+ let named = match (urlHostOf("https://ai.novacode.top/v1/models")) {
40
+ case Some(h) => h
41
+ case None => ""
42
+ }
43
+ @Expect(named, "ai.novacode.top")
44
+ @Expect(isIpLiteralHost(named), false)
45
+
46
+ // 直连 IP(带端口)→ 不带 SNI,连接与验证照旧
47
+ let byIp = match (urlHostOf("https://127.0.0.1:8765/api/health")) {
48
+ case Some(h) => h
49
+ case None => ""
50
+ }
51
+ @Expect(byIp, "127.0.0.1")
52
+ @Expect(isIpLiteralHost(byIp), true)
53
+ }
@@ -1,75 +1,75 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import stdx.encoding.json.*
5
- import std.unittest.*
6
- import std.unittest.testmacro.*
7
-
8
- /*
9
- * 版本与内置命令的一致性测试。
10
- * 版本号的权威目标是 CHANGELOG.md,四处常量由 scripts/check-version.sh 巡检;
11
- * 这里只锁「代码里的单一来源不漂移」。
12
- */
13
-
14
- @Test
15
- func testVersionConstantNotEmpty(): Unit {
16
- @Expect(CJ_TAURI_VERSION.size > 0, true)
17
- }
18
-
19
- @Test
20
- func testSystemVersionReportsConstant(): Unit {
21
- let commands = SystemCommands()
22
- let v = commands.handle("system:version", JsonObject(), IpcContext("main", IpcHub()))
23
- let o = v.asObject()
24
- // 命令报告的版本必须就是常量本身(防止有人改了其中一处)
25
- @Expect(o.get("version").getOrThrow().asString().getValue(), CJ_TAURI_VERSION)
26
- @Expect(o.get("name").getOrThrow().asString().getValue(), "cj-tauri")
27
- @Expect(o.get("protocol").getOrThrow().asString().getValue(), CJ_IPC_PROTOCOL)
28
- @Expect(o.get("platform").getOrThrow().asString().getValue(), currentPlatform())
29
- @Expect(currentPlatform().size > 0, true)
30
- }
31
-
32
- @Test
33
- func testIpcProtocolConstantPresent(): Unit {
34
- // 协议版本常量:非空、纯数字(抬版本时按「删字段/改语义必须 +1」的纪律走)
35
- @Expect(CJ_IPC_PROTOCOL.size > 0, true)
36
- for (b in CJ_IPC_PROTOCOL.toArray()) {
37
- @Expect(b >= 48u8 && b <= 57u8, true)
38
- }
39
- }
40
-
41
- @Test
42
- func testSystemPingEchoAndUnknown(): Unit {
43
- let commands = SystemCommands()
44
- let ctx = IpcContext("main", IpcHub())
45
- @Expect(commands.handle("system:ping", JsonObject(), ctx).asString().getValue(), "pong")
46
-
47
- var args = JsonObject()
48
- args.put("who", JsonString("cj"))
49
- let echoed = commands.handle("system:echo", args, ctx).asObject()
50
- @Expect(echoed.get("who").getOrThrow().asString().getValue(), "cj")
51
-
52
- // 未定义的 system:* 走 CommandException,前端拿到的是这句原文
53
- var thrown = ""
54
- try {
55
- commands.handle("system:nope", JsonObject(), ctx)
56
- } catch (e: CommandException) {
57
- thrown = e.message
58
- }
59
- @Expect(thrown, "unknown system command: system:nope")
60
- }
61
-
62
- @Test
63
- func testWindowConfigDefaultsMatchBridgeHardcoding(): Unit {
64
- // 默认值必须与桥里此前的硬编码一致(标题 cj-tauri / 900x640),否则不调 window() 的应用会变样
65
- let def = WindowConfig()
66
- @Expect(def.title, "cj-tauri")
67
- @Expect(def.width, 900)
68
- @Expect(def.height, 640)
69
- @Expect(def.devTools, true)
70
-
71
- let custom = WindowConfig("我的应用", 1200, 800)
72
- @Expect(custom.title, "我的应用")
73
- @Expect(custom.width, 1200)
74
- @Expect(custom.height, 800)
75
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import stdx.encoding.json.*
5
+ import std.unittest.*
6
+ import std.unittest.testmacro.*
7
+
8
+ /*
9
+ * 版本与内置命令的一致性测试。
10
+ * 版本号的权威目标是 CHANGELOG.md,四处常量由 scripts/check-version.sh 巡检;
11
+ * 这里只锁「代码里的单一来源不漂移」。
12
+ */
13
+
14
+ @Test
15
+ func testVersionConstantNotEmpty(): Unit {
16
+ @Expect(CJ_TAURI_VERSION.size > 0, true)
17
+ }
18
+
19
+ @Test
20
+ func testSystemVersionReportsConstant(): Unit {
21
+ let commands = SystemCommands()
22
+ let v = commands.handle("system:version", JsonObject(), IpcContext("main", IpcHub()))
23
+ let o = v.asObject()
24
+ // 命令报告的版本必须就是常量本身(防止有人改了其中一处)
25
+ @Expect(o.get("version").getOrThrow().asString().getValue(), CJ_TAURI_VERSION)
26
+ @Expect(o.get("name").getOrThrow().asString().getValue(), "cj-tauri")
27
+ @Expect(o.get("protocol").getOrThrow().asString().getValue(), CJ_IPC_PROTOCOL)
28
+ @Expect(o.get("platform").getOrThrow().asString().getValue(), currentPlatform())
29
+ @Expect(currentPlatform().size > 0, true)
30
+ }
31
+
32
+ @Test
33
+ func testIpcProtocolConstantPresent(): Unit {
34
+ // 协议版本常量:非空、纯数字(抬版本时按「删字段/改语义必须 +1」的纪律走)
35
+ @Expect(CJ_IPC_PROTOCOL.size > 0, true)
36
+ for (b in CJ_IPC_PROTOCOL.toArray()) {
37
+ @Expect(b >= 48u8 && b <= 57u8, true)
38
+ }
39
+ }
40
+
41
+ @Test
42
+ func testSystemPingEchoAndUnknown(): Unit {
43
+ let commands = SystemCommands()
44
+ let ctx = IpcContext("main", IpcHub())
45
+ @Expect(commands.handle("system:ping", JsonObject(), ctx).asString().getValue(), "pong")
46
+
47
+ var args = JsonObject()
48
+ args.put("who", JsonString("cj"))
49
+ let echoed = commands.handle("system:echo", args, ctx).asObject()
50
+ @Expect(echoed.get("who").getOrThrow().asString().getValue(), "cj")
51
+
52
+ // 未定义的 system:* 走 CommandException,前端拿到的是这句原文
53
+ var thrown = ""
54
+ try {
55
+ commands.handle("system:nope", JsonObject(), ctx)
56
+ } catch (e: CommandException) {
57
+ thrown = e.message
58
+ }
59
+ @Expect(thrown, "unknown system command: system:nope")
60
+ }
61
+
62
+ @Test
63
+ func testWindowConfigDefaultsMatchBridgeHardcoding(): Unit {
64
+ // 默认值必须与桥里此前的硬编码一致(标题 cj-tauri / 900x640),否则不调 window() 的应用会变样
65
+ let def = WindowConfig()
66
+ @Expect(def.title, "cj-tauri")
67
+ @Expect(def.width, 900)
68
+ @Expect(def.height, 640)
69
+ @Expect(def.devTools, true)
70
+
71
+ let custom = WindowConfig("我的应用", 1200, 800)
72
+ @Expect(custom.title, "我的应用")
73
+ @Expect(custom.width, 1200)
74
+ @Expect(custom.height, 800)
75
+ }