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.
- package/README.md +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -251
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +605 -585
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- 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
|
+
}
|