mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-04 14:22:11 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcacd5e0e6 | ||
|
|
d8cce2ee2c | ||
|
|
e69c8aeccf | ||
|
|
d2c6eafc9e |
Generated
+64
-170
@@ -150,7 +150,7 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
@@ -159,9 +159,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.3"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
|
||||
checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
@@ -1117,12 +1117,12 @@ dependencies = [
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"open",
|
||||
"parking_lot 0.13.0",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"regex",
|
||||
"reqwest 0.13.5",
|
||||
"reqwest_dav",
|
||||
"rfd 0.17.2",
|
||||
"rfd",
|
||||
"runas",
|
||||
"rustls",
|
||||
"scopeguard",
|
||||
@@ -1175,7 +1175,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"criterion",
|
||||
"parking_lot 0.13.0",
|
||||
"parking_lot",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -1198,7 +1198,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"flexi_logger",
|
||||
"log",
|
||||
"parking_lot 0.13.0",
|
||||
"parking_lot",
|
||||
"tracing-estuary",
|
||||
]
|
||||
|
||||
@@ -1255,8 +1255,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "facfbc410a385e0b0cbca8179dd9e363ed70e8455960bf7545137e90ca753b66"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"lock_api 0.4.14",
|
||||
"parking_lot_core 0.9.12",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
"polonius-the-crab",
|
||||
]
|
||||
|
||||
@@ -1333,7 +1333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1767,18 +1767,8 @@ version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"darling_macro 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
|
||||
dependencies = [
|
||||
"darling_core 0.24.1",
|
||||
"darling_macro 0.24.1",
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1794,41 +1784,17 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
|
||||
dependencies = [
|
||||
"darling_core 0.24.1",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.2.1"
|
||||
@@ -1838,9 +1804,9 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api 0.4.14",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.12",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2048,7 +2014,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3377,7 +3343,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3891,7 +3857,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"libc",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"path-tree",
|
||||
"pin-project-lite",
|
||||
"rand 0.10.2",
|
||||
@@ -4051,20 +4017,11 @@ dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65d91b22da179613dcf4d6ec8349563f52a363f6849e03b00dd0a185ec4a59af"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.35"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
@@ -4074,9 +4031,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust2"
|
||||
version = "0.24.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490750560fa7851fbbf5d3ab1a8750f09350ed361ed8aed85b1e1e40587812ee"
|
||||
checksum = "f1c46f3fcd2e888afdb01a07efc071cb21300e07c68d32c3e8b01b5c8cb1fcaa"
|
||||
dependencies = [
|
||||
"sha2 0.11.1",
|
||||
]
|
||||
@@ -4424,7 +4381,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4806,7 +4763,7 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
dependencies = [
|
||||
"parking_lot_core 0.9.12",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4829,9 +4786,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.4.5"
|
||||
version = "5.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95"
|
||||
checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"is-wsl",
|
||||
@@ -4901,7 +4858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4965,18 +4922,8 @@ version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api 0.4.14",
|
||||
"parking_lot_core 0.9.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9cf937ca4e1d0b7578784559063422fc39ae7c5a00f02471f2614d5486fd226"
|
||||
dependencies = [
|
||||
"lock_api 0.5.0",
|
||||
"parking_lot_core 0.10.0",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4987,19 +4934,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cfff060068cc18aa7815010fcaf80c68c7fb9e85fc7f5c0f9bf142c21d19826"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.9.5",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
@@ -5330,12 +5265,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pollster"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "polonius-the-crab"
|
||||
version = "0.5.0"
|
||||
@@ -5738,15 +5667,6 @@ dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
@@ -5966,30 +5886,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"percent-encoding",
|
||||
"pollster",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
@@ -6131,7 +6027,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6581,7 +6477,7 @@ version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
@@ -6841,7 +6737,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.5.18",
|
||||
"redox_syscall",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@@ -6876,9 +6772,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "specta"
|
||||
version = "2.0.0-rc.26"
|
||||
version = "2.0.0-rc.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed155e448f42fc4be7a5cbc12bd43f6c549981f7c32fcba429ff6bc57cc4f30c"
|
||||
checksum = "38f9a30cbcbb7011f1da7d73483983bf838af123883e45f2b36ed76328df9c50"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"rustc_version 0.4.1",
|
||||
@@ -6888,9 +6784,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "specta-macros"
|
||||
version = "2.0.0-rc.26"
|
||||
version = "2.0.0-rc.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b34b2516e16a9ddd3dc4e73cd6454cfe21fe209f8cb5a6f37346fe3774d759b3"
|
||||
checksum = "2ce14957ecc2897f1f848b8255b6531d13ddf49cbcf506b7c2c9fb1d005593bb"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
@@ -6900,9 +6796,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "specta-serde"
|
||||
version = "0.0.13"
|
||||
version = "0.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4983177ed0eb28235080261953de5f9ebae8a2d107851b5852420bd2ab246245"
|
||||
checksum = "ee8a72b755ddb8949fd8f17c5db43f0e8a806ea587d9bc602ee3f73240c00029"
|
||||
dependencies = [
|
||||
"specta",
|
||||
"specta-macros",
|
||||
@@ -6910,18 +6806,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "specta-typescript"
|
||||
version = "0.0.13"
|
||||
version = "0.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ecbe75dc1d27b36141b50b02b7c73bed00945710cee6edffa86d296a159a6f9"
|
||||
checksum = "639404ee95557f2f8b7e4cb773ffefd45304c7ab8ba21ac83b69051595e083c0"
|
||||
dependencies = [
|
||||
"specta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-util"
|
||||
version = "0.0.13"
|
||||
version = "0.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9bf531215ea70fba8264edecf98f12600462cf359f2038602a47aeda5ecd5b8"
|
||||
checksum = "29b1fc02b446f7244a92924fe68c0555921209f1d342990cd1539e9138e69502"
|
||||
dependencies = [
|
||||
"specta",
|
||||
]
|
||||
@@ -6951,7 +6847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"phf_shared 0.13.1",
|
||||
"precomputed-hash",
|
||||
]
|
||||
@@ -7087,9 +6983,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.40.1"
|
||||
version = "0.39.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e8a3edbe0394ce115f3917a00349dd242be09eb5a091ed509eaf67b55cfb8a8"
|
||||
checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
@@ -7198,7 +7094,7 @@ dependencies = [
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
"tao-macros",
|
||||
@@ -7405,7 +7301,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"deelevate",
|
||||
"libc",
|
||||
"parking_lot 0.13.0",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"specta",
|
||||
"sysinfo",
|
||||
@@ -7484,7 +7380,7 @@ checksum = "b7461aaacf1901915ea7b9f5cd74f8224a351ed96af0a62c07fce934003cb34b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd 0.16.0",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -7793,12 +7689,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta"
|
||||
version = "2.0.0-rc.26"
|
||||
version = "2.0.0-rc.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe71fc209fa4c49f959644f1ae2d70f72d5675bcb3d3ad8518122fa7b330994e"
|
||||
checksum = "ee080f36d2ac17ce2f3a82fb53f02d664e8345457de51b56dad3c394dacc41a2"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
@@ -7812,15 +7707,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta-macros"
|
||||
version = "2.0.0-rc.26"
|
||||
version = "2.0.0-rc.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf5689a1dbf2611e2351b79ba042ca089d0a3ce45274695e79084cd732a5aec"
|
||||
checksum = "6a59dfdce06c98d8d211619bea5fdb39486d8a8c558e12b2d2ce255972320012"
|
||||
dependencies = [
|
||||
"darling 0.24.1",
|
||||
"darling",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8146,9 +8041,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.54.1"
|
||||
version = "1.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
|
||||
checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -8504,7 +8399,7 @@ dependencies = [
|
||||
"flexi_logger",
|
||||
"log",
|
||||
"nu-ansi-term",
|
||||
"parking_lot 0.12.5",
|
||||
"parking_lot",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
@@ -9114,9 +9009,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.11"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171"
|
||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
@@ -9210,7 +9105,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9806,7 +9701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10138,9 +10033,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "9.3.0"
|
||||
version = "9.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426"
|
||||
checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
@@ -10151,7 +10046,6 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"hmac",
|
||||
"indexmap 2.14.0",
|
||||
"libc",
|
||||
"lzma-rust2",
|
||||
"memchr",
|
||||
"nix",
|
||||
|
||||
+3
-3
@@ -63,18 +63,18 @@ tracing = "0.1.44"
|
||||
|
||||
tauri = { version = "2.12.4" }
|
||||
tauri-plugin-clipboard-manager = "2.5.0"
|
||||
parking_lot = "0.13.0"
|
||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
||||
anyhow = "1.0.104"
|
||||
chrono = "0.4.45"
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
tokio = { version = "1.54.1", features = [
|
||||
tokio = { version = "1.53.3", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
flexi_logger = "0.31.10"
|
||||
log = "0.4.35"
|
||||
log = "0.4.34"
|
||||
regex = "1.13.1"
|
||||
reqwest = { version = "0.13.5", features = [
|
||||
"json",
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
specta = { version = "=2.0.0-rc.26", features = ["derive", "function"] }
|
||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
|
||||
clash-verge-logging = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -5,14 +5,14 @@ edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
specta = { version = "=2.0.0-rc.26", features = ["derive", "function"] }
|
||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
# sysinfo 0.38.2 conflicts with dark-light
|
||||
# see https://github.com/GuillaumeGomez/sysinfo/issues/1623
|
||||
sysinfo = { version = "0.40.1", features = ["network", "system"] }
|
||||
sysinfo = { version = "0.39.6", features = ["network", "system"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2.191"
|
||||
|
||||
@@ -27,9 +27,9 @@ pub struct SysInfo {
|
||||
impl Default for SysInfo {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
let system_name = System::name().unwrap_or_else(|_| "Null".into());
|
||||
let system_version = System::long_os_version().unwrap_or_else(|_| "Null".into());
|
||||
let system_kernel_version = System::kernel_version().unwrap_or_else(|_| "Null".into());
|
||||
let system_name = System::name().unwrap_or_else(|| "Null".into());
|
||||
let system_version = System::long_os_version().unwrap_or_else(|| "Null".into());
|
||||
let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into());
|
||||
let system_arch = System::cpu_arch();
|
||||
Self {
|
||||
system_name,
|
||||
@@ -130,14 +130,9 @@ pub fn current_gid() -> u32 {
|
||||
|
||||
#[inline]
|
||||
pub fn list_network_interfaces() -> Vec<String> {
|
||||
let Ok(mut networks) = Networks::new() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut networks = Networks::new();
|
||||
networks.refresh(false);
|
||||
networks
|
||||
.keys()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.collect()
|
||||
networks.keys().map(|name| name.to_owned()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -256,6 +256,40 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['scripts/react/*.{mjs,mts,ts,tsx}'],
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node },
|
||||
parserOptions: {
|
||||
projectService: false,
|
||||
project: './scripts/react/tsconfig.json',
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
'import-x/resolver-next': [
|
||||
createTypeScriptImportResolver({
|
||||
project: './scripts/react/tsconfig.json',
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['scripts/react/*.{mjs,mts}'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: false,
|
||||
project: './scripts/react/tsconfig.node.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['scripts/react/{boundaries,material,scene}.tsx'],
|
||||
// The isolated fixtures use full reloads and preserve aliased Hook names.
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
'@eslint-react/no-unnecessary-use-prefix': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.{ts,tsx,js,jsx,mts,mjs}'],
|
||||
rules: ipcBoundaryRules(),
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@6/schema.json",
|
||||
"entry": ["src/polyfills/*.js!", "scripts/check-contract-changes.mjs!"],
|
||||
"entry": [
|
||||
"src/polyfills/*.js!",
|
||||
"scripts/check-contract-changes.mjs!",
|
||||
"scripts/react/scene.tsx!",
|
||||
"scripts/react/profiler.ts!",
|
||||
"scripts/react/boundaries.tsx!",
|
||||
"scripts/react/material.tsx!",
|
||||
"scripts/react/yaml.ts!",
|
||||
"scripts/react/wdyr.ts!"
|
||||
],
|
||||
"project": ["src/**/*.{js,jsx,ts,tsx,sass,scss}!", "scripts/**/*.{js,mjs}!"],
|
||||
"include": [
|
||||
"files",
|
||||
|
||||
+9
-2
@@ -40,7 +40,11 @@
|
||||
"perf:build": "node scripts/perf/build.mjs",
|
||||
"perf:run": "node scripts/perf/run.mjs",
|
||||
"perf:compare": "node scripts/perf/compare.mjs",
|
||||
"perf:test": "node --test scripts/perf/report.test.mjs"
|
||||
"perf:test": "node --test scripts/perf/report.test.mjs",
|
||||
"dev:profile": "cross-env CVR_REACT_DIAGNOSTICS=profiler node scripts/dev.mjs dev",
|
||||
"react:ab": "node scripts/react/ab.mjs",
|
||||
"dev:wdyr": "cross-env CVR_REACT_DIAGNOSTICS=wdyr node scripts/dev.mjs dev",
|
||||
"audit:react": "node scripts/react/doctor.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/collision": "^0.5.0",
|
||||
@@ -134,7 +138,10 @@
|
||||
"vite-plugin-svgr": "^5.2.0",
|
||||
"vitest": "^5.0.1",
|
||||
"webdriverio": "10.1.0",
|
||||
"ws": "8.22.1"
|
||||
"ws": "8.22.1",
|
||||
"playwright": "1.64.1",
|
||||
"@welldone-software/why-did-you-render": "10.0.1",
|
||||
"react-doctor": "0.9.21"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,mjs}": [
|
||||
|
||||
Generated
+1027
-89
File diff suppressed because it is too large
Load Diff
@@ -113,27 +113,6 @@
|
||||
"ghcr.io/devcontainers/features/rust"
|
||||
],
|
||||
"groupName": "Rust toolchain"
|
||||
},
|
||||
{
|
||||
"description": "Update the Specta contract toolchain together",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchPackageNames": ["specta", "tauri-specta", "specta-typescript"],
|
||||
"groupName": "specta",
|
||||
"groupSlug": "specta",
|
||||
"prCreation": "immediate",
|
||||
"automerge": false
|
||||
},
|
||||
{
|
||||
"description": "Keep Specta aligned with the supported tauri-specta release",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchPackageNames": ["specta", "tauri-specta"],
|
||||
"allowedVersions": "/^2\\.0\\.0-rc\\.26$/"
|
||||
},
|
||||
{
|
||||
"description": "Keep the TypeScript exporter compatible with Specta rc.26",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchPackageNames": ["specta-typescript"],
|
||||
"allowedVersions": "/^0\\.0\\.13$/"
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# React diagnostics
|
||||
|
||||
`pnpm dev:profile` runs the usual development launcher with a root React
|
||||
Profiler. In WebView DevTools, `window.__CVR_REACT_PROFILER__.read()` returns
|
||||
copies of the latest 1,000 standard Profiler callback records; `.clear()` drops
|
||||
them. Records contain timing and commit metadata, never business props. Normal
|
||||
development and production builds do not load the collector.
|
||||
|
||||
`pnpm dev:wdyr` instead initializes why-did-you-render before loading the app
|
||||
and uses its development JSX runtime. It reports avoidable renders for
|
||||
`ProxyItem`, `ProxyItemMini` and `ProxyTunCard` in the console. The modes are
|
||||
mutually exclusive; `CVR_REACT_DIAGNOSTICS` accepts only `profiler` or `wdyr` on
|
||||
the development server. Each mode uses its own Vite cache. Production builds
|
||||
ignore this variable and include neither diagnostic entry nor runtime.
|
||||
|
||||
`pnpm audit:react` scans supported source files under `src`. Pass file paths to
|
||||
limit the scan, for example `pnpm audit:react src/hooks/use-proxy-delay-state.ts`;
|
||||
add `--json` for the original structured report. Diagnostics are advisory.
|
||||
Execution errors and incomplete scans fail the command. Telemetry, remote scores
|
||||
and supply-chain scanning are disabled; the command does not fix code, erase
|
||||
historical diagnostics or run in CI.
|
||||
|
||||
Install the browser explicitly with `pnpm exec playwright install chromium`.
|
||||
Then run `pnpm react:ab --base <ref> --head <ref>`. Both Git trees run against
|
||||
one installed dependency set and the same fixed DNS, delay-hook and proxy-tab
|
||||
fixtures. The command never installs a browser or changes either checkout.
|
||||
It retains source hashes, dependency identity, browser/environment information,
|
||||
behavior assertions, render observations and profiles under `/tmp/cvr-react-ab-*`.
|
||||
Each version and mode has separate Vite caches and output. Three pairs alternate
|
||||
order in ordinary development, Strict Mode and production. A behavior failure
|
||||
returns a nonzero exit status. DNS stale serialization and same-value renders
|
||||
are observations so a known baseline can be compared with its fix. Append
|
||||
`--assert dns` to require the head version to keep the latest serialized YAML in sync with
|
||||
continuous field edits; this is the focused DNS regression assertion.
|
||||
`--assert delay` requires same-value delay updates to retain their reference and memoized consumer,
|
||||
while each changed field (including clearing elapsed time) propagates.
|
||||
`--assert tabs` requires zero tab renders during unrelated parent updates. Use
|
||||
`--assert dns --assert delay --assert tabs` for a cumulative candidate.
|
||||
|
||||
The fixtures execute the actual selected components and hook. IPC, data,
|
||||
editor and UI rendering boundaries are replaced, and outgoing network requests
|
||||
are rejected. DNS serialization is observed at `js-yaml.dump`; tab execution is
|
||||
observed at the Material Paper boundary. These are component results, not native
|
||||
Tauri integration, real DNS/proxy/TUN writes, or whole-application CPU/RSS gains.
|
||||
Production React does not emit Profiler callbacks. Timing is diagnostic and has
|
||||
no hard acceptance threshold; measure native release CPU/RSS separately and
|
||||
calibrate A/A before claiming a performance gain. Keep raw reports out of Git.
|
||||
@@ -0,0 +1,596 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import {
|
||||
cp,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
realpath,
|
||||
symlink,
|
||||
writeFile,
|
||||
} from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { chromium } from 'playwright'
|
||||
import { build, createServer, preview } from 'vite'
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
base: { type: 'string' },
|
||||
head: { type: 'string' },
|
||||
assert: { type: 'string', multiple: true },
|
||||
},
|
||||
})
|
||||
if (!values.base || !values.head)
|
||||
throw new Error('Usage: pnpm react:ab --base <ref> --head <ref>')
|
||||
const assertions = new Set(values.assert ?? [])
|
||||
for (const name of assertions) {
|
||||
if (!['dns', 'delay', 'tabs'].includes(name))
|
||||
throw new Error(`Unknown regression assertion: ${name}`)
|
||||
}
|
||||
const harness = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(harness, '../..')
|
||||
const output = await mkdtemp('/tmp/cvr-react-ab-')
|
||||
const dependencies = path.join(root, 'node_modules')
|
||||
const frozenHarness = path.join(output, 'harness')
|
||||
await cp(harness, frozenHarness, {
|
||||
recursive: true,
|
||||
filter: (file) => !path.basename(file).startsWith('tsconfig'),
|
||||
})
|
||||
const files = [
|
||||
'src/components/setting/mods/dns-viewer.tsx',
|
||||
'src/hooks/use-proxy-delay-state.ts',
|
||||
'src/components/home/proxy-tun-card.tsx',
|
||||
]
|
||||
const hash = (content) => createHash('sha256').update(content).digest('hex')
|
||||
function git(...args) {
|
||||
const result = spawnSync('git', args, {
|
||||
cwd: root,
|
||||
maxBuffer: 128 * 1024 * 1024,
|
||||
})
|
||||
if (result.status !== 0) throw new Error(result.stderr.toString())
|
||||
return result.stdout
|
||||
}
|
||||
const manifest = {
|
||||
assertions: [...assertions],
|
||||
environment: {
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
node: process.version,
|
||||
os: os.release(),
|
||||
lock: hash(await readFile(path.join(root, 'pnpm-lock.yaml'))),
|
||||
},
|
||||
harness: Object.fromEntries(
|
||||
await Promise.all(
|
||||
(await readdir(frozenHarness)).map(async (file) => [
|
||||
file,
|
||||
hash(await readFile(path.join(frozenHarness, file))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
installedLock: hash(
|
||||
await readFile(path.join(dependencies, '.pnpm/lock.yaml')),
|
||||
),
|
||||
sources: {},
|
||||
runs: [],
|
||||
}
|
||||
let browser
|
||||
try {
|
||||
// Playwright intentionally fails here if its explicitly installed browser is missing.
|
||||
browser = await chromium.launch({ headless: true })
|
||||
manifest.environment.browser = browser.version()
|
||||
for (const [variant, ref] of Object.entries({
|
||||
base: values.base,
|
||||
head: values.head,
|
||||
})) {
|
||||
const tree = git('rev-parse', '--verify', `${ref}^{tree}`).toString().trim()
|
||||
const source = path.join(output, variant)
|
||||
await mkdir(source)
|
||||
const archive = git('archive', tree, 'src')
|
||||
const extracted = spawnSync('tar', ['-x', '-C', source], { input: archive })
|
||||
if (extracted.status !== 0) throw new Error(extracted.stderr.toString())
|
||||
await cp(frozenHarness, path.join(source, 'fixture'), { recursive: true })
|
||||
await symlink(dependencies, path.join(source, 'node_modules'), 'dir')
|
||||
manifest.sources[variant] = {
|
||||
ref,
|
||||
tree,
|
||||
files: Object.fromEntries(
|
||||
await Promise.all(
|
||||
files.map(async (file) => [
|
||||
file,
|
||||
hash(await readFile(path.join(source, file))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
for (const mode of ['development', 'strict', 'production']) {
|
||||
const servers = {}
|
||||
try {
|
||||
for (const variant of ['base', 'head']) {
|
||||
const source = path.join(output, variant)
|
||||
const fixture = path.join(source, 'fixture')
|
||||
const boundary = path.join(fixture, 'boundaries.tsx')
|
||||
const aliases = [
|
||||
'@/hooks/use-clash',
|
||||
'@/hooks/use-verge',
|
||||
'@/hooks/use-profiles',
|
||||
'@/hooks/use-system-state',
|
||||
'@/hooks/use-system-proxy-state',
|
||||
'@/services/states',
|
||||
'@/services/cmds',
|
||||
'@/services/mutate',
|
||||
'@/services/notice-service',
|
||||
'@/components/base',
|
||||
'@/components/shared/proxy-control-switches',
|
||||
'react-i18next',
|
||||
].map((find) => ({ find, replacement: boundary }))
|
||||
const config = {
|
||||
configFile: false,
|
||||
root: fixture,
|
||||
cacheDir: path.join(output, 'cache', variant, mode),
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: [
|
||||
...aliases,
|
||||
{
|
||||
find: '@/services/delay',
|
||||
replacement: path.join(fixture, 'delay.ts'),
|
||||
},
|
||||
{
|
||||
find: /^@mui\/material$/,
|
||||
replacement: path.join(fixture, 'material.tsx'),
|
||||
},
|
||||
{
|
||||
find: 'fixture-material',
|
||||
replacement: path.join(dependencies, '@mui/material/index.mjs'),
|
||||
},
|
||||
{ find: 'js-yaml', replacement: path.join(fixture, 'yaml.ts') },
|
||||
{
|
||||
find: 'fixture-yaml',
|
||||
replacement: path.join(
|
||||
dependencies,
|
||||
'js-yaml/dist/js-yaml.mjs',
|
||||
),
|
||||
},
|
||||
{ find: '@', replacement: path.join(source, 'src') },
|
||||
{
|
||||
find: /^react(?=\/|$)/,
|
||||
replacement: path.join(dependencies, 'react'),
|
||||
},
|
||||
{
|
||||
find: /^react-dom(?=\/|$)/,
|
||||
replacement: path.join(dependencies, 'react-dom'),
|
||||
},
|
||||
],
|
||||
dedupe: ['react', 'react-dom'],
|
||||
},
|
||||
define: { OS_PLATFORM: JSON.stringify(process.platform) },
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 0,
|
||||
hmr: false,
|
||||
fs: {
|
||||
allow: [
|
||||
output,
|
||||
await realpath(output),
|
||||
await realpath(dependencies),
|
||||
],
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: path.join(output, 'dist', variant, mode),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
preview: { host: '127.0.0.1', port: 0 },
|
||||
}
|
||||
if (mode === 'production') {
|
||||
await build(config)
|
||||
servers[variant] = await preview(config)
|
||||
} else {
|
||||
servers[variant] = await createServer(config)
|
||||
await servers[variant].listen()
|
||||
}
|
||||
}
|
||||
for (const [pair, order] of [
|
||||
['base', 'head'],
|
||||
['head', 'base'],
|
||||
['base', 'head'],
|
||||
].entries()) {
|
||||
for (const variant of order) {
|
||||
const label = `${mode}-${pair + 1}-${variant}`
|
||||
const context = await browser.newContext()
|
||||
const errors = []
|
||||
const address = servers[variant].httpServer.address()
|
||||
const origin = `http://127.0.0.1:${address.port}`
|
||||
await context.route('**/*', (route) => {
|
||||
if (new URL(route.request().url()).origin === origin)
|
||||
return route.continue()
|
||||
errors.push(`Unexpected network: ${route.request().url()}`)
|
||||
return route.abort()
|
||||
})
|
||||
const page = await context.newPage()
|
||||
page.on('pageerror', (error) => errors.push(String(error)))
|
||||
const result = { label, variant, mode, pair: pair + 1, errors }
|
||||
try {
|
||||
await page.goto(`${origin}/${mode === 'strict' ? '?strict' : ''}`)
|
||||
await page.waitForSelector('#delay', { timeout: 60000 })
|
||||
await page.waitForFunction(
|
||||
() => window.__REACT_AB__.read().listeners.length === 1,
|
||||
)
|
||||
result.tabs = await tabs(page)
|
||||
if (variant === 'head' && assertions.has('tabs')) {
|
||||
assert.equal(
|
||||
result.tabs.unrelated,
|
||||
0,
|
||||
'Unrelated parent updates must not rerender the memoized tabs',
|
||||
)
|
||||
}
|
||||
result.dns = await dns(page)
|
||||
if (variant === 'head' && assertions.has('dns')) {
|
||||
for (const edit of result.dns.edits) {
|
||||
assert.equal(
|
||||
edit.serialized.at(-1),
|
||||
edit.value,
|
||||
'Latest DNS serialization must match the edited field',
|
||||
)
|
||||
}
|
||||
}
|
||||
result.delay = await delay(page)
|
||||
if (variant === 'head' && assertions.has('delay')) {
|
||||
for (const [index, observation] of result.delay.entries()) {
|
||||
assert.equal(
|
||||
observation.sameReference,
|
||||
index === 1,
|
||||
'Preserve the reference only when all three delay fields match',
|
||||
)
|
||||
}
|
||||
assert.equal(
|
||||
result.delay[1].childRenders,
|
||||
result.delay[0].childRenders,
|
||||
'Equal delay state must not rerender the memoized consumer',
|
||||
)
|
||||
}
|
||||
result.profile = await page.evaluate(() => {
|
||||
const before = window.__CVR_REACT_PROFILER__.read()
|
||||
window.__CVR_REACT_PROFILER__.clear()
|
||||
return { before, after: window.__CVR_REACT_PROFILER__.read() }
|
||||
})
|
||||
assert.equal(result.profile.after.length, 0)
|
||||
result.collector = await page.evaluate(() =>
|
||||
window.__REACT_AB__.checkCollector(),
|
||||
)
|
||||
assert.deepEqual(result.collector, {
|
||||
length: 1000,
|
||||
first: '5',
|
||||
last: '1004',
|
||||
copied: true,
|
||||
cleared: true,
|
||||
})
|
||||
if (mode !== 'production')
|
||||
assert(
|
||||
result.profile.before.length > 0 &&
|
||||
result.profile.before.length <= 1000,
|
||||
)
|
||||
assert.deepEqual(errors, [])
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() =>
|
||||
window.__REACT_AB__.fixture.notices.filter(
|
||||
(notice) => notice.error,
|
||||
),
|
||||
),
|
||||
[],
|
||||
)
|
||||
result.ok = true
|
||||
} catch (error) {
|
||||
result.snapshot = await page
|
||||
.evaluate(() => window.__REACT_AB__?.read())
|
||||
.catch(() => null)
|
||||
result.ok = false
|
||||
result.error = String(error)
|
||||
throw error
|
||||
} finally {
|
||||
manifest.runs.push(result)
|
||||
await writeFile(
|
||||
path.join(output, `${label}.json`),
|
||||
JSON.stringify(result, null, 2),
|
||||
)
|
||||
await context.close()
|
||||
console.log(`${label}: ${result.ok ? 'pass' : 'FAIL'}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
for (const server of Object.values(servers)) {
|
||||
if (server.close) await server.close()
|
||||
else
|
||||
await new Promise((resolve, reject) =>
|
||||
server.httpServer.close((error) =>
|
||||
error ? reject(error) : resolve(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser?.close()
|
||||
await writeFile(
|
||||
path.join(output, 'report.json'),
|
||||
JSON.stringify(manifest, null, 2),
|
||||
)
|
||||
console.log(`Evidence: ${output}`)
|
||||
}
|
||||
|
||||
async function settle(page) {
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
async function tabs(page) {
|
||||
const buttons = page.locator('#tabs .MuiPaper-root')
|
||||
const readTabs = () =>
|
||||
page.evaluate(() =>
|
||||
Object.fromEntries(
|
||||
window.__REACT_AB__.fixture.tabRenders.map((tab) => [
|
||||
tab.label.split('.').at(-1),
|
||||
tab,
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
assert.equal(await buttons.count(), 2)
|
||||
assert.equal((await readTabs()).systemProxy.active, true)
|
||||
assert.equal((await readTabs()).tunMode.active, false)
|
||||
const before = await page.evaluate(
|
||||
() => window.__REACT_AB__.fixture.tabRenders.length,
|
||||
)
|
||||
for (let index = 0; index < 5; index++) {
|
||||
await page.evaluate(() => window.__REACT_AB__.update())
|
||||
await settle(page)
|
||||
}
|
||||
const unrelated =
|
||||
(await page.evaluate(() => window.__REACT_AB__.fixture.tabRenders.length)) -
|
||||
before
|
||||
await buttons.nth(1).click()
|
||||
await settle(page)
|
||||
assert.equal((await readTabs()).systemProxy.active, false)
|
||||
assert.equal((await readTabs()).tunMode.active, true)
|
||||
assert.equal(
|
||||
await page.evaluate(() =>
|
||||
localStorage.getItem('clash-verge-proxy-active-tab'),
|
||||
),
|
||||
'tun',
|
||||
)
|
||||
assert(
|
||||
(await page.locator('#fixture-controls').textContent()).endsWith(
|
||||
'toggles.tunMode',
|
||||
),
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ mounted: false }))
|
||||
await settle(page)
|
||||
assert(
|
||||
(await page.locator('#fixture-controls').textContent()).endsWith(
|
||||
'toggles.tunMode',
|
||||
),
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ mounted: true }))
|
||||
await settle(page)
|
||||
assert.equal((await readTabs()).tunMode.active, true)
|
||||
assert.equal((await readTabs()).systemProxy.active, false)
|
||||
await buttons.nth(0).click()
|
||||
await settle(page)
|
||||
assert.equal((await readTabs()).systemProxy.active, true)
|
||||
assert.equal((await readTabs()).tunMode.active, false)
|
||||
assert.equal(
|
||||
await page.evaluate(() =>
|
||||
localStorage.getItem('clash-verge-proxy-active-tab'),
|
||||
),
|
||||
'system',
|
||||
)
|
||||
await page.evaluate(() => {
|
||||
window.__REACT_AB__.fixture.indicator = true
|
||||
window.__REACT_AB__.fixture.verge.enable_tun_mode = true
|
||||
window.__REACT_AB__.update()
|
||||
})
|
||||
await settle(page)
|
||||
assert(Object.values(await readTabs()).every((tab) => tab.indicator))
|
||||
await page.evaluate(() => {
|
||||
window.__REACT_AB__.fixture.language = 'zh'
|
||||
window.__REACT_AB__.update()
|
||||
})
|
||||
await settle(page)
|
||||
const updated = Object.values(await readTabs())
|
||||
assert(updated.every((tab) => tab.label.startsWith('zh:')))
|
||||
assert(
|
||||
(await page.locator('#tabs').textContent()).includes(
|
||||
'zh:home.components.proxyTun.status.systemProxyEnabled',
|
||||
),
|
||||
)
|
||||
const beforeColor = await buttons
|
||||
.nth(0)
|
||||
.evaluate((element) => getComputedStyle(element).backgroundColor)
|
||||
await page.evaluate(() => {
|
||||
window.__REACT_AB__.fixture.theme = 'dark'
|
||||
window.__REACT_AB__.update()
|
||||
})
|
||||
await settle(page)
|
||||
const color = await buttons
|
||||
.nth(0)
|
||||
.evaluate((element) => getComputedStyle(element).backgroundColor)
|
||||
assert.notEqual(color, beforeColor)
|
||||
return { unrelated, updated, beforeColor, color }
|
||||
}
|
||||
async function dns(page) {
|
||||
await page.evaluate(() => window.__REACT_AB__.open())
|
||||
const listen = page.locator('#dns input[placeholder=":53"]')
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.querySelector('#dns input[placeholder=":53"]')?.value ===
|
||||
':5353',
|
||||
)
|
||||
const edits = []
|
||||
for (const value of [':54', ':55', ':56']) {
|
||||
await page.evaluate(() => {
|
||||
window.__REACT_AB__.fixture.serialized.length = 0
|
||||
})
|
||||
await listen.fill(value)
|
||||
await settle(page)
|
||||
assert.equal(await listen.inputValue(), value)
|
||||
edits.push({
|
||||
value,
|
||||
serialized: await page.evaluate(() =>
|
||||
window.__REACT_AB__.fixture.serialized.map(
|
||||
(config) => config.dns.listen,
|
||||
),
|
||||
),
|
||||
})
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
window.__REACT_AB__.fixture.serialized.length = 0
|
||||
})
|
||||
await listen.fill(':')
|
||||
await listen.pressSequentially('567', { delay: 0 })
|
||||
await settle(page)
|
||||
assert.equal(await listen.inputValue(), ':567')
|
||||
edits.push({
|
||||
value: ':567',
|
||||
rapid: true,
|
||||
serialized: await page.evaluate(() =>
|
||||
window.__REACT_AB__.fixture.serialized.map((config) => config.dns.listen),
|
||||
),
|
||||
})
|
||||
await page.locator('#save-dns').click()
|
||||
await page.waitForFunction(
|
||||
() => window.__REACT_AB__.fixture.saved.length === 1,
|
||||
)
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.__REACT_AB__.fixture.saved[0].dns.listen),
|
||||
':567',
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.open())
|
||||
await page.waitForSelector('#dns')
|
||||
await page
|
||||
.getByRole('button', { name: /shared.actions.resetToDefault$/ })
|
||||
.click()
|
||||
await settle(page)
|
||||
assert.equal(await listen.inputValue(), ':53')
|
||||
await page
|
||||
.getByRole('button', { name: /shared.editorModes.advanced$/ })
|
||||
.click()
|
||||
await settle(page)
|
||||
const editor = page.locator('#fixture-monaco')
|
||||
assert.match(await editor.inputValue(), /listen: ["']?:53/)
|
||||
await editor.fill(
|
||||
'dns:\n enable: true\n listen: ":8053"\nhosts:\n example.test: 127.0.0.1\n',
|
||||
)
|
||||
await page
|
||||
.getByRole('button', { name: /shared.editorModes.visualization$/ })
|
||||
.click()
|
||||
await settle(page)
|
||||
assert.equal(await listen.inputValue(), ':8053')
|
||||
await page
|
||||
.getByRole('button', { name: /shared.editorModes.advanced$/ })
|
||||
.click()
|
||||
await settle(page)
|
||||
await page.locator('#save-dns').click()
|
||||
await page.waitForFunction(
|
||||
() => window.__REACT_AB__.fixture.saved.length === 2,
|
||||
)
|
||||
const saved = await page.evaluate(() => window.__REACT_AB__.fixture.saved)
|
||||
assert.equal(saved[1].dns.listen, ':8053')
|
||||
assert.equal(saved[1].hosts['example.test'], '127.0.0.1')
|
||||
return { edits, saved }
|
||||
}
|
||||
async function delay(page) {
|
||||
const updates = [
|
||||
{ delay: 10, elapsed: 12, updatedAt: 100 },
|
||||
{ delay: 10, elapsed: 12, updatedAt: 100 },
|
||||
{ delay: 11, elapsed: 12, updatedAt: 100 },
|
||||
{ delay: 11, elapsed: 13, updatedAt: 100 },
|
||||
{ delay: 11, elapsed: 13, updatedAt: 101 },
|
||||
{ delay: 11, updatedAt: 101 },
|
||||
{ delay: -1, updatedAt: 102 },
|
||||
{ delay: 0, updatedAt: 103 },
|
||||
{ delay: 1000000, elapsed: 20, updatedAt: 104 },
|
||||
]
|
||||
const observations = []
|
||||
for (const update of updates) {
|
||||
await page.evaluate((value) => {
|
||||
window.__REACT_AB__.remember()
|
||||
window.__REACT_AB__.delay.emit('Node 1', 'Group 1', value)
|
||||
}, update)
|
||||
await settle(page)
|
||||
const result = await page.evaluate(() => window.__REACT_AB__.read())
|
||||
assert.deepEqual(result.delay, update)
|
||||
observations.push({
|
||||
update,
|
||||
sameReference: result.sameReference,
|
||||
childRenders: result.childRenders,
|
||||
})
|
||||
}
|
||||
await page.evaluate(() => window.__REACT_AB__.request())
|
||||
await settle(page)
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().delay.delay),
|
||||
-2,
|
||||
)
|
||||
await page.evaluate(() =>
|
||||
window.__REACT_AB__.delay.finish({ delay: 0, updatedAt: 200 }),
|
||||
)
|
||||
await settle(page)
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().delay.delay),
|
||||
0,
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.request())
|
||||
await settle(page)
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().delay.delay),
|
||||
-2,
|
||||
)
|
||||
await page.evaluate(() =>
|
||||
window.__REACT_AB__.delay.finish({
|
||||
delay: 1000000,
|
||||
elapsed: 20,
|
||||
updatedAt: 201,
|
||||
}),
|
||||
)
|
||||
await settle(page)
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().delay),
|
||||
{ delay: 1000000, elapsed: 20, updatedAt: 201 },
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ name: 'Node 2' }))
|
||||
await settle(page)
|
||||
let state = await page.evaluate(() => window.__REACT_AB__.read())
|
||||
assert.deepEqual(state.listeners, ['Group 1:Node 2'])
|
||||
assert.deepEqual(state.delay, { delay: -1, updatedAt: 0 })
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ group: 'Group 2' }))
|
||||
await settle(page)
|
||||
state = await page.evaluate(() => window.__REACT_AB__.read())
|
||||
assert.deepEqual(state.listeners, ['Group 2:Node 2'])
|
||||
for (const name of ['DIRECT', 'Unknown']) {
|
||||
await page.evaluate((name) => window.__REACT_AB__.update({ name }), name)
|
||||
await settle(page)
|
||||
const preset = await page.evaluate(() => window.__REACT_AB__.read())
|
||||
assert.deepEqual(preset.listeners, [])
|
||||
assert.deepEqual(preset.delay, { delay: -1, updatedAt: 0 })
|
||||
}
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ name: 'Node 2' }))
|
||||
await settle(page)
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().listeners),
|
||||
['Group 2:Node 2'],
|
||||
)
|
||||
await page.evaluate(() => window.__REACT_AB__.update({ mounted: false }))
|
||||
await settle(page)
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() => window.__REACT_AB__.read().listeners),
|
||||
[],
|
||||
)
|
||||
return observations
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Switch as MuiSwitch } from '@mui/material'
|
||||
|
||||
import { fixture } from './state'
|
||||
|
||||
const translations = {
|
||||
en: (key: string) => `en:${key}`,
|
||||
zh: (key: string) => `zh:${key}`,
|
||||
}
|
||||
export const useTranslation = () => ({
|
||||
t: translations[fixture.language as 'en' | 'zh'],
|
||||
})
|
||||
export const useVerge = () => ({ verge: fixture.verge })
|
||||
export const useProfiles = () => ({ current: { uid: 'fixture' } })
|
||||
export const useClash = () => ({
|
||||
mutateClash: () => {
|
||||
throw new Error('Unexpected mutateClash')
|
||||
},
|
||||
})
|
||||
export const useSystemState = () => ({ isTunModeAvailable: fixture.available })
|
||||
export const useSystemProxyState = () => ({ indicator: fixture.indicator })
|
||||
export const useThemeMode = () => fixture.theme
|
||||
export const getDnsConfigContent = async () => {
|
||||
fixture.commands.push('getDnsConfigContent')
|
||||
return fixture.initialDns
|
||||
}
|
||||
export const saveDnsConfig = async (config: any) => {
|
||||
fixture.commands.push('saveDnsConfig')
|
||||
fixture.saved.push(structuredClone(config))
|
||||
}
|
||||
export const validateDnsConfig = async () => {
|
||||
fixture.commands.push('validateDnsConfig')
|
||||
return { status: 'valid' }
|
||||
}
|
||||
export const applyDnsConfig = () => {
|
||||
throw new Error('Unexpected applyDnsConfig')
|
||||
}
|
||||
export const mutate = (fn: () => unknown) => fn()
|
||||
export const showNotice = {
|
||||
error: (...args: any[]) => fixture.notices.push({ error: args }),
|
||||
success: (...args: any[]) => fixture.notices.push({ success: args }),
|
||||
}
|
||||
export const Switch = MuiSwitch
|
||||
export function BaseDialog({ open, title, children, onClose, onOk }: any) {
|
||||
return open ? (
|
||||
<section id="dns">
|
||||
{title}
|
||||
<button id="close-dns" onClick={onClose}>
|
||||
close
|
||||
</button>
|
||||
<button id="save-dns" onClick={onOk}>
|
||||
save
|
||||
</button>
|
||||
{children}
|
||||
</section>
|
||||
) : null
|
||||
}
|
||||
export function MonacoEditor({ value, onChange }: any) {
|
||||
return (
|
||||
<textarea
|
||||
id="fixture-monaco"
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default function ProxyControlSwitches({ label }: any) {
|
||||
return <output id="fixture-controls">{label}</output>
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
type Update = { delay: number; elapsed?: number; updatedAt: number }
|
||||
const listeners = new Map<string, (update: Update) => void>()
|
||||
const cache = new Map<string, Update>()
|
||||
let finish: ((update: Update) => void) | undefined
|
||||
const key = (name: string, group: string) => `${group}:${name}`
|
||||
export const delayFixture = {
|
||||
listeners,
|
||||
emit(name: string, group: string, update: Update) {
|
||||
cache.set(key(name, group), update)
|
||||
listeners.get(key(name, group))?.(update)
|
||||
},
|
||||
finish(update: Update) {
|
||||
if (!finish) throw new Error('No pending delay request')
|
||||
finish(update)
|
||||
finish = undefined
|
||||
},
|
||||
}
|
||||
export default {
|
||||
setListener(name: string, group: string, listener: (update: Update) => void) {
|
||||
listeners.set(key(name, group), listener)
|
||||
},
|
||||
removeListener(name: string, group: string) {
|
||||
listeners.delete(key(name, group))
|
||||
},
|
||||
getDelayUpdate(name: string, group: string) {
|
||||
return cache.get(key(name, group))
|
||||
},
|
||||
getDelayFix() {
|
||||
return -1
|
||||
},
|
||||
checkDelay() {
|
||||
return new Promise<Update>((resolve) => {
|
||||
finish = resolve
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { statSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
import { globSync } from 'glob'
|
||||
|
||||
const { values, positionals } = parseArgs({
|
||||
options: { json: { type: 'boolean' } },
|
||||
allowPositionals: true,
|
||||
})
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||||
const files = positionals.length
|
||||
? positionals
|
||||
: globSync('src/**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts,html}', {
|
||||
cwd: root,
|
||||
nodir: true,
|
||||
}).sort()
|
||||
if (files.length === 0) throw new Error('No source files found')
|
||||
for (const file of files) {
|
||||
if (
|
||||
!/\.(?:[jt]sx?|[cm][jt]s|html)$/.test(file) ||
|
||||
!statSync(path.resolve(root, file)).isFile()
|
||||
)
|
||||
throw new Error(`Expected a source file: ${file}`)
|
||||
}
|
||||
const executable = fileURLToPath(
|
||||
new URL('../bin/react-doctor.js', import.meta.resolve('react-doctor')),
|
||||
)
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
executable,
|
||||
...files,
|
||||
'--json',
|
||||
'--no-telemetry',
|
||||
'--no-supply-chain',
|
||||
'--blocking',
|
||||
'none',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'inherit'],
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
},
|
||||
)
|
||||
if (result.error) throw result.error
|
||||
if (result.signal)
|
||||
throw new Error(`React Doctor terminated by ${result.signal}`)
|
||||
const report = JSON.parse(result.stdout)
|
||||
if (values.json) {
|
||||
process.stdout.write(result.stdout)
|
||||
} else {
|
||||
for (const diagnostic of report.diagnostics) {
|
||||
console.log(
|
||||
`${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity} ${diagnostic.plugin}/${diagnostic.rule}\n ${diagnostic.message}`,
|
||||
)
|
||||
}
|
||||
console.log(
|
||||
`${report.summary.totalDiagnosticCount} advisory diagnostics across ${report.projects.reduce((count, project) => count + project.analyzedFileCount, 0)} analyzed files`,
|
||||
)
|
||||
}
|
||||
// In 0.9.21, --blocking none also masks linter execution failures.
|
||||
const incomplete = report.projects.filter((project) => !project.complete)
|
||||
if (report.error) console.error(report.error.message)
|
||||
for (const project of incomplete) {
|
||||
console.error(
|
||||
'Incomplete React Doctor scan:',
|
||||
project.directory,
|
||||
project.skippedCheckReasons,
|
||||
)
|
||||
}
|
||||
process.exitCode =
|
||||
result.status || (!report.ok || incomplete.length > 0 ? 1 : 0)
|
||||
@@ -0,0 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><meta charset="UTF-8" /><title>React component A/B</title></head>
|
||||
<body><div id="root"></div><script type="module" src="./scene.tsx"></script></body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Paper as MuiPaper } from 'fixture-material'
|
||||
export * from 'fixture-material'
|
||||
|
||||
import { fixture } from './state'
|
||||
|
||||
export function Paper(props: any) {
|
||||
const { onClick, elevation, children, sx } = props
|
||||
if (onClick && sx?.maxWidth === 160) {
|
||||
fixture.tabRenders.push({
|
||||
active: elevation === 2,
|
||||
indicator: Boolean(children[2]),
|
||||
label: children[1].props.children,
|
||||
})
|
||||
}
|
||||
return <MuiPaper {...props} />
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ProfilerOnRenderCallback } from 'react'
|
||||
|
||||
export type ProfileRecord = Parameters<ProfilerOnRenderCallback>
|
||||
|
||||
const records: ProfileRecord[] = []
|
||||
|
||||
export const recordRender: ProfilerOnRenderCallback = (...record) => {
|
||||
if (records.length === 1000) records.shift()
|
||||
records.push(record)
|
||||
}
|
||||
|
||||
export const profiler = {
|
||||
read: () => records.map((record) => [...record]),
|
||||
clear: () => {
|
||||
records.length = 0
|
||||
},
|
||||
}
|
||||
|
||||
Object.assign(window, { __CVR_REACT_PROFILER__: profiler })
|
||||
@@ -0,0 +1,124 @@
|
||||
import { ThemeProvider, createTheme } from '@mui/material'
|
||||
import {
|
||||
Profiler,
|
||||
StrictMode,
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { ProxyTunCard } from '@/components/home/proxy-tun-card'
|
||||
import { DnsViewer } from '@/components/setting/mods/dns-viewer'
|
||||
import { useProxyDelayState } from '@/hooks/use-proxy-delay-state'
|
||||
|
||||
import { delayFixture } from './delay'
|
||||
import { profiler, recordRender } from './profiler'
|
||||
import { fixture } from './state'
|
||||
|
||||
const member = (name: string): any =>
|
||||
name === 'Unknown'
|
||||
? {
|
||||
kind: 'unresolved',
|
||||
ref: { kind: 'unresolved', name, reason: 'missing' },
|
||||
}
|
||||
: {
|
||||
kind: 'node',
|
||||
ref: { kind: 'node', recordId: name, name },
|
||||
node: { name, history: [], source: { kind: 'core', proxyName: name } },
|
||||
}
|
||||
let update: (patch?: any) => void
|
||||
let open: () => void
|
||||
let delay: ReturnType<typeof useProxyDelayState>
|
||||
let previous: unknown
|
||||
let childRenders = 0
|
||||
const DelayValue = memo(({ state }: any) => {
|
||||
childRenders++
|
||||
return <output id="delay">{JSON.stringify(state)}</output>
|
||||
})
|
||||
function DelayProbe({ name, group }: any) {
|
||||
const resolved = useMemo(() => member(name), [name])
|
||||
const current = useProxyDelayState(resolved, group)
|
||||
useEffect(() => {
|
||||
delay = current
|
||||
})
|
||||
return <DelayValue state={current.delayState} />
|
||||
}
|
||||
function Scene() {
|
||||
const [state, setState] = useState({
|
||||
revision: 0,
|
||||
name: 'Node 1',
|
||||
group: 'Group 1',
|
||||
mounted: true,
|
||||
})
|
||||
const ref = useRef<any>(null)
|
||||
useEffect(() => {
|
||||
update = (patch = {}) =>
|
||||
setState((old) => ({ ...old, ...patch, revision: old.revision + 1 }))
|
||||
open = () => ref.current.open()
|
||||
}, [])
|
||||
const mode = fixture.theme
|
||||
const theme = useMemo(() => createTheme({ palette: { mode } }), [mode])
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<main data-revision={state.revision}>
|
||||
<section id="tabs" key={state.mounted ? 'mounted' : 'reset'}>
|
||||
<ProxyTunCard />
|
||||
</section>
|
||||
<DnsViewer ref={ref} />
|
||||
{state.mounted && <DelayProbe name={state.name} group={state.group} />}
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
const strict = new URLSearchParams(location.search).has('strict')
|
||||
const app = (
|
||||
<Profiler id="fixtures" onRender={recordRender}>
|
||||
<Scene />
|
||||
</Profiler>
|
||||
)
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
strict ? <StrictMode>{app}</StrictMode> : app,
|
||||
)
|
||||
Object.assign(window, {
|
||||
__REACT_AB__: {
|
||||
update: (patch?: any) => update(patch),
|
||||
open: () => open(),
|
||||
fixture,
|
||||
delay: delayFixture,
|
||||
request: () => {
|
||||
void delay.onDelay()
|
||||
},
|
||||
remember: () => {
|
||||
previous = delay.delayState
|
||||
},
|
||||
checkCollector: () => {
|
||||
profiler.clear()
|
||||
for (let index = 0; index < 1005; index++)
|
||||
recordRender(String(index), 'update', 1, 2, 3, 4)
|
||||
const records = profiler.read()
|
||||
records[0][0] = 'changed copy'
|
||||
const preserved = profiler.read()
|
||||
const result = {
|
||||
length: preserved.length,
|
||||
first: preserved[0][0],
|
||||
last: preserved.at(-1)![0],
|
||||
copied: preserved[0][0] === '5',
|
||||
cleared: false,
|
||||
}
|
||||
profiler.clear()
|
||||
result.cleared = profiler.read().length === 0
|
||||
return result
|
||||
},
|
||||
read: () => ({
|
||||
fixture,
|
||||
profile: profiler.read(),
|
||||
childRenders,
|
||||
sameReference: previous === delay.delayState,
|
||||
delay: delay.delayState,
|
||||
listeners: [...delayFixture.listeners.keys()],
|
||||
}),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
export const fixture = {
|
||||
language: 'en',
|
||||
theme: 'light' as 'light' | 'dark',
|
||||
verge: { enable_tun_mode: false },
|
||||
indicator: false,
|
||||
available: true,
|
||||
initialDns: 'dns:\n enable: true\n listen: ":5353"\n',
|
||||
saved: [] as any[],
|
||||
serialized: [] as any[],
|
||||
tabRenders: [] as any[],
|
||||
notices: [] as any[],
|
||||
commands: [] as string[],
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["../../src/*"],
|
||||
"@root/*": ["../../*"],
|
||||
"fixture-material": ["../../node_modules/@mui/material"],
|
||||
"fixture-yaml": ["../../node_modules/js-yaml"]
|
||||
}
|
||||
},
|
||||
"include": ["./*.ts", "./*.tsx", "../../src"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./*.mjs", "./*.mts"]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { normalizePath, type Plugin } from 'vite'
|
||||
|
||||
export function reactDiagnostics(mode: string | undefined): Plugin[] {
|
||||
if (!mode) return []
|
||||
return [
|
||||
{
|
||||
name: 'react-diagnostics',
|
||||
enforce: 'pre',
|
||||
apply: 'serve',
|
||||
config() {
|
||||
if (mode !== 'profiler' && mode !== 'wdyr')
|
||||
throw new Error(`Unknown CVR_REACT_DIAGNOSTICS mode: ${mode}`)
|
||||
return {
|
||||
cacheDir: path.resolve('node_modules/.vite', mode),
|
||||
...(mode === 'wdyr'
|
||||
? {
|
||||
oxc: {
|
||||
jsx: {
|
||||
importSource: '@welldone-software/why-did-you-render',
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@welldone-software/why-did-you-render',
|
||||
'@welldone-software/why-did-you-render/jsx-dev-runtime',
|
||||
],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
},
|
||||
transformIndexHtml(html) {
|
||||
if (mode !== 'wdyr') return html
|
||||
const entry = 'src="./main.tsx"'
|
||||
if (!html.includes(entry))
|
||||
throw new Error('WDYR entry no longer matches index.html')
|
||||
return html.replace(
|
||||
entry,
|
||||
`src="/@fs/${normalizePath(path.resolve('scripts/react/wdyr.ts'))}"`,
|
||||
)
|
||||
},
|
||||
transform(code, id) {
|
||||
if (mode !== 'profiler') return
|
||||
if (id !== normalizePath(path.resolve('src/main.tsx'))) return
|
||||
const open = '<React.StrictMode>'
|
||||
const close = '</React.StrictMode>'
|
||||
if (!code.includes(open) || !code.includes(close)) {
|
||||
throw new Error('React profiling entry no longer matches main.tsx')
|
||||
}
|
||||
return {
|
||||
code: `import { Profiler } from 'react'\nimport { recordRender } from '../scripts/react/profiler'\n${code}`
|
||||
.replace(open, `<Profiler id="app" onRender={recordRender}>${open}`)
|
||||
.replace(close, `${close}</Profiler>`),
|
||||
map: null,
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import whyDidYouRender from '@welldone-software/why-did-you-render'
|
||||
import React from 'react'
|
||||
|
||||
whyDidYouRender(React, {
|
||||
include: [/^(ProxyItem|ProxyItemMini|ProxyTunCard)$/],
|
||||
trackAllPureComponents: false,
|
||||
trackHooks: true,
|
||||
})
|
||||
|
||||
await import('../../src/main')
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as yaml from 'fixture-yaml'
|
||||
export * from 'fixture-yaml'
|
||||
|
||||
import { fixture } from './state'
|
||||
|
||||
export function dump(value: any, options: any) {
|
||||
fixture.serialized.push(structuredClone(value))
|
||||
return yaml.dump(value, options)
|
||||
}
|
||||
@@ -58,8 +58,8 @@ smartstring = { workspace = true, features = ["serde"] }
|
||||
bitflags = { workspace = true }
|
||||
socket2 = { workspace = true }
|
||||
warp = { version = "0.4.3", features = ["server"] }
|
||||
open = "5.4.5"
|
||||
rfd = { version = "0.17.2", default-features = false, features = ["common-controls-v6", "xdg-portal"] }
|
||||
open = "5.4.4"
|
||||
rfd = { version = "0.16.0", default-features = false, features = ["common-controls-v6"] }
|
||||
dirs = "7.0.0"
|
||||
dunce = "1.0.5"
|
||||
nanoid = "0.5"
|
||||
@@ -79,7 +79,7 @@ tauri-plugin-fs = "2.7.1"
|
||||
tauri-plugin-process = "2.4.0"
|
||||
tauri-plugin-deep-link = "2.6.2"
|
||||
tauri-plugin-window-state = "2.5.0"
|
||||
zip = "9.3.0"
|
||||
zip = "9.1.3"
|
||||
flate2 = "1.1.10"
|
||||
reqwest_dav = "0.3.3"
|
||||
aes-gcm = { version = "0.11.1" }
|
||||
@@ -100,16 +100,16 @@ async-trait = "0.1.92"
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.7", version = "2.7.7", features = [
|
||||
"client",
|
||||
] }
|
||||
arc-swap = "1.9.3"
|
||||
arc-swap = "1.9.2"
|
||||
tokio-rustls = "0.26.6"
|
||||
rustls = { version = "0.23.46", features = ["ring"] }
|
||||
webpki-roots = "1.0.11"
|
||||
webpki-roots = "1.0.9"
|
||||
dark-light = "3.0.0"
|
||||
bytes = "1.12.1"
|
||||
tokio-util = { version = "0.7.20", features = ["time"] }
|
||||
specta = { version = "=2.0.0-rc.26", features = ["derive", "function", "serde_json"] }
|
||||
tauri-specta = { version = "=2.0.0-rc.26", features = ["derive", "typescript"] }
|
||||
specta-typescript = "=0.0.13"
|
||||
specta = { version = "=2.0.0-rc.25", features = ["derive", "function", "serde_json"] }
|
||||
tauri-specta = { version = "=2.0.0-rc.25", features = ["derive", "typescript"] }
|
||||
specta-typescript = "=0.0.12"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.191"
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
useTheme,
|
||||
Fade,
|
||||
} from '@mui/material'
|
||||
import { useState, useMemo, useCallback, memo, FC } from 'react'
|
||||
import { useState, useMemo, memo, FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import ProxyControlSwitches from '@/components/shared/proxy-control-switches'
|
||||
@@ -152,15 +152,10 @@ export const ProxyTunCard: FC = () => {
|
||||
showNotice.error(err)
|
||||
}
|
||||
|
||||
const handleSystemTabClick = useCallback(() => {
|
||||
setActiveTab('system')
|
||||
localStorage.setItem(LOCAL_STORAGE_TAB_KEY, 'system')
|
||||
}, [])
|
||||
|
||||
const handleTunTabClick = useCallback(() => {
|
||||
setActiveTab('tun')
|
||||
localStorage.setItem(LOCAL_STORAGE_TAB_KEY, 'tun')
|
||||
}, [])
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab)
|
||||
localStorage.setItem(LOCAL_STORAGE_TAB_KEY, tab)
|
||||
}
|
||||
|
||||
const tabDescription = useMemo(() => {
|
||||
if (activeTab === 'system') {
|
||||
@@ -196,14 +191,14 @@ export const ProxyTunCard: FC = () => {
|
||||
>
|
||||
<TabButton
|
||||
isActive={activeTab === 'system'}
|
||||
onClick={handleSystemTabClick}
|
||||
onClick={() => handleTabChange('system')}
|
||||
icon={ComputerRounded}
|
||||
label={t('settings.sections.system.toggles.systemProxy')}
|
||||
hasIndicator={systemProxyIndicator}
|
||||
/>
|
||||
<TabButton
|
||||
isActive={activeTab === 'tun'}
|
||||
onClick={handleTunTabClick}
|
||||
onClick={() => handleTabChange('tun')}
|
||||
icon={TroubleshootRounded}
|
||||
label={t('settings.sections.system.toggles.tunMode')}
|
||||
hasIndicator={enable_tun_mode! && isTunModeAvailable}
|
||||
|
||||
@@ -183,7 +183,6 @@ function useProxyRenderState(
|
||||
renderList,
|
||||
onProxies,
|
||||
onHeadState,
|
||||
setHeadState: setListHeadState,
|
||||
handleCheckAll,
|
||||
saveScrollPosition,
|
||||
getScrollPosition,
|
||||
@@ -378,17 +377,12 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
renderList,
|
||||
onProxies,
|
||||
onHeadState,
|
||||
setHeadState,
|
||||
handleCheckAll,
|
||||
getScrollPosition,
|
||||
saveScrollPosition,
|
||||
} = useProxyRenderState(mode, false, null)
|
||||
const emptyList = useEmptyRenderList()
|
||||
const { onDragEnd: onHeaderDragEnd } = useProxyGroupHeaderLayout()
|
||||
const { proxyView } = useProxiesData()
|
||||
const globalGroupName = proxyView?.global?.name
|
||||
// The revealed group sits below GLOBAL's rows, so a click must bring it into view.
|
||||
const pendingRevealRef = useRef<string | null>(null)
|
||||
const renderFirstRef = useRef(true)
|
||||
// Do not persist intermediate positions produced while restoring virtual scroll.
|
||||
const isRestoringRef = useRef(false)
|
||||
@@ -483,17 +477,9 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
if (!['Selector', 'URLTest', 'Fallback'].includes(group.type)) return
|
||||
if (!isInteractableMember(member)) return
|
||||
|
||||
if (group.name === globalGroupName) {
|
||||
pendingRevealRef.current =
|
||||
member.kind === 'group' ? member.ref.name : null
|
||||
// The selection refetches the view, so skip onHeadState's refetch.
|
||||
if (member.kind === 'group') {
|
||||
setHeadState(member.ref.name, { open: true })
|
||||
}
|
||||
}
|
||||
select(group.name, member.ref.name, group.now!, group.fixed!)
|
||||
},
|
||||
[globalGroupName, select, setHeadState],
|
||||
[select],
|
||||
)
|
||||
|
||||
const handleLocation = useStableCallback((group: ProxyGroupView) => {
|
||||
@@ -539,17 +525,6 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
return Array.from(new Set(names))
|
||||
}, [renderList])
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== 'global') {
|
||||
pendingRevealRef.current = null
|
||||
return
|
||||
}
|
||||
const groupName = pendingRevealRef.current
|
||||
if (!groupName || !proxyGroupNames.includes(groupName)) return
|
||||
pendingRevealRef.current = null
|
||||
handleGroupLocationByName(groupName)
|
||||
}, [handleGroupLocationByName, mode, proxyGroupNames])
|
||||
|
||||
const handleGroupToggle = useCallback(
|
||||
async (group: ProxyGroupView) => {
|
||||
const index = renderList.findIndex(
|
||||
@@ -633,7 +608,7 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
/>
|
||||
</DragDropProvider>
|
||||
|
||||
{mode === 'rule' && (
|
||||
{(mode === 'rule' || mode === 'global') && (
|
||||
<ProxyGroupNavigator
|
||||
proxyGroupNames={proxyGroupNames}
|
||||
onGroupLocation={handleGroupLocationByName}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useVerge } from '@/hooks/use-verge'
|
||||
import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context'
|
||||
import delayManager, { type DelaySnapshot } from '@/services/delay'
|
||||
import {
|
||||
findCurrentGroupMember,
|
||||
isInteractableMember,
|
||||
resolveMember,
|
||||
selectGlobalChainNodes,
|
||||
@@ -79,18 +78,6 @@ const resolveOccurrences = (view: ProxyViewV1, group: ProxyGroupView) =>
|
||||
member: resolveMember(view, member),
|
||||
}))
|
||||
|
||||
// mihomo selects DIRECT in GLOBAL by default, so the selected built-in stays visible.
|
||||
const HIDDEN_GLOBAL_BUILTINS = new Set(['DIRECT', 'REJECT'])
|
||||
|
||||
const isHiddenGlobalBuiltin = (
|
||||
group: ProxyGroupView,
|
||||
{ member }: ResolvedMemberOccurrence,
|
||||
) =>
|
||||
member.kind === 'node' &&
|
||||
member.node.source.kind === 'core' &&
|
||||
HIDDEN_GLOBAL_BUILTINS.has(member.node.source.proxyName) &&
|
||||
member.ref.name !== group.now
|
||||
|
||||
const memberKey = (
|
||||
group: ProxyGroupView,
|
||||
occurrence: ResolvedMemberOccurrence,
|
||||
@@ -222,10 +209,8 @@ export const useRenderList = (
|
||||
const renderGroups = useMemo(() => {
|
||||
if (!proxyView) return []
|
||||
if (mode === 'rule') return proxyView.groups
|
||||
const { global } = proxyView
|
||||
if (!global) return []
|
||||
const current = findCurrentGroupMember(proxyView, global)?.member
|
||||
return current?.kind === 'group' ? [global, current.group] : [global]
|
||||
const global = proxyView.global ? [proxyView.global] : []
|
||||
return mode === 'global' ? [...global, ...proxyView.groups] : global
|
||||
}, [mode, proxyView])
|
||||
|
||||
// Every group this list draws, so a test settling in any of them re-sorts that group.
|
||||
@@ -275,10 +260,10 @@ export const useRenderList = (
|
||||
}))
|
||||
}
|
||||
|
||||
const useRule = mode === 'rule' || mode === 'global'
|
||||
const cache = groupCacheRef.current
|
||||
|
||||
const retList = renderGroups.flatMap((group) => {
|
||||
const isFlat = group === proxyView.global
|
||||
const headState = headStates[group.name] || DEFAULT_STATE
|
||||
const cached = cache.get(group.name)
|
||||
if (
|
||||
@@ -294,24 +279,19 @@ export const useRenderList = (
|
||||
}
|
||||
|
||||
const ret: IRenderItem[] = [
|
||||
isFlat
|
||||
? { type: 1, key: `head-${group.name}`, group, headState }
|
||||
: {
|
||||
type: 0,
|
||||
key: group.name,
|
||||
group,
|
||||
headState,
|
||||
icon: group.icon!,
|
||||
testUrl: group.testUrl!,
|
||||
},
|
||||
{
|
||||
type: 0,
|
||||
key: group.name,
|
||||
group,
|
||||
headState,
|
||||
icon: group.icon!,
|
||||
testUrl: group.testUrl!,
|
||||
},
|
||||
]
|
||||
|
||||
if (headState.open || isFlat) {
|
||||
const resolved = resolveOccurrences(proxyView, group)
|
||||
if (headState.open || !useRule) {
|
||||
const occurrences = filterSort(
|
||||
isFlat
|
||||
? resolved.filter((item) => !isHiddenGlobalBuiltin(group, item))
|
||||
: resolved,
|
||||
resolveOccurrences(proxyView, group),
|
||||
group.name,
|
||||
headState.filterText,
|
||||
headState.sortType,
|
||||
@@ -322,6 +302,9 @@ export const useRenderList = (
|
||||
useRegularExpression: headState.filterUseRegularExpression,
|
||||
},
|
||||
)
|
||||
if (!useRule) {
|
||||
ret.push({ type: 1, key: `head-${group.name}`, group, headState })
|
||||
}
|
||||
if (occurrences.length === 0) {
|
||||
ret.push({ type: 3, key: `empty-${group.name}`, group, headState })
|
||||
} else if (col > 1) {
|
||||
@@ -360,8 +343,9 @@ export const useRenderList = (
|
||||
return ret
|
||||
})
|
||||
|
||||
const filtered =
|
||||
mode === 'rule' ? retList.filter((item) => !item.group.hidden) : retList
|
||||
const filtered = !useRule
|
||||
? retList.slice(1)
|
||||
: retList.filter((item) => !item.group.hidden)
|
||||
if (
|
||||
prevListRef.current.length === filtered.length &&
|
||||
filtered.every((item, index) => item === prevListRef.current[index])
|
||||
|
||||
@@ -586,10 +586,20 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
? event.target.checked
|
||||
: event.target.value
|
||||
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}))
|
||||
setValues((prev) => {
|
||||
const newValues = {
|
||||
...prev,
|
||||
[field]: value,
|
||||
}
|
||||
|
||||
if (visualization) {
|
||||
setTimeout(() => {
|
||||
updateYamlFromValues()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return newValues
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,10 +7,6 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { BaseDialog } from '@/components/base'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import type {
|
||||
IVergeTestItem_Deserialize,
|
||||
IVergeTestItem_Serialize,
|
||||
} from '@/services/bindings'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
|
||||
interface Props {
|
||||
@@ -31,7 +27,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { verge, patchVerge } = useVerge()
|
||||
const testList = verge?.test_list ?? []
|
||||
const { control, ...formIns } = useForm<IVergeTestItem_Deserialize>({
|
||||
const { control, ...formIns } = useForm<IVergeTestItem>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
icon: '',
|
||||
@@ -41,16 +37,11 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
||||
|
||||
const patchTestList = async (
|
||||
uid: string,
|
||||
patch: Partial<IVergeTestItem_Deserialize>,
|
||||
patch: Partial<IVergeTestItem>,
|
||||
) => {
|
||||
const newList = testList.map((x) => {
|
||||
if (x.uid === uid) {
|
||||
return {
|
||||
uid: x.uid,
|
||||
name: patch.name === undefined ? x.name : patch.name,
|
||||
icon: patch.icon === undefined ? x.icon : patch.icon,
|
||||
url: patch.url === undefined ? x.url : patch.url,
|
||||
}
|
||||
return { ...x, ...patch }
|
||||
}
|
||||
return x
|
||||
})
|
||||
@@ -86,12 +77,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>(
|
||||
|
||||
if (openType === 'new') {
|
||||
const uid = nanoid()
|
||||
const item: IVergeTestItem_Serialize = {
|
||||
uid,
|
||||
name: form.name ?? null,
|
||||
icon: form.icon ?? null,
|
||||
url: form.url ?? null,
|
||||
}
|
||||
const item = { ...form, uid }
|
||||
const newList = [...testList, item]
|
||||
await patchVerge({ test_list: newList })
|
||||
onChange(uid)
|
||||
|
||||
@@ -17,12 +17,7 @@ const PRESET_PROXY_NAMES = [
|
||||
'COMPATIBLE',
|
||||
]
|
||||
|
||||
const identity = (previous: DelayUpdate, next: DelayUpdate): DelayUpdate =>
|
||||
previous.delay === next.delay &&
|
||||
previous.elapsed === next.elapsed &&
|
||||
previous.updatedAt === next.updatedAt
|
||||
? previous
|
||||
: next
|
||||
const identity = (_: DelayUpdate, next: DelayUpdate): DelayUpdate => next
|
||||
|
||||
const INITIAL_DELAY: DelayUpdate = { delay: -1, updatedAt: 0 }
|
||||
|
||||
|
||||
+149
-255
@@ -23,10 +23,7 @@ export const commands = {
|
||||
__TAURI_INVOKE<string[]>('get_network_interfaces'),
|
||||
getSystemHostname: () => __TAURI_INVOKE<string>('get_system_hostname'),
|
||||
restartApp: () => __TAURI_INVOKE<null>('restart_app'),
|
||||
installUpdate: (
|
||||
version: string,
|
||||
onEvent: Channel<DownloadEvent_Deserialize>,
|
||||
) =>
|
||||
installUpdate: (version: string, onEvent: Channel<DownloadEvent>) =>
|
||||
__TAURI_INVOKE<InstallUpdateOutcome>('install_update', {
|
||||
version,
|
||||
onEvent,
|
||||
@@ -37,12 +34,11 @@ export const commands = {
|
||||
upgradeClashCore: (force: boolean) =>
|
||||
__TAURI_INVOKE<CoreUpgradeReport>('upgrade_clash_core', { force }),
|
||||
/** Returns one coherent core/service snapshot instead of independently refreshed state. */
|
||||
getRuntimeState: () =>
|
||||
__TAURI_INVOKE<RunStateView_Serialize>('get_runtime_state'),
|
||||
getRuntimeState: () => __TAURI_INVOKE<RunStateView>('get_runtime_state'),
|
||||
getPendingFailures: () =>
|
||||
__TAURI_INVOKE<PendingFailure[]>('get_pending_failures'),
|
||||
getSidecarFailure: () =>
|
||||
__TAURI_INVOKE<SidecarFailureSnapshot_Serialize>('get_sidecar_failure'),
|
||||
__TAURI_INVOKE<SidecarFailureSnapshot>('get_sidecar_failure'),
|
||||
entryLightweightMode: () => __TAURI_INVOKE<null>('entry_lightweight_mode'),
|
||||
installService: () =>
|
||||
__TAURI_INVOKE<ServiceInstallOutcome>('install_service'),
|
||||
@@ -52,7 +48,7 @@ export const commands = {
|
||||
repairService: () => __TAURI_INVOKE<ServiceInstallOutcome>('repair_service'),
|
||||
continueWithSidecar: () => __TAURI_INVOKE<null>('continue_with_sidecar'),
|
||||
syncRuntimeProviders: () => __TAURI_INVOKE<void>('sync_runtime_providers'),
|
||||
getClashInfo: () => __TAURI_INVOKE<ClashInfo_Serialize>('get_clash_info'),
|
||||
getClashInfo: () => __TAURI_INVOKE<ClashInfo>('get_clash_info'),
|
||||
patchClashConfig: (payload: YamlMapping) =>
|
||||
__TAURI_INVOKE<null>('patch_clash_config', { payload }),
|
||||
patchClashMode: (payload: string) =>
|
||||
@@ -60,11 +56,16 @@ export const commands = {
|
||||
/** Reads the saved mode without depending on strict mihomo `/configs` deserialization. */
|
||||
getClashMode: () => __TAURI_INVOKE<string | null>('get_clash_mode'),
|
||||
changeClashCore: (clashCore: string) =>
|
||||
__TAURI_INVOKE<CommandFailure_Serialize | null>('change_clash_core', {
|
||||
clashCore,
|
||||
}),
|
||||
__TAURI_INVOKE<{
|
||||
/** Stable frontend identifier, when classified. */
|
||||
code?: string | null
|
||||
/** Human-readable diagnostic detail. */
|
||||
detail: string
|
||||
/** Requested operation when known by the command. */
|
||||
operation?: FailedOperation | null
|
||||
} | null>('change_clash_core', { clashCore }),
|
||||
getRuntimeConfig: () =>
|
||||
__TAURI_INVOKE<YamlMapping | null>('get_runtime_config'),
|
||||
__TAURI_INVOKE<{ [key in string]: JsonValue } | null>('get_runtime_config'),
|
||||
getProxyView: () => __TAURI_INVOKE<ProxyViewV1_Serialize>('get_proxy_view'),
|
||||
getRuntimeYaml: () => __TAURI_INVOKE<string>('get_runtime_yaml'),
|
||||
getRuntimeLogs: () => __TAURI_INVOKE<RuntimeLogs>('get_runtime_logs'),
|
||||
@@ -72,7 +73,7 @@ export const commands = {
|
||||
__TAURI_INVOKE<string>('get_runtime_proxy_chain_config', {
|
||||
proxyChainExitNode,
|
||||
}),
|
||||
updateProxyChainConfigInRuntime: (proxyChainConfig: YamlValue | null) =>
|
||||
updateProxyChainConfigInRuntime: (proxyChainConfig: JsonValue | null) =>
|
||||
__TAURI_INVOKE<ValidationOutcome>('update_proxy_chain_config_in_runtime', {
|
||||
proxyChainConfig,
|
||||
}),
|
||||
@@ -102,11 +103,18 @@ export const commands = {
|
||||
takeDnsOverrideNotice: () =>
|
||||
__TAURI_INVOKE<boolean>('take_dns_override_notice'),
|
||||
takeServiceFallbackNotice: () =>
|
||||
__TAURI_INVOKE<ServiceFallbackNotice | null>(
|
||||
'take_service_fallback_notice',
|
||||
),
|
||||
__TAURI_INVOKE<
|
||||
| { kind: 'unavailable' }
|
||||
| { kind: 'coreRejected'; reason: string }
|
||||
| { kind: 'notAutoStarted' }
|
||||
| null
|
||||
>('take_service_fallback_notice'),
|
||||
getCoreStartupError: () =>
|
||||
__TAURI_INVOKE<CoreFailure | null>('get_core_startup_error'),
|
||||
__TAURI_INVOKE<
|
||||
| { kind: 'startFailed'; detail: string }
|
||||
| { kind: 'serviceCoreStopped'; detail: string }
|
||||
| null
|
||||
>('get_core_startup_error'),
|
||||
takeServiceRepairNotice: () =>
|
||||
__TAURI_INVOKE<boolean>('take_service_repair_notice'),
|
||||
takeServiceOwnerNotice: () =>
|
||||
@@ -132,12 +140,10 @@ export const commands = {
|
||||
openDevtools: () => __TAURI_INVOKE<void>('open_devtools'),
|
||||
exitApp: () => __TAURI_INVOKE<void>('exit_app'),
|
||||
getNetworkInterfacesInfo: () =>
|
||||
__TAURI_INVOKE<NetworkInterfaceView_Serialize[]>(
|
||||
'get_network_interfaces_info',
|
||||
),
|
||||
__TAURI_INVOKE<NetworkInterfaceView[]>('get_network_interfaces_info'),
|
||||
getProfiles: () => __TAURI_INVOKE<IProfiles_Serialize>('get_profiles'),
|
||||
enhanceProfiles: () => __TAURI_INVOKE<ValidationOutcome>('enhance_profiles'),
|
||||
patchProfilesConfig: (profiles: ProfileSelection_Deserialize) =>
|
||||
patchProfilesConfig: (profiles: ProfileSelection) =>
|
||||
__TAURI_INVOKE<ValidationOutcome>('patch_profiles_config', { profiles }),
|
||||
viewProfile: (index: string) =>
|
||||
__TAURI_INVOKE<null>('view_profile', { index }),
|
||||
@@ -145,12 +151,52 @@ export const commands = {
|
||||
__TAURI_INVOKE<null>('patch_profile', { index, profile }),
|
||||
createProfile: (item: ProfileCreate_Deserialize, fileData: string | null) =>
|
||||
__TAURI_INVOKE<null>('create_profile', { item, fileData }),
|
||||
importProfile: (url: string, option: PrfOption_Deserialize | null) =>
|
||||
__TAURI_INVOKE<null>('import_profile', { url, option }),
|
||||
importProfile: (
|
||||
url: string,
|
||||
option: {
|
||||
user_agent?: string | null
|
||||
with_proxy?: boolean | null
|
||||
self_proxy?: boolean | null
|
||||
update_interval?: number | null
|
||||
/**
|
||||
* HTTP request timeout in seconds
|
||||
* default is 60 seconds
|
||||
*/
|
||||
timeout_seconds?: number | null
|
||||
/** default is `false` */
|
||||
danger_accept_invalid_certs?: boolean | null
|
||||
allow_auto_update?: boolean | null
|
||||
merge?: string | null
|
||||
script?: string | null
|
||||
rules?: string | null
|
||||
proxies?: string | null
|
||||
groups?: string | null
|
||||
} | null,
|
||||
) => __TAURI_INVOKE<null>('import_profile', { url, option }),
|
||||
reorderProfile: (activeId: string, overId: string) =>
|
||||
__TAURI_INVOKE<null>('reorder_profile', { activeId, overId }),
|
||||
updateProfile: (index: string, option: PrfOption_Deserialize | null) =>
|
||||
__TAURI_INVOKE<null>('update_profile', { index, option }),
|
||||
updateProfile: (
|
||||
index: string,
|
||||
option: {
|
||||
user_agent?: string | null
|
||||
with_proxy?: boolean | null
|
||||
self_proxy?: boolean | null
|
||||
update_interval?: number | null
|
||||
/**
|
||||
* HTTP request timeout in seconds
|
||||
* default is 60 seconds
|
||||
*/
|
||||
timeout_seconds?: number | null
|
||||
/** default is `false` */
|
||||
danger_accept_invalid_certs?: boolean | null
|
||||
allow_auto_update?: boolean | null
|
||||
merge?: string | null
|
||||
script?: string | null
|
||||
rules?: string | null
|
||||
proxies?: string | null
|
||||
groups?: string | null
|
||||
} | null,
|
||||
) => __TAURI_INVOKE<null>('update_profile', { index, option }),
|
||||
deleteProfile: (index: string) =>
|
||||
__TAURI_INVOKE<null>('delete_profile', { index }),
|
||||
readProfileFile: (index: string) =>
|
||||
@@ -178,14 +224,11 @@ export const commands = {
|
||||
__TAURI_INVOKE<null>('delete_webdav_backup', { filename }),
|
||||
restoreWebdavBackup: (filename: string) =>
|
||||
__TAURI_INVOKE<null>('restore_webdav_backup', { filename }),
|
||||
getUnlockItems: () =>
|
||||
__TAURI_INVOKE<UnlockItem_Serialize[]>('get_unlock_items'),
|
||||
checkMediaUnlock: (onComplete: Channel<UnlockItem_Deserialize>) =>
|
||||
__TAURI_INVOKE<UnlockItem_Serialize[]>('check_media_unlock', {
|
||||
onComplete,
|
||||
}),
|
||||
getUnlockItems: () => __TAURI_INVOKE<UnlockItem[]>('get_unlock_items'),
|
||||
checkMediaUnlock: (onComplete: Channel<UnlockItem>) =>
|
||||
__TAURI_INVOKE<UnlockItem[]>('check_media_unlock', { onComplete }),
|
||||
checkMediaUnlockItem: (name: string) =>
|
||||
__TAURI_INVOKE<UnlockItem_Serialize>('check_media_unlock_item', { name }),
|
||||
__TAURI_INVOKE<UnlockItem>('check_media_unlock_item', { name }),
|
||||
}
|
||||
|
||||
/* Types */
|
||||
@@ -194,20 +237,7 @@ export type AutoProxy = {
|
||||
url: string
|
||||
}
|
||||
|
||||
export type ClashInfo = ClashInfo_Serialize | ClashInfo_Deserialize
|
||||
|
||||
export type ClashInfo_Deserialize = {
|
||||
/** clash core port */
|
||||
mixed_port: number
|
||||
socks_port: number
|
||||
port: number
|
||||
/** same as `external-controller` */
|
||||
server: string
|
||||
/** clash secret */
|
||||
secret?: string | null
|
||||
}
|
||||
|
||||
export type ClashInfo_Serialize = {
|
||||
export type ClashInfo = {
|
||||
/** clash core port */
|
||||
mixed_port: number
|
||||
socks_port: number
|
||||
@@ -226,21 +256,21 @@ export type CommandFailure =
|
||||
/** Structured command failure returned to the frontend. */
|
||||
export type CommandFailure_Deserialize = {
|
||||
/** Stable frontend identifier, when classified. */
|
||||
code?: string | null
|
||||
code: string | null
|
||||
/** Human-readable diagnostic detail. */
|
||||
detail: string
|
||||
/** Requested operation when known by the command. */
|
||||
operation?: FailedOperation | null
|
||||
operation: FailedOperation | null
|
||||
}
|
||||
|
||||
/** Structured command failure returned to the frontend. */
|
||||
export type CommandFailure_Serialize = {
|
||||
/** Stable frontend identifier, when classified. */
|
||||
code?: string
|
||||
code?: string | null
|
||||
/** Human-readable diagnostic detail. */
|
||||
detail: string
|
||||
/** Requested operation when known by the command. */
|
||||
operation?: FailedOperation
|
||||
operation?: FailedOperation | null
|
||||
}
|
||||
|
||||
/** Why the Core is not running, kept until a Core starts again. */
|
||||
@@ -263,26 +293,7 @@ export type DnsOverrideOutcome =
|
||||
| { status: 'confirmation_required'; source: string }
|
||||
|
||||
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||
export type DownloadEvent = DownloadEvent_Serialize | DownloadEvent_Deserialize
|
||||
|
||||
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||
export type DownloadEvent_Deserialize =
|
||||
| {
|
||||
event: 'Started'
|
||||
data: {
|
||||
contentLength?: number | null
|
||||
}
|
||||
}
|
||||
| {
|
||||
event: 'Progress'
|
||||
data: {
|
||||
chunkLength: number
|
||||
}
|
||||
}
|
||||
| { event: 'Finished' }
|
||||
|
||||
/** Same wire shape as the updater plugin's JS `DownloadEvent`. */
|
||||
export type DownloadEvent_Serialize =
|
||||
export type DownloadEvent =
|
||||
| {
|
||||
event: 'Started'
|
||||
data: {
|
||||
@@ -322,27 +333,14 @@ export type IProfiles_Serialize = {
|
||||
/** ### `verge.yaml` schema */
|
||||
export type IVerge = IVerge_Serialize | IVerge_Deserialize
|
||||
|
||||
export type IVergeTestItem =
|
||||
| IVergeTestItem_Serialize
|
||||
| IVergeTestItem_Deserialize
|
||||
|
||||
export type IVergeTestItem_Deserialize = {
|
||||
export type IVergeTestItem = {
|
||||
uid?: string | null
|
||||
name?: string | null
|
||||
icon?: string | null
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
export type IVergeTestItem_Serialize = {
|
||||
uid: string | null
|
||||
name: string | null
|
||||
icon: string | null
|
||||
url: string | null
|
||||
}
|
||||
|
||||
export type IVergeTheme = IVergeTheme_Serialize | IVergeTheme_Deserialize
|
||||
|
||||
export type IVergeTheme_Deserialize = {
|
||||
export type IVergeTheme = {
|
||||
primary_color?: string | null
|
||||
secondary_color?: string | null
|
||||
primary_text?: string | null
|
||||
@@ -355,19 +353,6 @@ export type IVergeTheme_Deserialize = {
|
||||
css_injection?: string | null
|
||||
}
|
||||
|
||||
export type IVergeTheme_Serialize = {
|
||||
primary_color: string | null
|
||||
secondary_color: string | null
|
||||
primary_text: string | null
|
||||
secondary_text: string | null
|
||||
info_color: string | null
|
||||
error_color: string | null
|
||||
warning_color: string | null
|
||||
success_color: string | null
|
||||
font_family: string | null
|
||||
css_injection: string | null
|
||||
}
|
||||
|
||||
/** ### `verge.yaml` schema */
|
||||
export type IVerge_Deserialize = {
|
||||
/** silent | error | warn | info | debug | trace */
|
||||
@@ -410,7 +395,7 @@ export type IVerge_Deserialize = {
|
||||
proxy_auto_config?: boolean | null
|
||||
pac_file_content?: string | null
|
||||
proxy_host?: string | null
|
||||
theme_setting?: IVergeTheme_Deserialize | null
|
||||
theme_setting?: IVergeTheme | null
|
||||
web_ui_list?: string[] | null
|
||||
clash_core?: string | null
|
||||
/** format: {func},{key} */
|
||||
@@ -426,7 +411,7 @@ export type IVerge_Deserialize = {
|
||||
auto_delay_detection_interval_minutes?: number | null
|
||||
enable_builtin_enhanced?: boolean | null
|
||||
proxy_layout_column?: number | null
|
||||
test_list?: IVergeTestItem_Deserialize[] | null
|
||||
test_list?: IVergeTestItem[] | null
|
||||
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
||||
auto_log_clean?: number | null
|
||||
enable_auto_backup_schedule?: boolean | null
|
||||
@@ -475,13 +460,13 @@ export type IVerge_Serialize = {
|
||||
traffic_graph: boolean | null
|
||||
/** show memory info (only for Mihomo) */
|
||||
enable_memory_usage: boolean | null
|
||||
enable_group_icon?: boolean
|
||||
enable_group_icon?: boolean | null
|
||||
pause_render_traffic_stats_on_blur: boolean | null
|
||||
common_tray_icon?: boolean
|
||||
menu_icon?: string
|
||||
menu_order?: string[]
|
||||
proxy_group_tools_position?: string
|
||||
notice_position?: string
|
||||
common_tray_icon?: boolean | null
|
||||
menu_icon?: string | null
|
||||
menu_order?: string[] | null
|
||||
proxy_group_tools_position?: string | null
|
||||
notice_position?: string | null
|
||||
collapse_navbar: boolean | null
|
||||
sysproxy_tray_icon: boolean | null
|
||||
tun_tray_icon: boolean | null
|
||||
@@ -500,11 +485,11 @@ export type IVerge_Serialize = {
|
||||
proxy_auto_config: boolean | null
|
||||
pac_file_content: string | null
|
||||
proxy_host: string | null
|
||||
theme_setting: IVergeTheme_Serialize | null
|
||||
theme_setting: IVergeTheme | null
|
||||
web_ui_list: string[] | null
|
||||
clash_core?: string
|
||||
clash_core?: string | null
|
||||
/** format: {func},{key} */
|
||||
hotkeys?: string[]
|
||||
hotkeys?: string[] | null
|
||||
enable_global_hotkey: boolean | null
|
||||
home_cards: JsonValue | null
|
||||
auto_close_connection: boolean | null
|
||||
@@ -516,7 +501,7 @@ export type IVerge_Serialize = {
|
||||
auto_delay_detection_interval_minutes: number | null
|
||||
enable_builtin_enhanced: boolean | null
|
||||
proxy_layout_column: number | null
|
||||
test_list: IVergeTestItem_Serialize[] | null
|
||||
test_list: IVergeTestItem[] | null
|
||||
/** 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 */
|
||||
auto_log_clean: number | null
|
||||
enable_auto_backup_schedule: boolean | null
|
||||
@@ -528,10 +513,10 @@ export type IVerge_Serialize = {
|
||||
verge_socks_enabled: boolean | null
|
||||
verge_port: number | null
|
||||
verge_http_enabled: boolean | null
|
||||
webdav_url?: string
|
||||
webdav_username?: string
|
||||
webdav_password?: string
|
||||
tray_proxy_groups_display_mode?: string
|
||||
webdav_url?: string | null
|
||||
webdav_username?: string | null
|
||||
webdav_password?: string | null
|
||||
tray_proxy_groups_display_mode?: string | null
|
||||
tray_inline_outbound_modes: boolean | null
|
||||
enable_auto_light_weight_mode: boolean | null
|
||||
/** 自动进入轻量模式的延迟(分钟) */
|
||||
@@ -593,17 +578,7 @@ export type LocalBackupFile = {
|
||||
content_length: number
|
||||
}
|
||||
|
||||
export type NetworkInterfaceView =
|
||||
| NetworkInterfaceView_Serialize
|
||||
| NetworkInterfaceView_Deserialize
|
||||
|
||||
export type NetworkInterfaceView_Deserialize = {
|
||||
name: string
|
||||
addr: InterfaceAddress[]
|
||||
mac_addr?: string | null
|
||||
}
|
||||
|
||||
export type NetworkInterfaceView_Serialize = {
|
||||
export type NetworkInterfaceView = {
|
||||
name: string
|
||||
addr: InterfaceAddress[]
|
||||
mac_addr: string | null
|
||||
@@ -662,20 +637,7 @@ export type PendingFailure = {
|
||||
sequence: number
|
||||
}
|
||||
|
||||
export type PlatformVergeFields =
|
||||
| PlatformVergeFields_Serialize
|
||||
| PlatformVergeFields_Deserialize
|
||||
|
||||
export type PlatformVergeFields_Deserialize = {
|
||||
tray_icon?: string | null
|
||||
verge_redir_port?: number | null
|
||||
verge_redir_enabled?: boolean | null
|
||||
verge_tproxy_port?: number | null
|
||||
verge_tproxy_enabled?: boolean | null
|
||||
enable_tray_speed?: boolean | null
|
||||
}
|
||||
|
||||
export type PlatformVergeFields_Serialize = {
|
||||
export type PlatformVergeFields = {
|
||||
tray_icon?: string | null
|
||||
verge_redir_port?: number | null
|
||||
verge_redir_enabled?: boolean | null
|
||||
@@ -701,7 +663,7 @@ export type PrfItem_Deserialize = {
|
||||
file?: string | null
|
||||
desc?: string | null
|
||||
url?: string | null
|
||||
selected?: PrfSelected_Deserialize[] | null
|
||||
selected?: PrfSelected[] | null
|
||||
extra?: PrfExtra | null
|
||||
updated?: number | null
|
||||
option?: PrfOption_Deserialize | null
|
||||
@@ -714,13 +676,13 @@ export type PrfItem_Serialize = {
|
||||
type: string | null
|
||||
name: string | null
|
||||
file: string | null
|
||||
desc?: string
|
||||
url?: string
|
||||
selected?: PrfSelected_Serialize[]
|
||||
extra?: PrfExtra
|
||||
desc?: string | null
|
||||
url?: string | null
|
||||
selected?: PrfSelected[] | null
|
||||
extra?: PrfExtra | null
|
||||
updated: number | null
|
||||
option?: PrfOption_Serialize
|
||||
home?: string
|
||||
option?: PrfOption_Serialize | null
|
||||
home?: string | null
|
||||
}
|
||||
|
||||
export type PrfOption = PrfOption_Serialize | PrfOption_Deserialize
|
||||
@@ -746,18 +708,18 @@ export type PrfOption_Deserialize = {
|
||||
}
|
||||
|
||||
export type PrfOption_Serialize = {
|
||||
user_agent?: string
|
||||
with_proxy?: boolean
|
||||
self_proxy?: boolean
|
||||
update_interval?: number
|
||||
user_agent?: string | null
|
||||
with_proxy?: boolean | null
|
||||
self_proxy?: boolean | null
|
||||
update_interval?: number | null
|
||||
/**
|
||||
* HTTP request timeout in seconds
|
||||
* default is 60 seconds
|
||||
*/
|
||||
timeout_seconds?: number
|
||||
timeout_seconds?: number | null
|
||||
/** default is `false` */
|
||||
danger_accept_invalid_certs?: boolean
|
||||
allow_auto_update?: boolean
|
||||
danger_accept_invalid_certs?: boolean | null
|
||||
allow_auto_update?: boolean | null
|
||||
merge: string | null
|
||||
script: string | null
|
||||
rules: string | null
|
||||
@@ -765,18 +727,11 @@ export type PrfOption_Serialize = {
|
||||
groups: string | null
|
||||
}
|
||||
|
||||
export type PrfSelected = PrfSelected_Serialize | PrfSelected_Deserialize
|
||||
|
||||
export type PrfSelected_Deserialize = {
|
||||
export type PrfSelected = {
|
||||
name?: string | null
|
||||
now?: string | null
|
||||
}
|
||||
|
||||
export type PrfSelected_Serialize = {
|
||||
name: string | null
|
||||
now: string | null
|
||||
}
|
||||
|
||||
export type ProfileCreate = ProfileCreate_Serialize | ProfileCreate_Deserialize
|
||||
|
||||
export type ProfileCreate_Deserialize = {
|
||||
@@ -806,7 +761,7 @@ export type ProfileDnsSettings_Deserialize = {
|
||||
|
||||
export type ProfileDnsSettings_Serialize = {
|
||||
enabled: boolean
|
||||
confirmation?: string
|
||||
confirmation?: string | null
|
||||
}
|
||||
|
||||
export type ProfilePatch = ProfilePatch_Serialize | ProfilePatch_Deserialize
|
||||
@@ -817,7 +772,7 @@ export type ProfilePatch_Deserialize = {
|
||||
desc?: string | null
|
||||
file?: string | null
|
||||
url?: string | null
|
||||
selected?: PrfSelected_Deserialize[] | null
|
||||
selected?: PrfSelected[] | null
|
||||
extra?: PrfExtra | null
|
||||
updated?: number | null
|
||||
option?: PrfOption_Deserialize | null
|
||||
@@ -829,24 +784,16 @@ export type ProfilePatch_Serialize = {
|
||||
desc: string | null
|
||||
file: string | null
|
||||
url: string | null
|
||||
selected: PrfSelected_Serialize[] | null
|
||||
selected: PrfSelected[] | null
|
||||
extra: PrfExtra | null
|
||||
updated: number | null
|
||||
option: PrfOption_Serialize | null
|
||||
}
|
||||
|
||||
export type ProfileSelection =
|
||||
| ProfileSelection_Serialize
|
||||
| ProfileSelection_Deserialize
|
||||
|
||||
export type ProfileSelection_Deserialize = {
|
||||
export type ProfileSelection = {
|
||||
current?: string | null
|
||||
}
|
||||
|
||||
export type ProfileSelection_Serialize = {
|
||||
current: string | null
|
||||
}
|
||||
|
||||
export type ProfileUpdatePayload = {
|
||||
uid: string
|
||||
}
|
||||
@@ -866,11 +813,11 @@ export type ProxyGroupView =
|
||||
export type ProxyGroupView_Deserialize = {
|
||||
name: string
|
||||
type: string
|
||||
now?: string | null
|
||||
fixed?: string | null
|
||||
hidden?: boolean | null
|
||||
icon?: string | null
|
||||
testUrl?: string | null
|
||||
now: string | null
|
||||
fixed: string | null
|
||||
hidden: boolean | null
|
||||
icon: string | null
|
||||
testUrl: string | null
|
||||
history: DelayHistory[]
|
||||
members: ProxyMemberRef[]
|
||||
} & ProxyCapabilities
|
||||
@@ -878,11 +825,11 @@ export type ProxyGroupView_Deserialize = {
|
||||
export type ProxyGroupView_Serialize = {
|
||||
name: string
|
||||
type: string
|
||||
now?: string
|
||||
fixed?: string
|
||||
hidden?: boolean
|
||||
icon?: string
|
||||
testUrl?: string
|
||||
now?: string | null
|
||||
fixed?: string | null
|
||||
hidden?: boolean | null
|
||||
icon?: string | null
|
||||
testUrl?: string | null
|
||||
history: DelayHistory[]
|
||||
members: ProxyMemberRef[]
|
||||
} & ProxyCapabilities
|
||||
@@ -908,10 +855,10 @@ export type ProxyNodeView_Deserialize = {
|
||||
name: string
|
||||
type: string
|
||||
history: DelayHistory[]
|
||||
id?: string | null
|
||||
hidden?: boolean | null
|
||||
icon?: string | null
|
||||
testUrl?: string | null
|
||||
id: string | null
|
||||
hidden: boolean | null
|
||||
icon: string | null
|
||||
testUrl: string | null
|
||||
source: ProxyNodeSource
|
||||
} & ProxyCapabilities
|
||||
|
||||
@@ -920,10 +867,10 @@ export type ProxyNodeView_Serialize = {
|
||||
name: string
|
||||
type: string
|
||||
history: DelayHistory[]
|
||||
id?: string
|
||||
hidden?: boolean
|
||||
icon?: string
|
||||
testUrl?: string
|
||||
id?: string | null
|
||||
hidden?: boolean | null
|
||||
icon?: string | null
|
||||
testUrl?: string | null
|
||||
source: ProxyNodeSource
|
||||
} & ProxyCapabilities
|
||||
|
||||
@@ -944,16 +891,16 @@ export type ProxyProviderView =
|
||||
export type ProxyProviderView_Deserialize = {
|
||||
name: string
|
||||
vehicleType: ProxyProviderVehicleType
|
||||
updatedAt?: string | null
|
||||
subscriptionInfo?: ProxySubscriptionInfo | null
|
||||
updatedAt: string | null
|
||||
subscriptionInfo: ProxySubscriptionInfo | null
|
||||
proxyRecordIds: string[]
|
||||
}
|
||||
|
||||
export type ProxyProviderView_Serialize = {
|
||||
name: string
|
||||
vehicleType: ProxyProviderVehicleType
|
||||
updatedAt?: string
|
||||
subscriptionInfo?: ProxySubscriptionInfo
|
||||
updatedAt?: string | null
|
||||
subscriptionInfo?: ProxySubscriptionInfo | null
|
||||
proxyRecordIds: string[]
|
||||
}
|
||||
|
||||
@@ -974,8 +921,8 @@ export type ProxyViewV1_Deserialize = {
|
||||
schemaVersion: number
|
||||
orderSource: ProxyViewOrderSource
|
||||
providerState: ProxyViewProviderState
|
||||
global?: ProxyGroupView_Deserialize | null
|
||||
direct?: string | null
|
||||
global: ProxyGroupView_Deserialize | null
|
||||
direct: string | null
|
||||
groups: ProxyGroupView_Deserialize[]
|
||||
records: { [key in string]: ProxyNodeView_Deserialize }
|
||||
standalone: string[]
|
||||
@@ -994,22 +941,7 @@ export type ProxyViewV1_Serialize = {
|
||||
providers: ProxyProviderView_Serialize[]
|
||||
}
|
||||
|
||||
export type RunStateView = RunStateView_Serialize | RunStateView_Deserialize
|
||||
|
||||
export type RunStateView_Deserialize = {
|
||||
mode: RunningMode
|
||||
service: ServiceHealthKind
|
||||
serviceUnavailableReason?: string | null
|
||||
pendingAction?: PendingAction | null
|
||||
sidecarAllowed: boolean
|
||||
isAdmin: boolean
|
||||
opInFlight: boolean
|
||||
serviceUsable: boolean
|
||||
tunCapable: boolean
|
||||
serviceNeedsAttention: boolean
|
||||
}
|
||||
|
||||
export type RunStateView_Serialize = {
|
||||
export type RunStateView = {
|
||||
mode: RunningMode
|
||||
service: ServiceHealthKind
|
||||
serviceUnavailableReason: string | null
|
||||
@@ -1048,18 +980,7 @@ export type ServiceInstallOutcome =
|
||||
| { status: 'sidecar'; reason: string }
|
||||
|
||||
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||
export type SidecarFailureSnapshot =
|
||||
| SidecarFailureSnapshot_Serialize
|
||||
| SidecarFailureSnapshot_Deserialize
|
||||
|
||||
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||
export type SidecarFailureSnapshot_Deserialize = {
|
||||
revision: number
|
||||
detail?: string | null
|
||||
}
|
||||
|
||||
/** A removal has a revision too, so a delayed read cannot restore a resolved failure. */
|
||||
export type SidecarFailureSnapshot_Serialize = {
|
||||
export type SidecarFailureSnapshot = {
|
||||
revision: number
|
||||
detail: string | null
|
||||
}
|
||||
@@ -1080,16 +1001,7 @@ export type ToggleableProxyPort = {
|
||||
port: number
|
||||
}
|
||||
|
||||
export type UnlockItem = UnlockItem_Serialize | UnlockItem_Deserialize
|
||||
|
||||
export type UnlockItem_Deserialize = {
|
||||
name: string
|
||||
status: string
|
||||
region?: string | null
|
||||
check_time?: string | null
|
||||
}
|
||||
|
||||
export type UnlockItem_Serialize = {
|
||||
export type UnlockItem = {
|
||||
name: string
|
||||
status: string
|
||||
region: string | null
|
||||
@@ -1115,11 +1027,7 @@ export type ValidationOutcome =
|
||||
|
||||
export type ValidationSkipReason = 'exiting' | 'debounced'
|
||||
|
||||
export type VergeEventPayloads =
|
||||
| VergeEventPayloads_Serialize
|
||||
| VergeEventPayloads_Deserialize
|
||||
|
||||
export type VergeEventPayloads_Deserialize = {
|
||||
export type VergeEventPayloads = {
|
||||
'verge://refresh-clash-config': null
|
||||
'verge://refresh-verge-config': null
|
||||
'verge://refresh-profiles': null
|
||||
@@ -1129,21 +1037,7 @@ export type VergeEventPayloads_Deserialize = {
|
||||
'verge://timer-updated': string
|
||||
'profile-update-started': ProfileUpdatePayload
|
||||
'profile-update-completed': ProfileUpdatePayload
|
||||
'verge://run-state-changed': RunStateView_Deserialize
|
||||
'verge://pending-failures-changed': null
|
||||
}
|
||||
|
||||
export type VergeEventPayloads_Serialize = {
|
||||
'verge://refresh-clash-config': null
|
||||
'verge://refresh-verge-config': null
|
||||
'verge://refresh-profiles': null
|
||||
'verge://refresh-proxy-config': null
|
||||
'verge://notice-message': [NoticeStatus, string]
|
||||
'profile-changed': string
|
||||
'verge://timer-updated': string
|
||||
'profile-update-started': ProfileUpdatePayload
|
||||
'profile-update-completed': ProfileUpdatePayload
|
||||
'verge://run-state-changed': RunStateView_Serialize
|
||||
'verge://run-state-changed': RunStateView
|
||||
'verge://pending-failures-changed': null
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
|
||||
export type {
|
||||
RunningMode,
|
||||
RunStateView_Serialize as RunState,
|
||||
RunStateView as RunState,
|
||||
FailedOperation,
|
||||
PendingFailure,
|
||||
SidecarFailureSnapshot,
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export type {
|
||||
NoticeStatus,
|
||||
VergeEventPayloads_Serialize as VergeEventPayloads,
|
||||
} from './bindings'
|
||||
export type { NoticeStatus, VergeEventPayloads } from './bindings'
|
||||
|
||||
@@ -5,12 +5,16 @@ import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import svgr from 'vite-plugin-svgr'
|
||||
|
||||
import { reactDiagnostics } from './scripts/react/vite.mts'
|
||||
|
||||
export default defineConfig({
|
||||
root: 'src',
|
||||
cacheDir: path.resolve('node_modules/.vite/default'),
|
||||
server: { port: 3000 },
|
||||
plugins: [
|
||||
svgr(),
|
||||
react(),
|
||||
...reactDiagnostics(process.env.CVR_REACT_DIAGNOSTICS),
|
||||
legacy({
|
||||
modernTargets: ['edge>=109', 'safari>=14'],
|
||||
renderLegacyChunks: false,
|
||||
|
||||
Reference in New Issue
Block a user