mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-27 16:22:11 +08:00
Merge remote-tracking branch 'origin/dev' into fix/grouped-virtual-list
This commit is contained in:
@@ -30,11 +30,19 @@ jobs:
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
|
||||
DRAFT: ${{ github.event.pull_request.draft }}
|
||||
ASSOCIATION: ${{ github.event.pull_request.author_association }}
|
||||
AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
|
||||
run: |
|
||||
if [ "$DRAFT" = "true" ] || printf '%s' "$LABELS" | grep -q 'no-issue-needed'; then
|
||||
echo "Draft or no-issue-needed; linkage check skipped."
|
||||
exit 0
|
||||
fi
|
||||
# OWNER/MEMBER/COLLABORATOR and bots mirror the slop review's
|
||||
# skip-roles/skip-bots trust tier.
|
||||
if [ "$AUTHOR_TYPE" = "Bot" ] || [ "$ASSOCIATION" = "OWNER" ] || [ "$ASSOCIATION" = "MEMBER" ] || [ "$ASSOCIATION" = "COLLABORATOR" ]; then
|
||||
echo "Bot or trusted author ($ASSOCIATION); linkage check skipped."
|
||||
exit 0
|
||||
fi
|
||||
if printf '%s\n%s\n' "$PR_TITLE" "$PR_BODY" | grep -Eq '#[0-9]+|issues/[0-9]+'; then
|
||||
echo "Issue reference found."
|
||||
else
|
||||
|
||||
Generated
+452
-591
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -61,10 +61,10 @@ clash-verge-i18n = { path = "crates/clash-verge-i18n" }
|
||||
clash-verge-limiter = { path = "crates/clash-verge-limiter" }
|
||||
tracing-estuary = { git = "https://github.com/Tunglies/tracing-estuary.git", branch = "main" }
|
||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||
tracing = "0.1"
|
||||
tracing = "0.1.44"
|
||||
|
||||
tauri = { version = "2.11.5" }
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
tauri-plugin-clipboard-manager = "2.3.3"
|
||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
||||
anyhow = "1.0.104"
|
||||
chrono = "0.4.45"
|
||||
@@ -76,9 +76,9 @@ tokio = { version = "1.53.1", features = [
|
||||
"sync",
|
||||
] }
|
||||
flexi_logger = "0.31.10"
|
||||
log = "0.4.33"
|
||||
log = "0.4.34"
|
||||
regex = "1.13.1"
|
||||
reqwest = { version = "0.13.4", features = [
|
||||
reqwest = { version = "0.13.5", features = [
|
||||
"json",
|
||||
"cookies",
|
||||
"gzip",
|
||||
@@ -92,7 +92,7 @@ smartstring = { version = "1.0.1" }
|
||||
serde = { version = "1.0.229" }
|
||||
serde_json = { version = "1.0.151" }
|
||||
serde_yaml_ng = { version = "0.10.0" }
|
||||
bitflags = { version = "2.13.1" }
|
||||
bitflags = { version = "2.13.2" }
|
||||
socket2 = "0.6.5"
|
||||
|
||||
# *** For Windows platform only ***
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
**🖥️ Windows**
|
||||
|
||||
- 修复 Windows 便携版或 Scoop 安装后服务内核无法启动的问题
|
||||
- 修复 Windows 系统代理设置偶发失败的问题
|
||||
- 修复 Windows 关机被应用阻止的问题
|
||||
- 修复 Windows 服务模式下 TUN 使用 system/mixed 堆栈时无法接管流量的问题,防火墙放行规则改为自动添加
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rust-i18n = "4.2.1"
|
||||
rust-i18n = "4.2.2"
|
||||
sys-locale = "0.3.2"
|
||||
|
||||
[lints]
|
||||
|
||||
+14
-14
@@ -56,13 +56,13 @@
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||
"@tauri-apps/plugin-fs": "^2.5.1",
|
||||
"@tauri-apps/plugin-http": "~2.5.9",
|
||||
"@tauri-apps/plugin-opener": "2.5.4",
|
||||
"@tauri-apps/plugin-updater": "2.10.1",
|
||||
"@tauri-apps/plugin-http": "~2.6.0",
|
||||
"@tauri-apps/plugin-opener": "2.5.5",
|
||||
"@tauri-apps/plugin-updater": "2.11.0",
|
||||
"ahooks": "^3.9.7",
|
||||
"cidr-block": "^2.3.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"dayjs": "1.11.21",
|
||||
"dayjs": "1.11.23",
|
||||
"foxact": "^0.3.4",
|
||||
"foxts": "^5.4.2",
|
||||
"i18next": "^26.2.0",
|
||||
@@ -73,11 +73,11 @@
|
||||
"monaco-yaml": "^5.5.0",
|
||||
"nanoid": "^6.0.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8",
|
||||
"react-error-boundary": "6.1.2",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react-error-boundary": "6.1.5",
|
||||
"react-hook-form": "^7.76.1",
|
||||
"react-i18next": "17.0.11",
|
||||
"react-i18next": "17.0.14",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^8.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -96,12 +96,12 @@
|
||||
"@tauri-apps/cli": "2.11.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.4",
|
||||
"@types/react": "19.3.0",
|
||||
"@types/react-dom": "19.3.0",
|
||||
"@types/validator": "^13.15.10",
|
||||
"@vitejs/plugin-legacy": "^8.0.2",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"@wdio/tauri-service": "1.3.0",
|
||||
"@wdio/tauri-service": "1.4.0",
|
||||
"adm-zip": "^0.6.0",
|
||||
"axios": "^1.16.1",
|
||||
"cli-color": "^2.0.4",
|
||||
@@ -121,7 +121,7 @@
|
||||
"https-proxy-agent": "^9.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.7.0",
|
||||
"knip": "6.32.1",
|
||||
"knip": "6.35.1",
|
||||
"lint-staged": "^17.0.5",
|
||||
"sass": "^1.100.0",
|
||||
"tar": "^7.5.15",
|
||||
@@ -131,8 +131,8 @@
|
||||
"vite": "^8.0.14",
|
||||
"vite-plugin-svgr": "^5.2.0",
|
||||
"vitest": "^5.0.0",
|
||||
"webdriverio": "9.30.0",
|
||||
"ws": "8.21.0"
|
||||
"webdriverio": "9.31.9",
|
||||
"ws": "8.21.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,mjs}": [
|
||||
|
||||
Generated
+369
-464
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -59,13 +59,13 @@ smartstring = { workspace = true, features = ["serde"] }
|
||||
bitflags = { workspace = true }
|
||||
socket2 = { workspace = true }
|
||||
warp = { version = "0.4.3", features = ["server"] }
|
||||
open = "5.4.1"
|
||||
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"
|
||||
chrono = { workspace = true }
|
||||
boa_engine = "0.21.1"
|
||||
boa_engine = "0.22.0"
|
||||
once_cell = { version = "1.21.4", features = ["parking_lot"] }
|
||||
percent-encoding = "2.3.2"
|
||||
reqwest = { workspace = true }
|
||||
@@ -74,37 +74,37 @@ sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs.git", branch
|
||||
"guard",
|
||||
] }
|
||||
network-interface = { version = "2.0.5", features = ["serde"] }
|
||||
tauri-plugin-shell = "2.3.5"
|
||||
tauri-plugin-opener = "2.5.4"
|
||||
tauri-plugin-dialog = "2.7.2"
|
||||
tauri-plugin-fs = "2.5.1"
|
||||
tauri-plugin-shell = "2.3.6"
|
||||
tauri-plugin-opener = "2.5.5"
|
||||
tauri-plugin-dialog = "2.7.3"
|
||||
tauri-plugin-fs = "2.5.2"
|
||||
tauri-plugin-process = "2.3.1"
|
||||
tauri-plugin-deep-link = "2.4.9"
|
||||
tauri-plugin-deep-link = "2.4.10"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
zip = "8.6.0"
|
||||
flate2 = "1.1.9"
|
||||
flate2 = "1.1.10"
|
||||
reqwest_dav = "0.3.3"
|
||||
aes-gcm = { version = "0.11.0" }
|
||||
aes-gcm = { version = "0.11.1" }
|
||||
base64 = "0.23.1"
|
||||
sha2 = "0.11"
|
||||
getrandom = "0.4.3"
|
||||
futures = "0.3.34"
|
||||
gethostname = "1.1.0"
|
||||
scopeguard = "1.2.0"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
tauri-plugin-notification = "2.4.0"
|
||||
tokio-stream = "0.1.19"
|
||||
backon = { version = "1.6.0", features = ["tokio-sleep"] }
|
||||
tauri-plugin-http = "2.5.9"
|
||||
tauri-plugin-http = "2.6.0"
|
||||
tracing = { workspace = true }
|
||||
tauri-plugin-devtools = { version = "2.1.0", optional = true }
|
||||
tauri-plugin-devtools = { version = "2.2.1", optional = true }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "main" }
|
||||
async-trait = "0.1.92"
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.0", version = "2.7.0", features = [
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.1", version = "2.7.1", features = [
|
||||
"client",
|
||||
] }
|
||||
arc-swap = "1.9.2"
|
||||
tokio-rustls = "0.26.4"
|
||||
rustls = { version = "0.23.43", features = ["ring"] }
|
||||
tokio-rustls = "0.26.5"
|
||||
rustls = { version = "0.23.45", features = ["ring"] }
|
||||
webpki-roots = "1.0.9"
|
||||
dark-light = "3.0.0"
|
||||
bytes = "1.12.1"
|
||||
@@ -114,7 +114,7 @@ tokio-util = { version = "0.7.19", features = ["time"] }
|
||||
libc = "0.2.189"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
tauri-plugin-wdio-webdriver = { version = "=1.3.0", optional = true }
|
||||
tauri-plugin-wdio-webdriver = { version = "=1.4.0", optional = true }
|
||||
objc2 = "0.6.4"
|
||||
objc2-foundation = { version = "0.3.2", features = [
|
||||
"NSString",
|
||||
@@ -148,7 +148,7 @@ windows-sys.workspace = true
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.1"
|
||||
tauri-plugin-global-shortcut = "2.3.2"
|
||||
tauri-plugin-updater = "2.10.1"
|
||||
tauri-plugin-updater = "2.11.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
|
||||
@@ -564,8 +564,39 @@ fn uninstall_service() -> Result<()> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn install_service() -> Result<()> {
|
||||
use std::process::Output;
|
||||
logging!(info, Type::Service, "install service");
|
||||
let cores = crate::config::IVerge::VALID_CLASH_CORES
|
||||
.iter()
|
||||
.map(|core| service_core_path(core, ".exe"))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
install_windows_service_with_cores(&cores, run_windows_service_installer)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn install_windows_service_with_cores(
|
||||
cores: &[PathBuf],
|
||||
mut run_installer: impl FnMut(&[std::ffi::OsString]) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
let cores: Vec<_> = cores.iter().filter(|core| core.is_file()).collect();
|
||||
if cores.is_empty() {
|
||||
bail!("no core executable is available; restore a bundled core before installing the service");
|
||||
}
|
||||
let mut arguments = vec!["--install-service".into()];
|
||||
for core in cores {
|
||||
let digest = sha256_hex(core)?;
|
||||
arguments.extend([
|
||||
"--install-core".into(),
|
||||
core.as_os_str().to_owned(),
|
||||
"--sha256".into(),
|
||||
digest.into(),
|
||||
]);
|
||||
}
|
||||
run_installer(&arguments).context("failed to install the service and its cores; choose Repair to retry")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn run_windows_service_installer(arguments: &[std::ffi::OsString]) -> Result<()> {
|
||||
use std::process::Output;
|
||||
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
@@ -583,7 +614,7 @@ fn install_service() -> Result<()> {
|
||||
let level = token.privilege_level()?;
|
||||
let output = match level {
|
||||
PrivilegeLevel::NotPrivileged => {
|
||||
let status = RunasCommand::new(&install_path).show(false).status()?;
|
||||
let status = RunasCommand::new(&install_path).args(arguments).show(false).status()?;
|
||||
Output {
|
||||
status,
|
||||
stdout: Vec::new(),
|
||||
@@ -592,7 +623,10 @@ fn install_service() -> Result<()> {
|
||||
}
|
||||
_ => {
|
||||
// StdCommand returns Output directly
|
||||
StdCommand::new(&install_path).creation_flags(0x08000000).output()?
|
||||
StdCommand::new(&install_path)
|
||||
.args(arguments)
|
||||
.creation_flags(0x08000000)
|
||||
.output()?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2167,6 +2201,82 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_service_install_combines_service_and_attested_cores() -> anyhow::Result<()> {
|
||||
use std::ffi::OsString;
|
||||
let root = TestDirectory::new("scoop install")?;
|
||||
let cores: Vec<_> = crate::config::IVerge::VALID_CLASH_CORES
|
||||
.iter()
|
||||
.map(|name| root.path().join(format!("{name}.exe")))
|
||||
.collect();
|
||||
for core in &cores {
|
||||
std::fs::write(core, b"abc")?;
|
||||
}
|
||||
let mut calls = Vec::new();
|
||||
super::install_windows_service_with_cores(&cores, |arguments| {
|
||||
calls.push(arguments.to_vec());
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(calls.len(), 1, "service and cores must share one elevation");
|
||||
let digest = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
|
||||
let mut expected: Vec<OsString> = vec!["--install-service".into()];
|
||||
expected.extend(cores.iter().flat_map(|core| {
|
||||
[
|
||||
"--install-core".into(),
|
||||
core.as_os_str().to_owned(),
|
||||
"--sha256".into(),
|
||||
digest.into(),
|
||||
]
|
||||
}));
|
||||
assert_eq!(calls[0], expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_service_install_skips_unavailable_cores() -> anyhow::Result<()> {
|
||||
let root = TestDirectory::new("missing-core")?;
|
||||
let core = root.path().join("verge-mihomo.exe");
|
||||
let missing = root.path().join("verge-mihomo-alpha.exe");
|
||||
std::fs::write(&core, b"abc")?;
|
||||
let cores = [core.clone(), missing.clone(), root.path().to_path_buf()];
|
||||
let mut calls = Vec::new();
|
||||
super::install_windows_service_with_cores(&cores, |arguments| {
|
||||
calls.push(arguments.to_vec());
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].len(), 5);
|
||||
assert_eq!(calls[0][0], "--install-service");
|
||||
assert_eq!(calls[0][2], core.as_os_str());
|
||||
|
||||
let error = super::install_windows_service_with_cores(&[missing, root.path().to_path_buf()], |_| {
|
||||
panic!("no available core must fail before invoking the installer")
|
||||
})
|
||||
.expect_err("installation requires at least one available core");
|
||||
assert!(error.to_string().contains("no core executable"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_service_install_propagates_installer_failure() -> anyhow::Result<()> {
|
||||
let root = TestDirectory::new("install-failure")?;
|
||||
let core = root.path().join("verge-mihomo.exe");
|
||||
std::fs::write(&core, b"abc")?;
|
||||
let mut calls = 0;
|
||||
let result = super::install_windows_service_with_cores(std::slice::from_ref(&core), |_| {
|
||||
calls += 1;
|
||||
bail!("installer failed with exit code 1")
|
||||
});
|
||||
let error = result.expect_err("installation failure must not be reported as success");
|
||||
assert!(format!("{error:#}").contains("exit code 1"));
|
||||
assert!(error.to_string().contains("Repair"));
|
||||
assert_eq!(calls, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn staging_directory(home: &Path) -> PathBuf {
|
||||
home.join("Applications/.clash-verge-rev-dev/service-core")
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ export const SystemInfoCard = () => {
|
||||
goToSettings()
|
||||
}
|
||||
} catch (err) {
|
||||
showNotice.error(err)
|
||||
console.warn('update check failed:', err)
|
||||
showNotice.warning(err)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -54,8 +54,9 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
|
||||
} else {
|
||||
updateRef.current?.open()
|
||||
}
|
||||
} catch (err: any) {
|
||||
showNotice.error(err)
|
||||
} catch (err) {
|
||||
console.warn('update check failed:', err)
|
||||
showNotice.warning(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ReactNode, isValidElement } from 'react'
|
||||
import type { FailedOperation } from '@/services/cmds'
|
||||
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
||||
|
||||
type NoticeType = 'success' | 'error' | 'info'
|
||||
type NoticeType = 'success' | 'error' | 'info' | 'warning'
|
||||
|
||||
interface NoticeTranslationDescriptor {
|
||||
key: string
|
||||
@@ -47,6 +47,7 @@ type ShowNotice = ((
|
||||
success: NoticeShortcut
|
||||
error: NoticeShortcut
|
||||
info: NoticeShortcut
|
||||
warning: NoticeShortcut
|
||||
}
|
||||
|
||||
type NoticeSubscriber = () => void
|
||||
@@ -54,6 +55,7 @@ type NoticeSubscriber = () => void
|
||||
const DEFAULT_DURATIONS: Readonly<Record<NoticeType, number>> = {
|
||||
success: 3000,
|
||||
info: 5000,
|
||||
warning: 6000,
|
||||
error: 8000,
|
||||
}
|
||||
|
||||
@@ -509,6 +511,8 @@ export const showNotice: ShowNotice = Object.assign(baseShowNotice, {
|
||||
baseShowNotice('error', message, ...extras),
|
||||
info: (message: NoticeContent, ...extras: NoticeExtra[]) =>
|
||||
baseShowNotice('info', message, ...extras),
|
||||
warning: (message: NoticeContent, ...extras: NoticeExtra[]) =>
|
||||
baseShowNotice('warning', message, ...extras),
|
||||
})
|
||||
|
||||
export function hideNotice(id: number) {
|
||||
|
||||
Reference in New Issue
Block a user