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,40 +1,40 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import cjTauri.plugins.ws.*
5
- import std.unittest.*
6
- import std.unittest.testmacro.*
7
-
8
- /*
9
- * ws 插件的策略层用例(帧 IO 归实机:真连接需要对端,单测不起网络)。
10
- * urlHostOf 的 ws/wss 用例也放这里(核心共享层函数,随 ws 插件落地一起验)。
11
- */
12
-
13
- @Test
14
- func testUrlHostOfAcceptsWsSchemes(): Unit {
15
- // cjh web 的默认端点形状:ws://127.0.0.1:8765/ws
16
- @Expect(urlHostOf("ws://127.0.0.1:8765/ws"), "127.0.0.1")
17
- @Expect(urlHostOf("wss://Example.COM/ws"), "example.com")
18
- }
19
-
20
- @Test
21
- func testWsPluginDeclarations(): Unit {
22
- let p = WsPlugin()
23
- @Expect(p.name(), "ws")
24
- @Expect(p.scopeAware(), true)
25
- // 权限集 default = connect + send + close:集声明 != 放行,清单引用才生效
26
- @Expect(p.permissions().contains("default"), true)
27
- let cmds = p.commands()
28
- @Expect(cmds.contains("connect"), true)
29
- @Expect(cmds.contains("send"), true)
30
- @Expect(cmds.contains("close"), true)
31
- // 推两个事件(frame / state),无前端 shim
32
- @Expect(p.events().size == 2, true)
33
- @Expect(p.jsShim().size == 0, true)
34
- }
35
-
36
- @Test
37
- func testWsStateConstants(): Unit {
38
- @Expect(CJ_WS_STATE_CONNECTED, "connected")
39
- @Expect(CJ_WS_STATE_CLOSED, "closed")
40
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import cjTauri.plugins.ws.*
5
+ import std.unittest.*
6
+ import std.unittest.testmacro.*
7
+
8
+ /*
9
+ * ws 插件的策略层用例(帧 IO 归实机:真连接需要对端,单测不起网络)。
10
+ * urlHostOf 的 ws/wss 用例也放这里(核心共享层函数,随 ws 插件落地一起验)。
11
+ */
12
+
13
+ @Test
14
+ func testUrlHostOfAcceptsWsSchemes(): Unit {
15
+ // cjh web 的默认端点形状:ws://127.0.0.1:8765/ws
16
+ @Expect(urlHostOf("ws://127.0.0.1:8765/ws"), "127.0.0.1")
17
+ @Expect(urlHostOf("wss://Example.COM/ws"), "example.com")
18
+ }
19
+
20
+ @Test
21
+ func testWsPluginDeclarations(): Unit {
22
+ let p = WsPlugin()
23
+ @Expect(p.name(), "ws")
24
+ @Expect(p.scopeAware(), true)
25
+ // 权限集 default = connect + send + close:集声明 != 放行,清单引用才生效
26
+ @Expect(p.permissions().contains("default"), true)
27
+ let cmds = p.commands()
28
+ @Expect(cmds.contains("connect"), true)
29
+ @Expect(cmds.contains("send"), true)
30
+ @Expect(cmds.contains("close"), true)
31
+ // 推两个事件(frame / state),无前端 shim
32
+ @Expect(p.events().size == 2, true)
33
+ @Expect(p.jsShim().size == 0, true)
34
+ }
35
+
36
+ @Test
37
+ func testWsStateConstants(): Unit {
38
+ @Expect(CJ_WS_STATE_CONNECTED, "connected")
39
+ @Expect(CJ_WS_STATE_CLOSED, "closed")
40
+ }
@@ -1,129 +1,129 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import std.unittest.*
5
- import std.unittest.testmacro.*
6
-
7
- /*
8
- * 静默启动的参数线格式(`joinHiddenArgs`)单测。
9
- *
10
- * 真正难的**不是拼接**,是「拼出来还能原样切开」这条不变量:C 桥按 0x1F 把这条串切成 argv,
11
- * 一旦某个参数自身带着 0x1F,它就在桥那边变成两个参数——而调用方拿到的是「启动成功」,
12
- * 错误要到目标进程抱怨「未知选项」时才现身。所以这里有两条硬要求:
13
- * ① 参数**逐字**进线格式(空格 / 引号 / 中文都不在这里转义,转义是平台侧的事);
14
- * ② 参数里出现 0x1F 就当场拒绝,不许拼接。
15
- *
16
- * 起真进程 / 判可见窗口的部分不在这里(需要桌面与平台桥),归实机取证。
17
- */
18
-
19
- /** 线格式分隔符:0x1F(单元分隔符),与 C 桥 `CJ_ARG_SEP` 同值 */
20
- let hiddenSep: String = "\u{1F}"
21
-
22
- /** 拼装成功取回线格式;被拒绝时回一个可比较的哨兵值(避免在断言里直接比 Option) */
23
- func hiddenWireOf(args: Array<String>): String {
24
- let parsed = joinHiddenArgs(args)
25
- var wire = "<rejected>"
26
- match (parsed) {
27
- case Some(w) => wire = w
28
- case None => wire = "<rejected>"
29
- }
30
- return wire
31
- }
32
-
33
- /** 是否被拒绝(0x1F 出现在参数里) */
34
- func hiddenArgsRejected(args: Array<String>): Bool {
35
- let parsed = joinHiddenArgs(args)
36
- var rejected = false
37
- match (parsed) {
38
- case Some(_) => rejected = false
39
- case None => rejected = true
40
- }
41
- return rejected
42
- }
43
-
44
- @Test
45
- func testJoinHiddenArgsKeepsOrderAndUsesUnitSeparator(): Unit {
46
- // 顺序必须与传入一致(桥按位取 argv),分隔符只有一个 0x1F
47
- let wire = hiddenWireOf(["web", "--port", "8765", "--bind", "127.0.0.1"])
48
- @Expect(wire, "web" + hiddenSep + "--port" + hiddenSep + "8765" + hiddenSep + "--bind" +
49
- hiddenSep + "127.0.0.1")
50
- }
51
-
52
- @Test
53
- func testJoinHiddenArgsSingleArgHasNoSeparator(): Unit {
54
- // 只有一个参数时线格式里不该留下尾随分隔符(否则桥会切出一个空 argv)
55
- let wire = hiddenWireOf(["web"])
56
- @Expect(wire, "web")
57
- // indexOf 回 Option<Int64>,这里只关心「有没有」,拆开再断言(@Expect 对不同泛型的 Option 实参推不出类型)
58
- let found = wire.indexOf(hiddenSep)
59
- var hasSep = true
60
- match (found) {
61
- case Some(_) => hasSep = true
62
- case None => hasSep = false
63
- }
64
- @Expect(hasSep, false)
65
- }
66
-
67
- @Test
68
- func testJoinHiddenArgsEmptyArgListIsEmptyWire(): Unit {
69
- // 没参数的启动(如 cjh 只用默认端口)走这条:空线格式,桥侧切成零个 argv
70
- let wire = hiddenWireOf([])
71
- @Expect(wire, "")
72
- }
73
-
74
- @Test
75
- func testJoinHiddenArgsKeepsSpacesQuotesAndChineseVerbatim(): Unit {
76
- // 参数原样上线:不在这里加引号、不转义——那些是平台侧(CreateProcessW / posix_spawn)的事。
77
- // 中文按 UTF-8 逐字节过桥,不做任何改写。
78
- let wire = hiddenWireOf(["hello world", "\"quoted\"", "中文参数", "--path=C:/a b/c"])
79
- @Expect(wire, "hello world" + hiddenSep + "\"quoted\"" + hiddenSep + "中文参数" + hiddenSep +
80
- "--path=C:/a b/c")
81
- }
82
-
83
- @Test
84
- func testJoinHiddenArgsKeepsEmptyArgSlot(): Unit {
85
- // 中间那个空串参数不能被吞:它占一个 argv 位,桥那边必须收到一个空参数
86
- let wire = hiddenWireOf(["a", "", "b"])
87
- @Expect(wire, "a" + hiddenSep + hiddenSep + "b")
88
- }
89
-
90
- @Test
91
- func testJoinHiddenArgsRoundTripsThroughSplit(): Unit {
92
- // 不变量:拼出来还能按同一个分隔符切回去,条数不变、逐条相等
93
- let args = ["web", "--port", "8765", "--bind", "127.0.0.1"]
94
- let back = hiddenWireOf(args).split(hiddenSep)
95
- @Expect(back.size, args.size)
96
- var same = true
97
- for (i in 0..args.size) {
98
- if (back[i] != args[i]) {
99
- same = false
100
- }
101
- }
102
- @Expect(same, true)
103
- }
104
-
105
- @Test
106
- func testJoinHiddenArgsRejectsUnitSeparatorInArg(): Unit {
107
- // 0x1F 混在参数里 → 必须拒绝(拼接成功等于把参数悄悄劈成两个)
108
- @Expect(hiddenArgsRejected(["plain" + hiddenSep + "injected"]), true)
109
- // 位置无关:开头 / 中间 / 末尾都要拒
110
- @Expect(hiddenArgsRejected([hiddenSep + "lead"]), true)
111
- @Expect(hiddenArgsRejected(["--port", "1" + hiddenSep + "2"]), true)
112
- @Expect(hiddenArgsRejected(["trail" + hiddenSep]), true)
113
- // 整个参数就是一个分隔符
114
- @Expect(hiddenArgsRejected([hiddenSep]), true)
115
- // 对照:正常参数不被拒(否则上面的「拒绝」可能只是恒 true)
116
- @Expect(hiddenArgsRejected(["web", "--port", "8765"]), false)
117
- }
118
-
119
- @Test
120
- func testLaunchHiddenRejectsEmptyPathWithoutTouchingBridge(): Unit {
121
- // 空路径在**进桥之前**就返回 None:不需要任何平台产物,也不会留下 pid
122
- let started = launchHidden("", ["web"])
123
- var isNone = false
124
- match (started) {
125
- case Some(_) => isNone = false
126
- case None => isNone = true
127
- }
128
- @Expect(isNone, true)
129
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.unittest.*
5
+ import std.unittest.testmacro.*
6
+
7
+ /*
8
+ * 静默启动的参数线格式(`joinHiddenArgs`)单测。
9
+ *
10
+ * 真正难的**不是拼接**,是「拼出来还能原样切开」这条不变量:C 桥按 0x1F 把这条串切成 argv,
11
+ * 一旦某个参数自身带着 0x1F,它就在桥那边变成两个参数——而调用方拿到的是「启动成功」,
12
+ * 错误要到目标进程抱怨「未知选项」时才现身。所以这里有两条硬要求:
13
+ * ① 参数**逐字**进线格式(空格 / 引号 / 中文都不在这里转义,转义是平台侧的事);
14
+ * ② 参数里出现 0x1F 就当场拒绝,不许拼接。
15
+ *
16
+ * 起真进程 / 判可见窗口的部分不在这里(需要桌面与平台桥),归实机取证。
17
+ */
18
+
19
+ /** 线格式分隔符:0x1F(单元分隔符),与 C 桥 `CJ_ARG_SEP` 同值 */
20
+ let hiddenSep: String = "\u{1F}"
21
+
22
+ /** 拼装成功取回线格式;被拒绝时回一个可比较的哨兵值(避免在断言里直接比 Option) */
23
+ func hiddenWireOf(args: Array<String>): String {
24
+ let parsed = joinHiddenArgs(args)
25
+ var wire = "<rejected>"
26
+ match (parsed) {
27
+ case Some(w) => wire = w
28
+ case None => wire = "<rejected>"
29
+ }
30
+ return wire
31
+ }
32
+
33
+ /** 是否被拒绝(0x1F 出现在参数里) */
34
+ func hiddenArgsRejected(args: Array<String>): Bool {
35
+ let parsed = joinHiddenArgs(args)
36
+ var rejected = false
37
+ match (parsed) {
38
+ case Some(_) => rejected = false
39
+ case None => rejected = true
40
+ }
41
+ return rejected
42
+ }
43
+
44
+ @Test
45
+ func testJoinHiddenArgsKeepsOrderAndUsesUnitSeparator(): Unit {
46
+ // 顺序必须与传入一致(桥按位取 argv),分隔符只有一个 0x1F
47
+ let wire = hiddenWireOf(["web", "--port", "8765", "--bind", "127.0.0.1"])
48
+ @Expect(wire, "web" + hiddenSep + "--port" + hiddenSep + "8765" + hiddenSep + "--bind" +
49
+ hiddenSep + "127.0.0.1")
50
+ }
51
+
52
+ @Test
53
+ func testJoinHiddenArgsSingleArgHasNoSeparator(): Unit {
54
+ // 只有一个参数时线格式里不该留下尾随分隔符(否则桥会切出一个空 argv)
55
+ let wire = hiddenWireOf(["web"])
56
+ @Expect(wire, "web")
57
+ // indexOf 回 Option<Int64>,这里只关心「有没有」,拆开再断言(@Expect 对不同泛型的 Option 实参推不出类型)
58
+ let found = wire.indexOf(hiddenSep)
59
+ var hasSep = true
60
+ match (found) {
61
+ case Some(_) => hasSep = true
62
+ case None => hasSep = false
63
+ }
64
+ @Expect(hasSep, false)
65
+ }
66
+
67
+ @Test
68
+ func testJoinHiddenArgsEmptyArgListIsEmptyWire(): Unit {
69
+ // 没参数的启动(如 cjh 只用默认端口)走这条:空线格式,桥侧切成零个 argv
70
+ let wire = hiddenWireOf([])
71
+ @Expect(wire, "")
72
+ }
73
+
74
+ @Test
75
+ func testJoinHiddenArgsKeepsSpacesQuotesAndChineseVerbatim(): Unit {
76
+ // 参数原样上线:不在这里加引号、不转义——那些是平台侧(CreateProcessW / posix_spawn)的事。
77
+ // 中文按 UTF-8 逐字节过桥,不做任何改写。
78
+ let wire = hiddenWireOf(["hello world", "\"quoted\"", "中文参数", "--path=C:/a b/c"])
79
+ @Expect(wire, "hello world" + hiddenSep + "\"quoted\"" + hiddenSep + "中文参数" + hiddenSep +
80
+ "--path=C:/a b/c")
81
+ }
82
+
83
+ @Test
84
+ func testJoinHiddenArgsKeepsEmptyArgSlot(): Unit {
85
+ // 中间那个空串参数不能被吞:它占一个 argv 位,桥那边必须收到一个空参数
86
+ let wire = hiddenWireOf(["a", "", "b"])
87
+ @Expect(wire, "a" + hiddenSep + hiddenSep + "b")
88
+ }
89
+
90
+ @Test
91
+ func testJoinHiddenArgsRoundTripsThroughSplit(): Unit {
92
+ // 不变量:拼出来还能按同一个分隔符切回去,条数不变、逐条相等
93
+ let args = ["web", "--port", "8765", "--bind", "127.0.0.1"]
94
+ let back = hiddenWireOf(args).split(hiddenSep)
95
+ @Expect(back.size, args.size)
96
+ var same = true
97
+ for (i in 0..args.size) {
98
+ if (back[i] != args[i]) {
99
+ same = false
100
+ }
101
+ }
102
+ @Expect(same, true)
103
+ }
104
+
105
+ @Test
106
+ func testJoinHiddenArgsRejectsUnitSeparatorInArg(): Unit {
107
+ // 0x1F 混在参数里 → 必须拒绝(拼接成功等于把参数悄悄劈成两个)
108
+ @Expect(hiddenArgsRejected(["plain" + hiddenSep + "injected"]), true)
109
+ // 位置无关:开头 / 中间 / 末尾都要拒
110
+ @Expect(hiddenArgsRejected([hiddenSep + "lead"]), true)
111
+ @Expect(hiddenArgsRejected(["--port", "1" + hiddenSep + "2"]), true)
112
+ @Expect(hiddenArgsRejected(["trail" + hiddenSep]), true)
113
+ // 整个参数就是一个分隔符
114
+ @Expect(hiddenArgsRejected([hiddenSep]), true)
115
+ // 对照:正常参数不被拒(否则上面的「拒绝」可能只是恒 true)
116
+ @Expect(hiddenArgsRejected(["web", "--port", "8765"]), false)
117
+ }
118
+
119
+ @Test
120
+ func testLaunchHiddenRejectsEmptyPathWithoutTouchingBridge(): Unit {
121
+ // 空路径在**进桥之前**就返回 None:不需要任何平台产物,也不会留下 pid
122
+ let started = launchHidden("", ["web"])
123
+ var isNone = false
124
+ match (started) {
125
+ case Some(_) => isNone = false
126
+ case None => isNone = true
127
+ }
128
+ @Expect(isNone, true)
129
+ }