mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-12 05:32:09 +08:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e3be5a27f | ||
|
|
4adac9e4b6 | ||
|
|
2c60f55f45 | ||
|
|
b8c13c0370 | ||
|
|
f86db4b344 | ||
|
|
389dddf512 | ||
|
|
304a65fc36 | ||
|
|
9d0bee4af9 | ||
|
|
1e4f709766 | ||
|
|
aae5dc90ba | ||
|
|
05a6e9a64f | ||
|
|
7764289ba5 | ||
|
|
417f0b4c32 |
@@ -4,7 +4,7 @@
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:2": {
|
||||
"version": "26.13.0",
|
||||
"version": "26.13.1",
|
||||
"pnpmVersion": "12.18.0"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"version": "v9.0.0",
|
||||
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
|
||||
},
|
||||
"github/gh-aw-actions/setup@v1.0.1": {
|
||||
"github/gh-aw-actions/setup@v1.2.2": {
|
||||
"repo": "github/gh-aw-actions/setup",
|
||||
"version": "v1.0.1",
|
||||
"sha": "221ba4f4319de6b8e809d1a15528b3ae6a818aa5"
|
||||
"version": "v1.2.2",
|
||||
"sha": "53d0749e19424a918acd82b4d140264ce48f2f14"
|
||||
}
|
||||
},
|
||||
"containers": {
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -395,7 +395,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -479,7 +479,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6.1.1
|
||||
name: Install pnpm
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
@@ -31,6 +31,27 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install gh-aw extension
|
||||
uses: github/gh-aw-actions/setup-cli@406c7719e31d7fe354224d4bb8fe3d4aeb47da1c # v1.2.1
|
||||
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
|
||||
with:
|
||||
version: v1.0.1
|
||||
version: v1.2.2
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
- name: Install Linux build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
- name: Install frontend dependencies
|
||||
env:
|
||||
HUSKY: 0
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
+282
-103
File diff suppressed because one or more lines are too long
@@ -33,6 +33,7 @@ max-daily-ai-credits: 5000
|
||||
permissions:
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
|
||||
pre-agent-steps:
|
||||
- name: Make Copilot quota exhaustion non-blocking
|
||||
|
||||
Generated
+288
-107
File diff suppressed because one or more lines are too long
@@ -30,6 +30,7 @@ permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
steps:
|
||||
- name: Load review rules from the base revision
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
if: github.event.action != 'closed'
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
- name: Validate source and publish the approved snapshot
|
||||
id: source
|
||||
env:
|
||||
@@ -231,7 +231,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -461,7 +461,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -542,7 +542,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
@@ -565,7 +565,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -593,7 +593,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -661,7 +661,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
+282
-103
File diff suppressed because one or more lines are too long
@@ -28,6 +28,7 @@ permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
copilot-requests: none
|
||||
max-ai-credits: 60
|
||||
max-turns: 12
|
||||
max-daily-ai-credits: 500
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '26.13.0'
|
||||
node-version: '26.13.1'
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
nodejs 26.13.0
|
||||
nodejs 26.13.1
|
||||
pnpm 12.18.0
|
||||
rust 1.99.1
|
||||
|
||||
Generated
+70
-14
@@ -159,9 +159,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.3"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
|
||||
checksum = "84a22b5d51209f6de83d37a0b33d320acb8da88823e42a94c075140708f032a5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
@@ -1120,7 +1120,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"reqwest 0.13.5",
|
||||
"reqwest_dav",
|
||||
"rfd",
|
||||
"rfd 0.18.0",
|
||||
"runas",
|
||||
"rustls",
|
||||
"scopeguard",
|
||||
@@ -2404,9 +2404,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
checksum = "626d4928326b646e70f65ed36fc7cf9f206bdb68a0019ee7c6b4709da4e084c4"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.9.1",
|
||||
@@ -3241,9 +3241,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
|
||||
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -4070,9 +4070,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust2"
|
||||
version = "0.24.0"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490750560fa7851fbbf5d3ab1a8750f09350ed361ed8aed85b1e1e40587812ee"
|
||||
checksum = "ef1d73c59a56771343712b22ea5615092c612c16ba291c0979e3105dfeec4890"
|
||||
dependencies = [
|
||||
"sha2 0.11.1",
|
||||
]
|
||||
@@ -4538,6 +4538,7 @@ dependencies = [
|
||||
"objc2-core-video",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-uniform-type-identifiers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4759,6 +4760,16 @@ dependencies = [
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-uniform-type-identifiers"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.3.2"
|
||||
@@ -5302,6 +5313,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pollster"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336"
|
||||
|
||||
[[package]]
|
||||
name = "polonius-the-crab"
|
||||
version = "0.5.0"
|
||||
@@ -5934,6 +5951,31 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4b9d4857b10297ffb0f64cae711e983a7aeaee561848d56d429d3858bd8ed93"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-uniform-type-identifiers",
|
||||
"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"
|
||||
@@ -7427,7 +7469,7 @@ checksum = "ce0fb9c2b6d34a9720627ed930ebc8af26f61e648695aecaeb03d5c33c5896f5"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"rfd 0.17.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -10032,6 +10074,20 @@ name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
@@ -10070,9 +10126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "9.3.0"
|
||||
version = "9.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426"
|
||||
checksum = "0700e6c427aff64acdbf02e85fbfd946c2ee12319d1af013092f3a2178914ff8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
@@ -10100,9 +10156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
|
||||
@@ -24,10 +24,13 @@
|
||||
<details>
|
||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||
|
||||
- 修复应用退出时无条件重写 `clash.yaml`、`verge.yaml` 和 `profiles.yaml` 导致文件创建时间丢失的问题
|
||||
|
||||
- 修复检测更新后又发布新版时,应用内更新失败
|
||||
- 修复订阅响应过大导致内存占用过高,限制单次下载为 4 MiB
|
||||
- 修复全局模式默认停在 `DIRECT` 或 `REJECT` 导致流量未经代理,现自动切换到第一个策略组
|
||||
- 修复订阅中未加引号的 REALITY `short-id`(如 `1234e123`)被误读为数字,导致订阅无法启用
|
||||
- 修复订阅中唯一的代理组名为 `GLOBAL` 或没有代理组时,规则模式下代理页面显示「未获取到代理信息」
|
||||
|
||||
**🍎 macOS**
|
||||
|
||||
@@ -39,6 +42,7 @@
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 新增连接页面按网络协议(TCP/UDP)筛选连接
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -74,12 +74,16 @@ impl<T: Clone> Draft<T> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Commits and clears the draft.
|
||||
/// Commits and clears the draft, reporting whether one was staged.
|
||||
#[inline]
|
||||
pub fn apply(&self) {
|
||||
pub fn apply(&self) -> bool {
|
||||
let mut guard = self.inner.data.lock();
|
||||
if let Some(d) = guard.1.take() {
|
||||
guard.0 = d;
|
||||
match guard.1.take() {
|
||||
Some(d) => {
|
||||
guard.0 = d;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -67,7 +67,7 @@
|
||||
"dayjs": "1.11.23",
|
||||
"foxact": "^0.3.12",
|
||||
"foxts": "^5.9.1",
|
||||
"i18next": "^26.4.2",
|
||||
"i18next": "^27.0.0",
|
||||
"js-yaml": "^5.4.2",
|
||||
"lodash-es": "^4.18.1",
|
||||
"meta-json-schema": "^1.19.31",
|
||||
@@ -79,7 +79,7 @@
|
||||
"react-dom": "19.3.1",
|
||||
"react-error-boundary": "6.1.6",
|
||||
"react-hook-form": "^7.88.0",
|
||||
"react-i18next": "17.1.13",
|
||||
"react-i18next": "18.0.1",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^8.4.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -121,7 +121,7 @@
|
||||
"https-proxy-agent": "^9.1.0",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.7.0",
|
||||
"knip": "6.44.1",
|
||||
"knip": "6.44.2",
|
||||
"lint-staged": "^17.5.1",
|
||||
"sass": "^1.104.1",
|
||||
"tar": "^7.5.22",
|
||||
@@ -133,7 +133,7 @@
|
||||
"vite-plugin-svgr": "^5.2.0",
|
||||
"vitest": "^5.0.1",
|
||||
"webdriverio": "10.2.0",
|
||||
"ws": "8.22.1"
|
||||
"ws": "8.22.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,mjs}": [
|
||||
|
||||
Generated
+110
-118
@@ -239,8 +239,8 @@ importers:
|
||||
specifier: ^5.9.1
|
||||
version: 5.9.1
|
||||
i18next:
|
||||
specifier: ^26.4.2
|
||||
version: 26.4.2(typescript@6.0.3)
|
||||
specifier: ^27.0.0
|
||||
version: 27.0.1(typescript@6.0.3)
|
||||
js-yaml:
|
||||
specifier: ^5.4.2
|
||||
version: 5.4.2
|
||||
@@ -275,8 +275,8 @@ importers:
|
||||
specifier: ^7.88.0
|
||||
version: 7.88.0(react@19.3.1)
|
||||
react-i18next:
|
||||
specifier: 17.1.13
|
||||
version: 17.1.13(i18next@26.4.2(typescript@6.0.3))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)
|
||||
specifier: 18.0.1
|
||||
version: 18.0.1(@types/react@19.3.0)(i18next@27.0.1(typescript@6.0.3))(react@19.3.1)(typescript@6.0.3)
|
||||
react-markdown:
|
||||
specifier: 10.1.0
|
||||
version: 10.1.0(@types/react@19.3.0)(react@19.3.1)(supports-color@7.2.0)
|
||||
@@ -396,8 +396,8 @@ importers:
|
||||
specifier: ^2.7.0
|
||||
version: 2.7.0
|
||||
knip:
|
||||
specifier: 6.44.1
|
||||
version: 6.44.1
|
||||
specifier: 6.44.2
|
||||
version: 6.44.2
|
||||
lint-staged:
|
||||
specifier: ^17.5.1
|
||||
version: 17.5.1
|
||||
@@ -432,8 +432,8 @@ importers:
|
||||
specifier: 10.2.0
|
||||
version: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0)
|
||||
ws:
|
||||
specifier: 8.22.1
|
||||
version: 8.22.1
|
||||
specifier: 8.22.2
|
||||
version: 8.22.2
|
||||
|
||||
packages:
|
||||
|
||||
@@ -1595,131 +1595,128 @@ packages:
|
||||
'@octokit/types@18.0.0':
|
||||
resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==}
|
||||
|
||||
'@oxc-parser/binding-android-arm-eabi@0.155.0':
|
||||
resolution: {integrity: sha512-A6xv7hDtnTm+xlZ7utn6rRQpRMJHRhVr3gjRGqlx0RUX84mNk0pAsy6YN/wWhuOTkaZhRrrzaLxf0NfbH77j2w==}
|
||||
'@oxc-parser/binding-android-arm-eabi@0.156.0':
|
||||
resolution: {integrity: sha512-RMq7DKNzEp1T51QKNLTn0NryNEt2IYfh7mZBlIdi2Ju52ltOEDMeIXv0XBpA6dL7j7ChjlcovtGcc1Jv4qMipg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxc-parser/binding-android-arm64@0.155.0':
|
||||
resolution: {integrity: sha512-T1wWwH9/JI5g7Z4kEQwDOwhToxdDhRyOTklH+uuNpJ16m3MZFmGl+l2rKc0NHQdbVRG2LTOBsNjpW3h1BWytPA==}
|
||||
'@oxc-parser/binding-android-arm64@0.156.0':
|
||||
resolution: {integrity: sha512-mwkWKhOBpmh8fWc3m4MiLsmEAtx0YuHmvyo9ff557cnb3QZ2kt2eauSz75wCGIpiZG3gUsJ4WwzEjm4R4/BHig==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxc-parser/binding-darwin-arm64@0.155.0':
|
||||
resolution: {integrity: sha512-ZoRB3qJxJ8DWA41jHja+YeB0lLfjBy4T+Kr15vnN1k65X3zbJt3rPZftEwN4Ju4tmA4K/T9dWWD7Z8ewQLnrVA==}
|
||||
'@oxc-parser/binding-darwin-arm64@0.156.0':
|
||||
resolution: {integrity: sha512-6pGR5fUszM9M4eBBapqpGjCRJolFwuCldx8bRQjqXyM09cQysBTWVF2Ttg4UNJVxYSaw5RRINbveCOOCrsuGvA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxc-parser/binding-darwin-x64@0.155.0':
|
||||
resolution: {integrity: sha512-vxtL86BtM+IgrpejA86nbTk5j0HKTIMeuaJ1fQciw6p9RfavPtIPjI7gN34owap0yG9txXBaAWpGdxokNwdqIw==}
|
||||
'@oxc-parser/binding-darwin-x64@0.156.0':
|
||||
resolution: {integrity: sha512-M9QghL7uCAqMKjYJv1rDGblfEvkeQu3QzTPd7Uwo16gG3uO7m9ikVQ9OL8FZTnKzU4vbCEby6kTlEkqN+JeEMA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxc-parser/binding-freebsd-x64@0.155.0':
|
||||
resolution: {integrity: sha512-tN6Ru5e0UVnWaIuiH8jEwnvxuxg7FvhWwSd3oe5oDhOD4ObsSr9/N6WfHLjilUWlPPj65ptwrSDH0Y7lzwTzmw==}
|
||||
'@oxc-parser/binding-freebsd-x64@0.156.0':
|
||||
resolution: {integrity: sha512-lX0ANxSwAOrpOT7NHB+YsrBqDJ8sOVon5m7GS9eQtkuUeNzQMMGpFsJLsStawTbrdwOF5KDVxgPWcKmt/DIZUQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf@0.155.0':
|
||||
resolution: {integrity: sha512-h4ozA5n6L6lwolsz9iwunEhKWnIVbmFRots5QxEix0H6nJ5ICpuDa7PuShuhfpIDFe3+Qvyscioyiv+9g+4Syg==}
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf@0.156.0':
|
||||
resolution: {integrity: sha512-iPgYpasRcqk7X3TGncQg7B+6RotDvG8weug0UrgF+FXLf4KmaNDyT91BKONlqbHcNgK9Nryp7AEZGuQn32fP6g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxc-parser/binding-linux-arm-musleabihf@0.155.0':
|
||||
resolution: {integrity: sha512-058/+LardpfuUlsKIvBGk/CYoakEVcMgqYx8GgyUZeOxFWTojYLlCc1Gb60/y21v3esDhHidgiY/Zbq9LpBhaQ==}
|
||||
'@oxc-parser/binding-linux-arm-musleabihf@0.156.0':
|
||||
resolution: {integrity: sha512-sJqJDEZevtkEfoxrXfaRTkSt6Itckr549cIfBbs11UgXv8bFyqQsXTQa6iKm8mTiWYmCDDFJf7TLnqxcbqzPOw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.155.0':
|
||||
resolution: {integrity: sha512-nrOt2Y3xIAzu+lSPzH0864ieO+iCCXgoo9qFlGANE8Xh0cQ+RP8Wstfp+/wXN2YCyb98cAGzX0VruKYw7it/jw==}
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.156.0':
|
||||
resolution: {integrity: sha512-mrQs5OdRiqKskRuWEORPKT6YGIFhrpVvCvL9hqVaIh60EzgR4qjPvSsV6hWZcOfyWHBTbdEvfnDJPh/eQlxPRQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.155.0':
|
||||
resolution: {integrity: sha512-KYAayyN5hQbJPtGKtRxlyc8XYmyntOplyXSQzTDrmbz7l/GCrzQbSswvyJFBTH1LGtsZrbXOAfN52i/Kz6kLKg==}
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.156.0':
|
||||
resolution: {integrity: sha512-ZtPqgCU3+wpU7dvX7YbaEiw71r0UdguzuPhcx4PIEZSs4XFqc6F5wXCtNdb6M7m3DMErlxrwosryoXrzYOY/Gg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-ppc64-gnu@0.155.0':
|
||||
resolution: {integrity: sha512-CSuSY5hfjtsBnHXydOj8jiNfS1qHbdPZuPz1YL1cSgK7VmNYP+QlZIbG0BBYYBgX/vYEgZwbG9mBdW4YairZ5w==}
|
||||
'@oxc-parser/binding-linux-ppc64-gnu@0.156.0':
|
||||
resolution: {integrity: sha512-ZuGRT/adReZZJn3PYNneb1HQIOH0iIZ2fzFgC1/XJYpMQ5KsRKmWIEzy3wEFsUwIOTomCmnmTZ/zbFYo9z9VeA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.155.0':
|
||||
resolution: {integrity: sha512-7KCfOQm+Nz2N8trAXbQkal4TQ5756WVEmhm35y816YHvyFnEfVsICsiV5k0pEszNf/fRm4cm2i6i18SJ6/iCWg==}
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.156.0':
|
||||
resolution: {integrity: sha512-T+iWCeAOoEAJpHqr6/pogHIv98g9p8Ctccysm/h962hG4ayX5OL2c0Ih4oYe+JuFndLXy/OASvwMlTPe76sIiw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-musl@0.155.0':
|
||||
resolution: {integrity: sha512-JSlhgoK+/g1TGJI5NuADx2+6syhxnQNLCWLXyO5QLFyetomcr/vIxSdiNlmyAv963YXUzCuwAwRG6npZ5xyJ1A==}
|
||||
'@oxc-parser/binding-linux-riscv64-musl@0.156.0':
|
||||
resolution: {integrity: sha512-wL55bI2kVKfwdxScbw60zVgzAqbQbmE5zpSPWPZoZ9RSG8egm6ITE/UWE5mDWlglxz1QElJCcUbpaJCtHD11BQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.155.0':
|
||||
resolution: {integrity: sha512-WFIX+dPokWp64mi434Md3U3lJaTwNxAphvGjpltoyQjGUsdXavOIRrnp9dOS34Hd75HOAwDxAI+YwWOwqmJbtg==}
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.156.0':
|
||||
resolution: {integrity: sha512-+D+JilhLqwrDLODYEl4qtovVwgxKALb23FkAWBiPvgmUWYgjCV83KKuwwxZJM8GCdKlNA1vt3iKtow8HUFjXtg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.155.0':
|
||||
resolution: {integrity: sha512-g6xSDC/JpbnVOz5uafMGyK3txBVRFMQTx8opv/yHXGxqV3ZMVl3bf7W8XrSnF68dmy3gM4dgReNryuE53+WkeQ==}
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.156.0':
|
||||
resolution: {integrity: sha512-jRfwpN4xKrsEpC+SgrUPY5k0mINYZn7aLrt22I3uQFoumiSgHgsLYgS9f84ntbkvSydH4HiwGuDa2b1XCHaJzw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-parser/binding-linux-x64-musl@0.155.0':
|
||||
resolution: {integrity: sha512-6/4xlxKlZJWrQ0uwDFPc0H9+xLu7068CKhX3vIDW3SbvP/lVgnVuT5v6dyugeWskWpuxqF/WJ4WKwhxjU3pG2w==}
|
||||
'@oxc-parser/binding-linux-x64-musl@0.156.0':
|
||||
resolution: {integrity: sha512-P/ajnypgLmxmuRRyCIZcp2o4lOwmCaXYH5+eqDTcCW+MCTI+csKSw/Zc6GB67jZoVa4ey3q1jXEt7S60adBEMQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-parser/binding-openharmony-arm64@0.155.0':
|
||||
resolution: {integrity: sha512-tltklL0J0XjDxIRTBXWOBhvc2mitcmES45pIxhwDWfxRyqz7Vkeu0NekTZEIxGE4f/lXgq3vZcjjffteQOVorg==}
|
||||
'@oxc-parser/binding-openharmony-arm64@0.156.0':
|
||||
resolution: {integrity: sha512-WSN6rQZZc9KqgZ4lMfjVDvqKhVOCjey/YEZIPisGXmwBqwgWOzru207dyiH50LHcf1n9LO2oceGk/id6RL9v0g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxc-parser/binding-win32-arm64-msvc@0.155.0':
|
||||
resolution: {integrity: sha512-alAb9TzjatUEsOeoncAziUnfdTDxfyswhalFZ9sC6plUgATIUx2NIZ0+bMn6aJ28AduS1OpG/OAisr/YISn4Ig==}
|
||||
'@oxc-parser/binding-win32-arm64-msvc@0.156.0':
|
||||
resolution: {integrity: sha512-l21Tz6tjraGBC3JefVeJZ9pojD0fCiB62PydAv+i9nrqJX7z/pDmyDNYLamDr/PRcQ4UQbFwkSnz7NKBB9UMLw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxc-parser/binding-win32-ia32-msvc@0.155.0':
|
||||
resolution: {integrity: sha512-yc5cbSfcGmRv/KpxDFuyk8adEBaWd/9HilWvBbQ4bFbus1V2PUh2mGB2Cfq9rqZTqLeiMT8x0skV+LKp9hOMgQ==}
|
||||
'@oxc-parser/binding-win32-ia32-msvc@0.156.0':
|
||||
resolution: {integrity: sha512-CUq0ZFl/RODqPiIgfNNSamkpTj5x8AjBTkR/y1DNVIQiTGnmWqjHcn3o08ZW3q/LgkI4g9G9g6UirmmQFvuOZw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxc-parser/binding-win32-x64-msvc@0.155.0':
|
||||
resolution: {integrity: sha512-8heq6jGq+rfpTDRDjoQiBHw0maQZf3IXkS99pdHQZPoTL5kyF5qyPS0RidUlSdnWDIqxXb5nux7GBD9cT+imUw==}
|
||||
'@oxc-parser/binding-win32-x64-msvc@0.156.0':
|
||||
resolution: {integrity: sha512-dcpx5Y1x2v/cs7U/q3SZMvmxfyXjVvqC86jMuw2ZZUbHDd7W2cW7uSMQfg3GLVaE68Su3H9xd/Pc4v6QNyeSRg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxc-project/types@0.155.0':
|
||||
resolution: {integrity: sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==}
|
||||
|
||||
'@oxc-project/types@0.156.0':
|
||||
resolution: {integrity: sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==}
|
||||
|
||||
@@ -3692,8 +3689,9 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
i18next@26.4.2:
|
||||
resolution: {integrity: sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==}
|
||||
i18next@27.0.1:
|
||||
resolution: {integrity: sha512-KaE5ozP8UUeVpOJIwMVbbnpRKpyYQuxd9ZbseJoDzezE3nvLnZZQn20pkzyIJwPI2Uet/Qs3WzPTG812f6F/Eg==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
@@ -3858,8 +3856,8 @@ packages:
|
||||
keyv@5.6.0:
|
||||
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
|
||||
|
||||
knip@6.44.1:
|
||||
resolution: {integrity: sha512-Hbd8nvkIsNgFfXgC7Jsb5LHv7jj6VwA3tmfHd53XPX2z9Vc9RnAZqV0BIiP4OrtYqKwCoLseFHQZerb1bNlbSw==}
|
||||
knip@6.44.2:
|
||||
resolution: {integrity: sha512-6URO7pnqtOnyvSsXfW6+8C9j1lfcicxUCysXO0RgMtHTZ96HqCXh29op8iabeQW5JeIc8dJWwzSdJVWDokeScQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -4230,8 +4228,8 @@ packages:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
oxc-parser@0.155.0:
|
||||
resolution: {integrity: sha512-C/ReSf1JUFukK/zvk6jhcNibSssvas5+ktIzK7fbKVd3CedWe59C3QwHEOrEZj/4Rt9+xg0Jeis/Jv1F/KM1mw==}
|
||||
oxc-parser@0.156.0:
|
||||
resolution: {integrity: sha512-7JhjgOfZjrIFJH5TlHrpRua864Si5GvwrPr5+eIRCY2sszhKXo6pyhP0HQKOGTtfO4UowwZ72XTGba0+lZphOQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
|
||||
oxc-resolver@11.24.2:
|
||||
@@ -4399,18 +4397,16 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-i18next@17.1.13:
|
||||
resolution: {integrity: sha512-ZxhOSlyKSKPtbUNPLwYMmE3w3bhoc0jaZJZ6bc7wGOT6cLbrbs//6m01HxCLtFdSjHvFpwFwoPhxxzIlHSs9cw==}
|
||||
react-i18next@18.0.1:
|
||||
resolution: {integrity: sha512-kPWXI0E/lrPj3+/umcKNzYZVnWmEedLiamMzDIHfbs+5ANf6yzvlZsjKou8f7kaGkfMMDLIZBejV+4aSN1CyBw==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
i18next: '>= 26.2.0'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
'@types/react': '*'
|
||||
i18next: '>= 27.0.1'
|
||||
react: '>= 18.0.0'
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
'@types/react':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
@@ -5067,8 +5063,8 @@ packages:
|
||||
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ws@8.22.1:
|
||||
resolution: {integrity: sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==}
|
||||
ws@8.22.2:
|
||||
resolution: {integrity: sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -6486,65 +6482,63 @@ snapshots:
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 29.0.1
|
||||
|
||||
'@oxc-parser/binding-android-arm-eabi@0.155.0':
|
||||
'@oxc-parser/binding-android-arm-eabi@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-android-arm64@0.155.0':
|
||||
'@oxc-parser/binding-android-arm64@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-darwin-arm64@0.155.0':
|
||||
'@oxc-parser/binding-darwin-arm64@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-darwin-x64@0.155.0':
|
||||
'@oxc-parser/binding-darwin-x64@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-freebsd-x64@0.155.0':
|
||||
'@oxc-parser/binding-freebsd-x64@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf@0.155.0':
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-arm-musleabihf@0.155.0':
|
||||
'@oxc-parser/binding-linux-arm-musleabihf@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.155.0':
|
||||
'@oxc-parser/binding-linux-arm64-gnu@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.155.0':
|
||||
'@oxc-parser/binding-linux-arm64-musl@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-ppc64-gnu@0.155.0':
|
||||
'@oxc-parser/binding-linux-ppc64-gnu@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.155.0':
|
||||
'@oxc-parser/binding-linux-riscv64-gnu@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-riscv64-musl@0.155.0':
|
||||
'@oxc-parser/binding-linux-riscv64-musl@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.155.0':
|
||||
'@oxc-parser/binding-linux-s390x-gnu@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.155.0':
|
||||
'@oxc-parser/binding-linux-x64-gnu@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-linux-x64-musl@0.155.0':
|
||||
'@oxc-parser/binding-linux-x64-musl@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-openharmony-arm64@0.155.0':
|
||||
'@oxc-parser/binding-openharmony-arm64@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-win32-arm64-msvc@0.155.0':
|
||||
'@oxc-parser/binding-win32-arm64-msvc@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-win32-ia32-msvc@0.155.0':
|
||||
'@oxc-parser/binding-win32-ia32-msvc@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-parser/binding-win32-x64-msvc@0.155.0':
|
||||
'@oxc-parser/binding-win32-x64-msvc@0.156.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-project/types@0.155.0': {}
|
||||
|
||||
'@oxc-project/types@0.156.0': {}
|
||||
|
||||
'@oxc-resolver/binding-android-arm-eabi@11.24.2':
|
||||
@@ -8538,7 +8532,7 @@ snapshots:
|
||||
|
||||
husky@9.1.7: {}
|
||||
|
||||
i18next@26.4.2(typescript@6.0.3):
|
||||
i18next@27.0.1(typescript@6.0.3):
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
@@ -8690,12 +8684,12 @@ snapshots:
|
||||
dependencies:
|
||||
'@keyv/serialize': 1.1.1
|
||||
|
||||
knip@6.44.1:
|
||||
knip@6.44.2:
|
||||
dependencies:
|
||||
formatly: 0.11.4
|
||||
get-tsconfig: 4.14.3
|
||||
jiti: 2.7.0
|
||||
oxc-parser: 0.155.0
|
||||
oxc-parser: 0.156.0
|
||||
oxc-resolver: 11.24.2
|
||||
picomatch: 4.0.7
|
||||
smol-toml: 1.9.1
|
||||
@@ -9176,29 +9170,29 @@ snapshots:
|
||||
type-check: 0.4.0
|
||||
word-wrap: 1.2.5
|
||||
|
||||
oxc-parser@0.155.0:
|
||||
oxc-parser@0.156.0:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.155.0
|
||||
'@oxc-project/types': 0.156.0
|
||||
optionalDependencies:
|
||||
'@oxc-parser/binding-android-arm-eabi': 0.155.0
|
||||
'@oxc-parser/binding-android-arm64': 0.155.0
|
||||
'@oxc-parser/binding-darwin-arm64': 0.155.0
|
||||
'@oxc-parser/binding-darwin-x64': 0.155.0
|
||||
'@oxc-parser/binding-freebsd-x64': 0.155.0
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf': 0.155.0
|
||||
'@oxc-parser/binding-linux-arm-musleabihf': 0.155.0
|
||||
'@oxc-parser/binding-linux-arm64-gnu': 0.155.0
|
||||
'@oxc-parser/binding-linux-arm64-musl': 0.155.0
|
||||
'@oxc-parser/binding-linux-ppc64-gnu': 0.155.0
|
||||
'@oxc-parser/binding-linux-riscv64-gnu': 0.155.0
|
||||
'@oxc-parser/binding-linux-riscv64-musl': 0.155.0
|
||||
'@oxc-parser/binding-linux-s390x-gnu': 0.155.0
|
||||
'@oxc-parser/binding-linux-x64-gnu': 0.155.0
|
||||
'@oxc-parser/binding-linux-x64-musl': 0.155.0
|
||||
'@oxc-parser/binding-openharmony-arm64': 0.155.0
|
||||
'@oxc-parser/binding-win32-arm64-msvc': 0.155.0
|
||||
'@oxc-parser/binding-win32-ia32-msvc': 0.155.0
|
||||
'@oxc-parser/binding-win32-x64-msvc': 0.155.0
|
||||
'@oxc-parser/binding-android-arm-eabi': 0.156.0
|
||||
'@oxc-parser/binding-android-arm64': 0.156.0
|
||||
'@oxc-parser/binding-darwin-arm64': 0.156.0
|
||||
'@oxc-parser/binding-darwin-x64': 0.156.0
|
||||
'@oxc-parser/binding-freebsd-x64': 0.156.0
|
||||
'@oxc-parser/binding-linux-arm-gnueabihf': 0.156.0
|
||||
'@oxc-parser/binding-linux-arm-musleabihf': 0.156.0
|
||||
'@oxc-parser/binding-linux-arm64-gnu': 0.156.0
|
||||
'@oxc-parser/binding-linux-arm64-musl': 0.156.0
|
||||
'@oxc-parser/binding-linux-ppc64-gnu': 0.156.0
|
||||
'@oxc-parser/binding-linux-riscv64-gnu': 0.156.0
|
||||
'@oxc-parser/binding-linux-riscv64-musl': 0.156.0
|
||||
'@oxc-parser/binding-linux-s390x-gnu': 0.156.0
|
||||
'@oxc-parser/binding-linux-x64-gnu': 0.156.0
|
||||
'@oxc-parser/binding-linux-x64-musl': 0.156.0
|
||||
'@oxc-parser/binding-openharmony-arm64': 0.156.0
|
||||
'@oxc-parser/binding-win32-arm64-msvc': 0.156.0
|
||||
'@oxc-parser/binding-win32-ia32-msvc': 0.156.0
|
||||
'@oxc-parser/binding-win32-x64-msvc': 0.156.0
|
||||
|
||||
oxc-resolver@11.24.2:
|
||||
optionalDependencies:
|
||||
@@ -9362,15 +9356,13 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.3.1
|
||||
|
||||
react-i18next@17.1.13(i18next@26.4.2(typescript@6.0.3))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3):
|
||||
react-i18next@18.0.1(@types/react@19.3.0)(i18next@27.0.1(typescript@6.0.3))(react@19.3.1)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
html-parse-stringify: 4.0.1
|
||||
i18next: 26.4.2(typescript@6.0.3)
|
||||
i18next: 27.0.1(typescript@6.0.3)
|
||||
react: 19.3.1
|
||||
use-sync-external-store: 1.7.0(react@19.3.1)
|
||||
optionalDependencies:
|
||||
react-dom: 19.3.1(react@19.3.1)
|
||||
'@types/react': 19.3.0
|
||||
typescript: 6.0.3
|
||||
|
||||
react-is@16.13.1: {}
|
||||
@@ -10007,7 +9999,7 @@ snapshots:
|
||||
deepmerge-ts: 8.0.2
|
||||
https-proxy-agent: 9.1.0(supports-color@7.2.0)
|
||||
undici: 8.12.3
|
||||
ws: 8.22.1
|
||||
ws: 8.22.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- kerberos
|
||||
@@ -10075,7 +10067,7 @@ snapshots:
|
||||
string-width: 7.2.0
|
||||
strip-ansi: 7.2.0
|
||||
|
||||
ws@8.22.1: {}
|
||||
ws@8.22.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
const reviewer = 'renovate-minor-review.lock.yml'
|
||||
const agents = ['copilot-swe-agent', 'copilot-swe-agent[bot]']
|
||||
const agents = ['Copilot', 'copilot-swe-agent', 'copilot-swe-agent[bot]']
|
||||
const checks = {
|
||||
'.github/workflows/frontend-check.yml': [
|
||||
'Run Prettier check',
|
||||
|
||||
@@ -58,13 +58,13 @@ smartstring = { workspace = true, features = ["serde"] }
|
||||
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"] }
|
||||
rfd = { version = "0.18.0", default-features = false, features = ["common-controls-v6", "xdg-portal"] }
|
||||
dirs = "7.0.0"
|
||||
dunce = "1.0.5"
|
||||
nanoid = "0.5"
|
||||
chrono = { workspace = true }
|
||||
boa_engine = "0.22.0"
|
||||
http-body-util = "0.1.4"
|
||||
http-body-util = "0.1.5"
|
||||
percent-encoding = "2.3.2"
|
||||
reqwest = { workspace = true }
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs.git", branch = "main", features = [
|
||||
@@ -77,8 +77,8 @@ tauri-plugin-dialog = "2.8.4"
|
||||
tauri-plugin-fs = "2.7.1"
|
||||
tauri-plugin-deep-link = "2.6.3"
|
||||
tauri-plugin-window-state = "2.5.0"
|
||||
zip = "9.3.0"
|
||||
flate2 = "1.1.10"
|
||||
zip = "9.5.0"
|
||||
flate2 = "1.1.11"
|
||||
reqwest_dav = "0.3.3"
|
||||
aes-gcm = { version = "0.11.1" }
|
||||
base64 = "0.23.1"
|
||||
@@ -97,7 +97,7 @@ 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.5"
|
||||
tokio-rustls = "0.26.6"
|
||||
rustls = { version = "0.23.46", features = ["ring"] }
|
||||
webpki-roots = "1.0.11"
|
||||
|
||||
@@ -5,16 +5,10 @@ use crate::{
|
||||
core::{
|
||||
handle::Handle,
|
||||
proxy_view::{GroupScope, ProxyViewBuilder, ProxyViewInput, ProxyViewV1},
|
||||
tray::Tray,
|
||||
},
|
||||
process::AsyncHandler,
|
||||
};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
/// Merges one selection pair into fresh backend state to avoid stale-list overwrites.
|
||||
#[tauri::command]
|
||||
@@ -27,15 +21,12 @@ pub async fn record_selected_node(group_name: String, node: String) -> CmdResult
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn forget_selected_node(group_name: String) -> CmdResult<()> {
|
||||
crate::config::profiles::forget_selected_node(&group_name)
|
||||
pub async fn switch_proxy_node(group_name: String, proxy_name: String, unfix: bool) -> CmdResult<()> {
|
||||
crate::feat::switch_proxy_node(&group_name, &proxy_name, unfix)
|
||||
.await
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
static TRAY_SYNC_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
static TRAY_SYNC_PENDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn runtime_group_order(config: Option<&Mapping>) -> Vec<String> {
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
@@ -124,48 +115,3 @@ pub async fn get_proxy_view() -> CmdResult<ProxyViewV1> {
|
||||
providers: providers.ok(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn sync_tray_proxy_selection() -> CmdResult<()> {
|
||||
if TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
AsyncHandler::spawn(move || async move {
|
||||
run_tray_sync_loop().await;
|
||||
});
|
||||
} else {
|
||||
TRAY_SYNC_PENDING.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_tray_sync_loop() {
|
||||
// Permanent watcher: keeps syncing until a pass finds no pending sync request.
|
||||
loop {
|
||||
match Tray::global().update_menu().await {
|
||||
Ok(_) => {
|
||||
logging!(debug, Type::Cmd, "Tray proxy selection synced successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
if !TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel) {
|
||||
TRAY_SYNC_RUNNING.store(false, Ordering::Release);
|
||||
|
||||
if TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel)
|
||||
&& TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,25 +280,29 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Commits drafts during exit/restart/shutdown so user changes are not lost.
|
||||
/// A config with nothing staged keeps its file, so its timestamps stay put.
|
||||
pub async fn apply_all_and_save_file() {
|
||||
logging!(debug, Type::Config, "save all draft data");
|
||||
let save_clash_task = AsyncHandler::spawn(|| async {
|
||||
let clash = Self::clash().await;
|
||||
clash.apply();
|
||||
logging_error!(Type::Config, clash.data_arc().save_config().await);
|
||||
if clash.apply() {
|
||||
logging_error!(Type::Config, clash.data_arc().save_config().await);
|
||||
}
|
||||
});
|
||||
|
||||
let save_verge_task = AsyncHandler::spawn(|| async {
|
||||
let verge = Self::verge().await;
|
||||
verge.apply();
|
||||
logging_error!(Type::Config, verge.data_arc().save_file().await);
|
||||
if verge.apply() {
|
||||
logging_error!(Type::Config, verge.data_arc().save_file().await);
|
||||
}
|
||||
});
|
||||
|
||||
let save_profiles_task = AsyncHandler::spawn(|| async {
|
||||
let _profile_write = super::profiles::PROFILE_WRITE_LOCK.lock().await;
|
||||
let profiles = Self::profiles().await;
|
||||
profiles.apply();
|
||||
logging_error!(Type::Config, profiles.data_arc().save_file().await);
|
||||
if profiles.apply() {
|
||||
logging_error!(Type::Config, profiles.data_arc().save_file().await);
|
||||
}
|
||||
});
|
||||
|
||||
let _ = tokio::join!(save_clash_task, save_verge_task, save_profiles_task);
|
||||
|
||||
@@ -1000,7 +1000,9 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
Some((g, p)) => (g, p),
|
||||
None => return,
|
||||
};
|
||||
feat::switch_proxy_node(group_name, proxy_name).await;
|
||||
if let Err(err) = feat::switch_proxy_node(group_name, proxy_name, false).await {
|
||||
logging!(error, Type::Tray, "Failed to switch proxy: {err:#}");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
logging!(debug, Type::Tray, "Unhandled tray menu event: {:?}", event.id);
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
cmd,
|
||||
config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe},
|
||||
core::{CoreManager, handle, tray, validate::ValidationOutcome},
|
||||
process::AsyncHandler,
|
||||
utils::help::{mask_err, mask_url},
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
@@ -21,37 +22,85 @@ pub async fn toggle_proxy_profile(profile_index: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell the profile which node this group is on now.
|
||||
///
|
||||
/// The core is already switched by the time this runs, so failing to record is not a reason to
|
||||
/// report the switch as failed — but it is worth a log line, because what the profile holds is
|
||||
/// what gets re-applied the next time a core starts.
|
||||
async fn record_switched_node(group_name: &str, proxy_name: &str) {
|
||||
if let Err(error) = crate::config::profiles::record_selected_node(group_name, proxy_name).await {
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str, unfix: bool) -> Result<()> {
|
||||
let mihomo = handle::Handle::mihomo();
|
||||
let previous_proxy = if Config::verge().await.data_arc().auto_close_connection.unwrap_or(false) {
|
||||
match mihomo.get_proxy_by_name(group_name).await {
|
||||
Ok(group) => group.now.filter(|name| !name.is_empty()),
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read previous proxy selection for {group_name}: {err}"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if unfix {
|
||||
mihomo.unfixed_proxy(group_name).await?;
|
||||
} else {
|
||||
mihomo.select_node_for_group(group_name, proxy_name).await?;
|
||||
}
|
||||
|
||||
let save_result = if unfix {
|
||||
crate::config::profiles::forget_selected_node(group_name).await
|
||||
} else {
|
||||
crate::config::profiles::record_selected_node(group_name, proxy_name).await
|
||||
};
|
||||
// The core selection is already applied; persistence failure must not report it as failed.
|
||||
if let Err(error) = save_result {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"切换代理成功但未能记录到配置: {} -> {}, 错误: {error:#}",
|
||||
group_name,
|
||||
proxy_name
|
||||
"Failed to persist proxy selection for {group_name}: {error:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
announce(Refresh::Proxies);
|
||||
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
match handle::Handle::mihomo()
|
||||
.select_node_for_group(group_name, proxy_name)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
announce(Refresh::Proxies);
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Tray, "切换代理失败: {err:?}");
|
||||
}
|
||||
if let Some(previous_proxy) = previous_proxy {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
let connections = match mihomo.get_connections().await {
|
||||
Ok(connections) => connections,
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read connections after switching proxy: {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
futures::future::join_all(
|
||||
connections
|
||||
.connections
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|connection| connection.chains.contains(&previous_proxy))
|
||||
.map(|connection| async move {
|
||||
if let Err(err) = mihomo.close_connection(&connection.id).await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to close connection {} after switching proxy: {err}",
|
||||
connection.id
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
if let Err(err) = tray::Tray::global().update_menu().await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Tray,
|
||||
"Failed to refresh tray after switching proxy: {err:#}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result<Option<(String, Option<PrfOption>)>> {
|
||||
|
||||
@@ -45,9 +45,8 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
cmd::update_proxy_chain_config_in_runtime,
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::switch_proxy_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::set_dns_override,
|
||||
|
||||
@@ -606,9 +606,6 @@ export const CurrentProxyCard = () => {
|
||||
)
|
||||
|
||||
const { select } = useProxySelection({
|
||||
onSuccess: () => {
|
||||
refreshProxy()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('代理切换失败', error)
|
||||
refreshProxy()
|
||||
@@ -755,10 +752,9 @@ export const CurrentProxyCard = () => {
|
||||
if (!selectedGroup || !option || !isInteractableMember(option.member)) {
|
||||
return
|
||||
}
|
||||
const previousProxy = selectedGroup.now
|
||||
const nextName = option.member.ref.name
|
||||
// The profile selection is the durable source across core restarts and run modes.
|
||||
select(selectedGroup.name, nextName, previousProxy!, selectedGroup.fixed!)
|
||||
select(selectedGroup.name, nextName, selectedGroup.fixed!)
|
||||
},
|
||||
[isDirectMode, select, selectedGroup, unsortedProxyOptions],
|
||||
)
|
||||
|
||||
@@ -470,9 +470,6 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
}, [handleScroll])
|
||||
|
||||
const { select } = useProxySelection({
|
||||
onSuccess: () => {
|
||||
onProxies()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('代理切换失败', error)
|
||||
onProxies()
|
||||
@@ -487,12 +484,12 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
if (group.name === globalGroupName) {
|
||||
pendingRevealRef.current =
|
||||
member.kind === 'group' ? member.ref.name : null
|
||||
// The selection refetches the view, so skip onHeadState's refetch.
|
||||
// The backend selection event refreshes the view.
|
||||
if (member.kind === 'group') {
|
||||
setHeadState(member.ref.name, { open: true })
|
||||
}
|
||||
}
|
||||
select(group.name, member.ref.name, group.now!, group.fixed!)
|
||||
select(group.name, member.ref.name, group.fixed!)
|
||||
},
|
||||
[globalGroupName, select, setHeadState],
|
||||
)
|
||||
|
||||
@@ -209,7 +209,13 @@ export const useRenderList = (
|
||||
|
||||
const renderGroups = useMemo(() => {
|
||||
if (!proxyView) return []
|
||||
if (mode === 'rule') return proxyView.groups
|
||||
// A profile whose only group is GLOBAL (mihomo then skips its built-in one)
|
||||
// or that has no proxy-groups has nothing else to show in rule mode.
|
||||
if (mode === 'rule') {
|
||||
return proxyView.groups.length || !proxyView.global
|
||||
? proxyView.groups
|
||||
: [proxyView.global]
|
||||
}
|
||||
const { global } = proxyView
|
||||
if (!global) return []
|
||||
const current = findCurrentGroupMember(proxyView, global)?.member
|
||||
|
||||
@@ -1,51 +1,35 @@
|
||||
import { selectNodeForGroup } from 'tauri-plugin-mihomo-api'
|
||||
import { expect, it, vi } from 'vitest'
|
||||
|
||||
import { switchProxyNode } from '@/services/cmds'
|
||||
|
||||
import { useProxySelection } from './use-proxy-selection'
|
||||
|
||||
const record = vi.hoisted(() => vi.fn())
|
||||
vi.mock('react', () => ({
|
||||
useCallback: (callback: unknown) => callback,
|
||||
useRef: (current: unknown) => ({ current }),
|
||||
}))
|
||||
vi.mock('@/hooks/use-record-selection', () => ({
|
||||
useRecordSelection: () => record,
|
||||
useForgetSelection: () => vi.fn(),
|
||||
}))
|
||||
vi.mock('@/hooks/use-verge', () => ({ useVerge: () => ({ verge: {} }) }))
|
||||
vi.mock('tauri-plugin-mihomo-api', () => ({
|
||||
selectNodeForGroup: vi.fn(),
|
||||
unfixedProxy: vi.fn(),
|
||||
}))
|
||||
vi.mock('@/services/cmds', () => ({ syncTrayProxySelection: vi.fn() }))
|
||||
vi.mock('@/services/cmds', () => ({ switchProxyNode: vi.fn() }))
|
||||
vi.mock('@/services/notice-service', () => ({ showNotice: { error: vi.fn() } }))
|
||||
vi.mock('@/services/query-client', () => ({ revalidateQueries: vi.fn() }))
|
||||
|
||||
it('applies and records independent group selections while another group is pending', async () => {
|
||||
it('requests independent group selections while another group is pending', async () => {
|
||||
let finishFirst!: () => void
|
||||
vi.mocked(selectNodeForGroup)
|
||||
vi.mocked(switchProxyNode)
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
finishFirst = resolve
|
||||
finishFirst = () => resolve(null)
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(undefined)
|
||||
const firstSuccess = vi.fn()
|
||||
const secondSuccess = vi.fn()
|
||||
const first = useProxySelection({ onSuccess: firstSuccess })
|
||||
const second = useProxySelection({ onSuccess: secondSuccess })
|
||||
.mockResolvedValueOnce(null)
|
||||
const first = useProxySelection()
|
||||
const second = useProxySelection()
|
||||
first.select('Group A', 'Node A')
|
||||
second.select('Group B', 'Node B')
|
||||
second.select('Group B', 'Node B', 'Node B')
|
||||
await vi.waitFor(() =>
|
||||
expect(record).toHaveBeenCalledWith('Group B', 'Node B'),
|
||||
expect(switchProxyNode).toHaveBeenCalledWith('Group B', 'Node B', true),
|
||||
)
|
||||
expect(selectNodeForGroup).toHaveBeenCalledTimes(2)
|
||||
expect(firstSuccess).not.toHaveBeenCalled()
|
||||
expect(secondSuccess).toHaveBeenCalledOnce()
|
||||
expect(switchProxyNode).toHaveBeenCalledWith('Group A', 'Node A', false)
|
||||
expect(switchProxyNode).toHaveBeenCalledTimes(2)
|
||||
finishFirst()
|
||||
await vi.waitFor(() =>
|
||||
expect(record).toHaveBeenCalledWith('Group A', 'Node A'),
|
||||
)
|
||||
expect(firstSuccess).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -1,132 +1,44 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
closeConnection,
|
||||
getConnections,
|
||||
selectNodeForGroup,
|
||||
unfixedProxy,
|
||||
} from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import {
|
||||
useForgetSelection,
|
||||
useRecordSelection,
|
||||
} from '@/hooks/use-record-selection'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { syncTrayProxySelection } from '@/services/cmds'
|
||||
import { switchProxyNode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
|
||||
// 缓存连接清理
|
||||
const cleanupConnections = async (previousProxy: string) => {
|
||||
try {
|
||||
const { connections } = await getConnections()
|
||||
const cleanupPromises = (connections ?? [])
|
||||
.filter((conn) => conn.chains.includes(previousProxy))
|
||||
.map((conn) => closeConnection(conn.id))
|
||||
|
||||
if (cleanupPromises.length > 0) {
|
||||
await Promise.allSettled(cleanupPromises)
|
||||
debugLog(`[ProxySelection] 清理了 ${cleanupPromises.length} 个连接`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ProxySelection] 连接清理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
interface ProxySelectionOptions {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}
|
||||
|
||||
interface ProxyChangeRequest {
|
||||
groupName: string
|
||||
proxyName: string
|
||||
previousProxy?: string
|
||||
fixed?: string
|
||||
}
|
||||
|
||||
// 代理选择 Hook
|
||||
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
const recordSelection = useRecordSelection()
|
||||
const forgetSelection = useForgetSelection()
|
||||
const { verge } = useVerge()
|
||||
|
||||
const { onSuccess, onError } = options
|
||||
|
||||
const autoCloseConnection = verge?.auto_close_connection ?? false
|
||||
|
||||
// 切换节点
|
||||
const syncTraySelection = useCallback(() => {
|
||||
mutate(() => syncTrayProxySelection(), {
|
||||
id: 'sync-tray-selection',
|
||||
errorNotice: false,
|
||||
}).catch((error) => {
|
||||
console.error('[ProxySelection] 托盘状态同步失败:', error)
|
||||
})
|
||||
}, [])
|
||||
const { onError } = options
|
||||
|
||||
const executeChange = useCallback(
|
||||
async (request: ProxyChangeRequest) => {
|
||||
const { groupName, proxyName, previousProxy, fixed } = request
|
||||
const isFixedProxy = fixed === proxyName
|
||||
if (isFixedProxy) {
|
||||
debugLog(`[ProxySelection] 代理取消固定: ${groupName} -> ${proxyName}`)
|
||||
} else {
|
||||
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`)
|
||||
}
|
||||
|
||||
async ({ groupName, proxyName, fixed }: ProxyChangeRequest) => {
|
||||
try {
|
||||
const result = await mutate(
|
||||
() =>
|
||||
isFixedProxy
|
||||
? unfixedProxy(groupName)
|
||||
: selectNodeForGroup(groupName, proxyName),
|
||||
await mutate(
|
||||
() => switchProxyNode(groupName, proxyName, fixed === proxyName),
|
||||
{ id: `select-proxy-node:${groupName}`, errorNotice: false },
|
||||
)
|
||||
if (!result.ok) return
|
||||
onSuccess?.()
|
||||
syncTraySelection()
|
||||
if (isFixedProxy) {
|
||||
await forgetSelection(groupName)
|
||||
debugLog(`[ProxySelection] 代理和状态同步完成: ${groupName}`)
|
||||
} else {
|
||||
await recordSelection(groupName, proxyName)
|
||||
debugLog(
|
||||
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (autoCloseConnection && previousProxy) {
|
||||
void cleanupConnections(previousProxy)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ProxySelection] 代理切换失败: ${groupName} -> ${proxyName}`,
|
||||
`[ProxySelection] Failed to switch proxy: ${groupName} -> ${proxyName}`,
|
||||
error,
|
||||
)
|
||||
onError?.(error)
|
||||
}
|
||||
},
|
||||
[
|
||||
autoCloseConnection,
|
||||
forgetSelection,
|
||||
onError,
|
||||
onSuccess,
|
||||
recordSelection,
|
||||
syncTraySelection,
|
||||
],
|
||||
[onError],
|
||||
)
|
||||
|
||||
const select = useCallback(
|
||||
(
|
||||
groupName: string,
|
||||
proxyName: string,
|
||||
previousProxy?: string,
|
||||
fixed?: string,
|
||||
) => {
|
||||
(groupName: string, proxyName: string, fixed?: string) => {
|
||||
void executeChange({
|
||||
groupName,
|
||||
proxyName,
|
||||
previousProxy,
|
||||
fixed,
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { forgetSelectedNode, recordSelectedNode } from '@/services/cmds'
|
||||
import { recordSelectedNode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
|
||||
/**
|
||||
@@ -19,16 +19,3 @@ export const useRecordSelection = () => {
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
export const useForgetSelection = () => {
|
||||
return useCallback(async (groupName: string) => {
|
||||
try {
|
||||
await mutate(() => forgetSelectedNode(groupName), {
|
||||
id: `persist-selected-node:${groupName}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Selection] 清除代理选择失败:', error)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ const ORDER_OPTIONS = [
|
||||
|
||||
type OrderKey = (typeof ORDER_OPTIONS)[number]['id']
|
||||
|
||||
type NetworkFilter = 'all' | 'tcp' | 'udp'
|
||||
|
||||
let savedNetworkFilter: NetworkFilter = 'all'
|
||||
|
||||
const orderFunctionMap = ORDER_OPTIONS.reduce<Record<OrderKey, OrderFunc>>(
|
||||
(acc, option) => {
|
||||
acc[option.id] = option.fn
|
||||
@@ -86,6 +90,9 @@ const ConnectionsPage = () => {
|
||||
const { searchState, match, onSearch } = usePageSearch('connections')
|
||||
const hasSearch = searchState.text.length > 0
|
||||
const [curOrderOpt, setCurOrderOpt] = useState<OrderKey>('default')
|
||||
const [networkFilter, setNetworkFilter] =
|
||||
useState<NetworkFilter>(savedNetworkFilter)
|
||||
const hasFilter = networkFilter !== 'all'
|
||||
const [connectionsType, setConnectionsType] = useState<'active' | 'closed'>(
|
||||
'active',
|
||||
)
|
||||
@@ -112,18 +119,30 @@ const ConnectionsPage = () => {
|
||||
const filterConn = useMemo(() => {
|
||||
const orderFunc = orderFunctionMap[curOrderOpt]
|
||||
|
||||
if (isTableLayout && !hasSearch) return selectedConnections
|
||||
if (!hasSearch) return orderFunc([...selectedConnections])
|
||||
if (isTableLayout && !hasSearch && !hasFilter) return selectedConnections
|
||||
if (!hasSearch && !hasFilter) return orderFunc([...selectedConnections])
|
||||
|
||||
const matchConns = selectedConnections.filter((conn) => {
|
||||
const { host, destinationIP, process } = conn.metadata
|
||||
const { host, destinationIP, process, network } = conn.metadata
|
||||
if (networkFilter !== 'all' && network !== networkFilter) return false
|
||||
return (
|
||||
match(host || '') || match(destinationIP || '') || match(process || '')
|
||||
!hasSearch ||
|
||||
match(host || '') ||
|
||||
match(destinationIP || '') ||
|
||||
match(process || '')
|
||||
)
|
||||
})
|
||||
|
||||
return orderFunc ? orderFunc(matchConns) : matchConns
|
||||
}, [selectedConnections, isTableLayout, hasSearch, match, curOrderOpt])
|
||||
}, [
|
||||
selectedConnections,
|
||||
isTableLayout,
|
||||
hasSearch,
|
||||
hasFilter,
|
||||
networkFilter,
|
||||
match,
|
||||
curOrderOpt,
|
||||
])
|
||||
|
||||
const displayRows = useConnectionRowViews(
|
||||
isTableLayout ? EMPTY_CONNECTIONS : filterConn,
|
||||
@@ -255,6 +274,24 @@ const ConnectionsPage = () => {
|
||||
))}
|
||||
</BaseStyledSelect>
|
||||
)}
|
||||
<BaseStyledSelect
|
||||
style={{ width: 80 }}
|
||||
value={networkFilter}
|
||||
onChange={(e) => {
|
||||
savedNetworkFilter = e.target.value as NetworkFilter
|
||||
setNetworkFilter(savedNetworkFilter)
|
||||
}}
|
||||
>
|
||||
<MenuItem value="all">
|
||||
<span style={{ fontSize: 14 }}>All</span>
|
||||
</MenuItem>
|
||||
<MenuItem value="tcp">
|
||||
<span style={{ fontSize: 14 }}>TCP</span>
|
||||
</MenuItem>
|
||||
<MenuItem value="udp">
|
||||
<span style={{ fontSize: 14 }}>UDP</span>
|
||||
</MenuItem>
|
||||
</BaseStyledSelect>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
|
||||
@@ -78,13 +78,11 @@ export const commands = {
|
||||
}),
|
||||
invokeUwpTool: () => __TAURI_INVOKE<null>('invoke_uwp_tool'),
|
||||
copyClashEnv: () => __TAURI_INVOKE<null>('copy_clash_env'),
|
||||
syncTrayProxySelection: () =>
|
||||
__TAURI_INVOKE<null>('sync_tray_proxy_selection'),
|
||||
/** Merges one selection pair into fresh backend state to avoid stale-list overwrites. */
|
||||
recordSelectedNode: (groupName: string, node: string) =>
|
||||
__TAURI_INVOKE<null>('record_selected_node', { groupName, node }),
|
||||
forgetSelectedNode: (groupName: string) =>
|
||||
__TAURI_INVOKE<null>('forget_selected_node', { groupName }),
|
||||
switchProxyNode: (groupName: string, proxyName: string, unfix: boolean) =>
|
||||
__TAURI_INVOKE<null>('switch_proxy_node', { groupName, proxyName, unfix }),
|
||||
saveDnsConfig: (dnsConfig: YamlMapping) =>
|
||||
__TAURI_INVOKE<null>('save_dns_config', { dnsConfig }),
|
||||
applyDnsConfig: (apply: boolean) =>
|
||||
|
||||
@@ -29,14 +29,11 @@ export interface BusHandlerDeps {
|
||||
/**
|
||||
* Pure per-event handler table for the bus. Handlers may trigger documented
|
||||
* side effects (SWR revalidation, notice toasts) but write store state only
|
||||
* through dispatched pure actions. Created exactly once per bus mount; the
|
||||
* throttles below live in the returned closure, mirroring the previous
|
||||
* scattered listeners.
|
||||
* through dispatched pure actions. Created exactly once per bus mount.
|
||||
*/
|
||||
export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
let lastProfileId: string | null = null
|
||||
let lastProfileChangeTime = 0
|
||||
let lastProxyRefreshTime = 0
|
||||
const refreshThrottle = 800
|
||||
|
||||
const handleProfileChanged = (newProfileId: string) => {
|
||||
@@ -52,13 +49,6 @@ export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
deps.revalidateKeys(['getProfiles'])
|
||||
}
|
||||
|
||||
const handleRefreshProxyConfig = () => {
|
||||
const now = Date.now()
|
||||
if (now - lastProxyRefreshTime <= refreshThrottle) return
|
||||
lastProxyRefreshTime = now
|
||||
deps.revalidateKeys(['getProxyView'])
|
||||
}
|
||||
|
||||
const handlers: BusHandlers = {
|
||||
'verge://refresh-clash-config': () =>
|
||||
deps.revalidateKeys([
|
||||
@@ -74,7 +64,7 @@ export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
'verge://refresh-verge-config': () =>
|
||||
deps.revalidateKeys(['getVergeConfig', 'getSystemProxy', 'getAutoProxy']),
|
||||
'verge://refresh-profiles': () => deps.revalidateKeys(['getProfiles']),
|
||||
'verge://refresh-proxy-config': handleRefreshProxyConfig,
|
||||
'verge://refresh-proxy-config': () => deps.revalidateKeys(['getProxyView']),
|
||||
'verge://notice-message': (payload) => deps.handleNotice(payload),
|
||||
'profile-changed': handleProfileChanged,
|
||||
'verge://timer-updated': (uid) =>
|
||||
|
||||
@@ -152,17 +152,17 @@ export async function patchClashMode(payload: string) {
|
||||
return commands.patchClashMode(payload)
|
||||
}
|
||||
|
||||
export async function syncTrayProxySelection() {
|
||||
return commands.syncTrayProxySelection()
|
||||
}
|
||||
|
||||
/** Sends one selection pair so the backend can merge against current profile state. */
|
||||
export async function recordSelectedNode(groupName: string, node: string) {
|
||||
return commands.recordSelectedNode(groupName, node)
|
||||
}
|
||||
|
||||
export async function forgetSelectedNode(groupName: string) {
|
||||
return commands.forgetSelectedNode(groupName)
|
||||
export async function switchProxyNode(
|
||||
groupName: string,
|
||||
proxyName: string,
|
||||
unfix: boolean,
|
||||
) {
|
||||
return commands.switchProxyNode(groupName, proxyName, unfix)
|
||||
}
|
||||
|
||||
export async function getProxyView(): Promise<ProxyViewV1> {
|
||||
|
||||
Reference in New Issue
Block a user