From 7316e932f0ce1c3c710f17a2dbbde034123fd8bf Mon Sep 17 00:00:00 2001 From: Dokril Date: Wed, 8 Jul 2026 14:56:38 +0300 Subject: [PATCH] Bump release version and snapshot startup state --- package-lock.json | 4 +- package.json | 2 +- scripts/prepare-release.ps1 | 9 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/adapters/singbox.rs | 4 +- src-tauri/src/commands.rs | 86 ++++++++++++++++++-- src-tauri/src/component_detection.rs | 6 +- src-tauri/src/main.rs | 2 + src-tauri/src/process.rs | 18 ++++ src-tauri/tauri.conf.json | 2 +- src-tauri/tests/command_tests.rs | 2 + src-tauri/tests/component_detection_tests.rs | 2 + src-tauri/tests/singbox_adapter_tests.rs | 2 + src-tauri/tests/singbox_command_tests.rs | 2 + src-tauri/tests/singbox_service_tests.rs | 2 + src/api/tauriCommands.ts | 13 +++ src/app/App.tsx | 61 ++++---------- 18 files changed, 149 insertions(+), 72 deletions(-) create mode 100644 src-tauri/src/process.rs diff --git a/package-lock.json b/package-lock.json index b5a7228..d49f732 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "proxywarden", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "proxywarden", - "version": "0.1.0", + "version": "1.0.0", "dependencies": { "@fontsource-variable/jetbrains-mono": "^5.2.8", "@tauri-apps/api": "^2.0.0", diff --git a/package.json b/package.json index 7e59a4b..69b1654 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxywarden", - "version": "0.1.0", + "version": "1.0.0", "private": true, "type": "module", "description": "Standalone Windows desktop proxy management app for ProxyWarden.", diff --git a/scripts/prepare-release.ps1 b/scripts/prepare-release.ps1 index 108e26c..e3dda5c 100644 --- a/scripts/prepare-release.ps1 +++ b/scripts/prepare-release.ps1 @@ -353,13 +353,12 @@ function New-ReleaseDirectory { $releaseDir = Join-Path $root "proxywarden-v$TargetVersion" - if ((Test-Path -LiteralPath $releaseDir) -and $Force) { + if (Test-Path -LiteralPath $releaseDir) { if (-not (Test-IsSubPath -Parent $root -Child $releaseDir)) { throw "Refusing to remove release directory outside OutputRoot: $releaseDir" } + Write-Host "Replacing existing release directory: $releaseDir" Remove-Item -LiteralPath $releaseDir -Recurse -Force - } elseif (Test-Path -LiteralPath $releaseDir) { - throw "Release directory already exists: $releaseDir. Use -Force to replace it." } New-Item -ItemType Directory -Path (Join-Path $releaseDir "artifacts") -Force | Out-Null @@ -409,10 +408,6 @@ function Invoke-ReleaseBuild { function Copy-ReleaseArtifacts { param([string]$ReleaseDir) - if ($SkipBuild) { - return @() - } - if (-not (Test-Path -LiteralPath $BundleRoot)) { throw "Tauri bundle output was not found: $BundleRoot" } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e0c66e8..83e4f73 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "proxywarden" -version = "0.1.0" +version = "1.0.0" dependencies = [ "base64 0.22.1", "reqwest 0.12.28", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fee977b..40759a6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proxywarden" -version = "0.1.0" +version = "1.0.0" description = "Standalone Windows desktop proxy management app for ProxyWarden." authors = ["ProxyWarden"] edition = "2021" diff --git a/src-tauri/src/adapters/singbox.rs b/src-tauri/src/adapters/singbox.rs index 0bcc83b..3e272e5 100644 --- a/src-tauri/src/adapters/singbox.rs +++ b/src-tauri/src/adapters/singbox.rs @@ -1,10 +1,10 @@ use crate::models::{LocalSingBoxConfig, SubscriptionCache}; +use crate::process::command_no_window; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::{ env, fs, path::Path, - process::Command, time::{SystemTime, UNIX_EPOCH}, }; @@ -215,7 +215,7 @@ impl SingBoxConfigChecker for SingBoxCommandChecker { ) })?; - let output = Command::new(binary_path) + let output = command_no_window(binary_path) .arg("check") .arg("-c") .arg(&config_path) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 3a769a6..cb0a24c 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -20,6 +20,7 @@ use crate::models::{ Profile, ProfileInput, ProfileItem, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol, SubscriptionCache, SubscriptionServer, Target, TargetInput, TargetKind, }; +use crate::process::command_no_window; #[cfg(test)] use crate::proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, ProxiFyreProxy}; #[cfg(test)] @@ -185,6 +186,17 @@ pub struct SavedStateResponse { pub generated_config_path: String, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StartupSnapshotResponse { + pub admin_status: AdminStatusResponse, + pub saved_state: SavedStateResponse, + pub components: Vec, + pub proxifyre_setup_status: ProxiFyreSetupStatusDto, + pub singbox_status: LocalSingBoxStatusResponse, + pub singbox_setup_status: SingBoxSetupStatusDto, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProxiFyreSetupStatusDto { @@ -563,6 +575,16 @@ pub fn restart_as_admin(app: tauri::AppHandle) -> Result<(), CommandError> { Ok(()) } +#[tauri::command] +pub async fn get_startup_snapshot( + state: tauri::State<'_, CommandState>, +) -> Result { + let storage = state.storage(); + tauri::async_runtime::spawn_blocking(move || read_startup_snapshot(&storage)) + .await + .map_err(background_task_error)? +} + #[tauri::command] pub fn get_saved_state( state: tauri::State<'_, CommandState>, @@ -1011,6 +1033,41 @@ pub fn read_components(storage: &JsonStorage) -> Result, }) } +pub fn read_startup_snapshot( + storage: &JsonStorage, +) -> Result { + let detected_proxyfier = detect_proxyfier_install(); + let detected_singbox = detect_singbox_install(); + let saved_state = read_saved_state_with_proxifyre_config( + storage, + detected_proxyfier + .as_ref() + .and_then(|detected| detected.config_path.as_deref()), + )?; + let stored_components = storage.read_components().map_err(storage_error)?; + let components = resolve_component_statuses( + stored_components, + detected_proxyfier.clone(), + detected_singbox.clone(), + ) + .iter() + .map(ComponentStatusDto::from) + .collect(); + let proxifyre_setup_status = + build_proxifyre_setup_status_with_detection(detected_proxyfier.as_ref()); + let singbox_status = read_singbox_status_with_detection(storage, detected_singbox.as_ref())?; + let singbox_setup_status = build_singbox_setup_status(detected_singbox.as_ref()); + + Ok(StartupSnapshotResponse { + admin_status: admin_status(), + saved_state, + components, + proxifyre_setup_status, + singbox_status, + singbox_setup_status, + }) +} + pub fn read_activity(storage: &JsonStorage) -> Result, CommandError> { storage .read_activity() @@ -1315,13 +1372,20 @@ pub fn apply_profiles_with_services( pub fn read_singbox_status( storage: &JsonStorage, +) -> Result { + let detected = detect_singbox_install(); + read_singbox_status_with_detection(storage, detected.as_ref()) +} + +fn read_singbox_status_with_detection( + storage: &JsonStorage, + detected: Option<&DetectedSingBox>, ) -> Result { let config = storage.read_local_singbox_config().map_err(storage_error)?; let cache = storage .read_singbox_subscription_cache() .map_err(storage_error)?; - let detected = detect_singbox_install(); - let component = singbox_component_from_detection(detected.as_ref()); + let component = singbox_component_from_detection(detected); Ok(LocalSingBoxStatusResponse { config: LocalSingBoxConfigDto::from(&config), @@ -1968,7 +2032,7 @@ fn control_singbox_service( config_source, config_target.as_deref(), ); - let output = Command::new("powershell") + let output = command_no_window("powershell") .args([ "-NoProfile", "-NonInteractive", @@ -2824,7 +2888,7 @@ Write-ServiceResult $false 'stop_failed' $status $processId "# ); - let output = Command::new("powershell") + let output = command_no_window("powershell") .args([ "-NoProfile", "-NonInteractive", @@ -3086,9 +3150,15 @@ fn uninstall_proxifyre_component() -> Result { } fn build_proxifyre_setup_status() -> ProxiFyreSetupStatusDto { + let proxifyre = detect_proxyfier_install(); + build_proxifyre_setup_status_with_detection(proxifyre.as_ref()) +} + +fn build_proxifyre_setup_status_with_detection( + proxifyre: Option<&DetectedProxyfier>, +) -> ProxiFyreSetupStatusDto { let vc_runtime = detect_vc_runtime(); let packet_filter = detect_windows_packet_filter(); - let proxifyre = detect_proxyfier_install(); let vc_runtime_item = setup_item_from_program( "vc-runtime", @@ -3199,7 +3269,7 @@ if ($null -ne $program) {{ escape_powershell_single(pattern) ); - let output = Command::new("powershell") + let output = command_no_window("powershell") .args([ "-NoProfile", "-NonInteractive", @@ -3379,7 +3449,7 @@ fn write_powershell_script(path: &Path, script: &str) -> std::io::Result<()> { } fn run_powershell_command(script: &str) -> std::io::Result { - Command::new("powershell") + command_no_window("powershell") .args([ "-NoProfile", "-NonInteractive", @@ -3392,7 +3462,7 @@ fn run_powershell_command(script: &str) -> std::io::Result { } fn run_powershell_file(script_path: &Path) -> std::io::Result { - Command::new("powershell") + command_no_window("powershell") .args([ "-NoProfile", "-NonInteractive", diff --git a/src-tauri/src/component_detection.rs b/src-tauri/src/component_detection.rs index fa6b96f..5b2a859 100644 --- a/src-tauri/src/component_detection.rs +++ b/src-tauri/src/component_detection.rs @@ -2,11 +2,11 @@ use crate::models::{ ComponentId, ComponentState, ComponentStatus, DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT, DEFAULT_LOCAL_SINGBOX_SERVICE_NAME, }; +use crate::process::command_no_window; use serde::Deserialize; use std::{ env, path::{Path, PathBuf}, - process::Command, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -460,7 +460,7 @@ fn same_path(left: &Path, right: &Path) -> bool { } fn powershell_bool(script: &str) -> bool { - Command::new("powershell") + command_no_window("powershell") .args(["-NoProfile", "-NonInteractive", "-Command", script]) .output() .ok() @@ -492,7 +492,7 @@ $items | ConvertTo-Json -Compress "#; - let Ok(output) = Command::new("powershell") + let Ok(output) = command_no_window("powershell") .args(["-NoProfile", "-NonInteractive", "-Command", script]) .output() else { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 80c8fc2..aff92f6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,6 +4,7 @@ mod activity; mod commands; mod component_detection; mod models; +mod process; mod singbox_service; mod storage; mod subscription; @@ -38,6 +39,7 @@ fn main() { commands::get_status, commands::get_admin_status, commands::restart_as_admin, + commands::get_startup_snapshot, commands::get_profiles, commands::get_saved_state, commands::save_profile, diff --git a/src-tauri/src/process.rs b/src-tauri/src/process.rs new file mode 100644 index 0000000..fd13ea1 --- /dev/null +++ b/src-tauri/src/process.rs @@ -0,0 +1,18 @@ +use std::{ffi::OsStr, process::Command}; + +pub fn command_no_window(program: impl AsRef) -> Command { + let mut command = Command::new(program); + hide_console_window(&mut command); + command +} + +#[cfg(windows)] +fn hide_console_window(command: &mut Command) { + use std::os::windows::process::CommandExt; + + const CREATE_NO_WINDOW: u32 = 0x08000000; + command.creation_flags(CREATE_NO_WINDOW); +} + +#[cfg(not(windows))] +fn hide_console_window(_command: &mut Command) {} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index aba5d85..defa304 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ProxyWarden", - "version": "0.1.0", + "version": "1.0.0", "identifier": "ru.dokops.proxywarden.windows", "build": { "beforeDevCommand": "npm run dev", diff --git a/src-tauri/tests/command_tests.rs b/src-tauri/tests/command_tests.rs index 97594e8..47f19ad 100644 --- a/src-tauri/tests/command_tests.rs +++ b/src-tauri/tests/command_tests.rs @@ -6,6 +6,8 @@ mod commands; mod component_detection; #[path = "../src/models.rs"] mod models; +#[path = "../src/process.rs"] +mod process; #[path = "../src/adapters/proxifyre.rs"] mod proxifyre; #[path = "../src/adapters/proxy_router.rs"] diff --git a/src-tauri/tests/component_detection_tests.rs b/src-tauri/tests/component_detection_tests.rs index 4b03ce7..ed73dc8 100644 --- a/src-tauri/tests/component_detection_tests.rs +++ b/src-tauri/tests/component_detection_tests.rs @@ -2,6 +2,8 @@ mod component_detection; #[path = "../src/models.rs"] mod models; +#[path = "../src/process.rs"] +mod process; use component_detection::{ detect_proxyfier_install_with_host, detect_singbox_install_with_host, diff --git a/src-tauri/tests/singbox_adapter_tests.rs b/src-tauri/tests/singbox_adapter_tests.rs index 5c7fcfb..45b3ce0 100644 --- a/src-tauri/tests/singbox_adapter_tests.rs +++ b/src-tauri/tests/singbox_adapter_tests.rs @@ -1,5 +1,7 @@ #[path = "../src/models.rs"] mod models; +#[path = "../src/process.rs"] +mod process; #[path = "../src/adapters/proxifyre.rs"] mod proxifyre; #[path = "../src/adapters/proxy_router.rs"] diff --git a/src-tauri/tests/singbox_command_tests.rs b/src-tauri/tests/singbox_command_tests.rs index 4a7eab3..8de747a 100644 --- a/src-tauri/tests/singbox_command_tests.rs +++ b/src-tauri/tests/singbox_command_tests.rs @@ -6,6 +6,8 @@ mod commands; mod component_detection; #[path = "../src/models.rs"] mod models; +#[path = "../src/process.rs"] +mod process; #[path = "../src/adapters/proxifyre.rs"] mod proxifyre; #[path = "../src/adapters/proxy_router.rs"] diff --git a/src-tauri/tests/singbox_service_tests.rs b/src-tauri/tests/singbox_service_tests.rs index 48596ee..125756d 100644 --- a/src-tauri/tests/singbox_service_tests.rs +++ b/src-tauri/tests/singbox_service_tests.rs @@ -2,6 +2,8 @@ mod component_detection; #[path = "../src/models.rs"] mod models; +#[path = "../src/process.rs"] +mod process; #[path = "../src/singbox_service.rs"] mod singbox_service; diff --git a/src/api/tauriCommands.ts b/src/api/tauriCommands.ts index 50a13d0..15829a3 100644 --- a/src/api/tauriCommands.ts +++ b/src/api/tauriCommands.ts @@ -43,6 +43,15 @@ export interface SavedStateResponse { generatedConfigPath: string; } +export interface StartupSnapshotResponse { + adminStatus: AdminStatusResponse; + savedState: SavedStateResponse; + components: ComponentStatus[]; + proxifyreSetupStatus: ProxiFyreSetupStatus; + singboxStatus: LocalSingBoxStatusResponse; + singboxSetupStatus: SingBoxSetupStatus; +} + export interface ProxiFyreSetupItem { id: string; name: string; @@ -150,6 +159,10 @@ export function restartAsAdmin(): Promise { return invoke('restart_as_admin'); } +export function getStartupSnapshot(): Promise { + return invoke('get_startup_snapshot'); +} + export function getSavedState(): Promise { return invoke('get_saved_state'); } diff --git a/src/app/App.tsx b/src/app/App.tsx index de3893f..d059212 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -6,12 +6,12 @@ import { fetchSingBoxSubscription, forgetSingBoxSubscription, generateSingBoxConfig, - getAdminStatus, getComponents, getProxiFyreSetupStatus, getSavedState, getSingBoxSetupStatus, getSingBoxStatus, + getStartupSnapshot, installProxiFyre, installSingBox, pingAllSingBoxServers, @@ -268,11 +268,21 @@ export function App() { }, [activeLogId]); async function refresh() { - void refreshAdminStatus(); setIsLoading(true); + setIsDetectingComponents(true); try { - const saved = await getSavedState(); - applySavedState(saved.profiles, saved.targets, saved.generatedConfigPath); + const snapshot = await getStartupSnapshot(); + setAdminStatus(snapshot.adminStatus); + setComponents(snapshot.components); + setSetupStatus(snapshot.proxifyreSetupStatus); + setSingBoxStatus(snapshot.singboxStatus); + setSingBoxSetupStatus(snapshot.singboxSetupStatus); + applySavedState( + snapshot.savedState.profiles, + snapshot.savedState.targets, + snapshot.savedState.generatedConfigPath, + snapshot.singboxStatus, + ); } catch { showNotice({ kind: 'info', @@ -281,17 +291,7 @@ export function App() { }); } finally { setIsLoading(false); - } - - void refreshComponents(); - } - - async function refreshAdminStatus() { - try { - const status = await getAdminStatus(); - setAdminStatus(status); - } catch { - setAdminStatus(null); + setIsDetectingComponents(false); } } @@ -310,37 +310,6 @@ export function App() { } } - async function refreshComponents() { - setIsDetectingComponents(true); - try { - const [detectedComponents, detectedSetupStatus, detectedSingBoxStatus, detectedSingBoxSetupStatus] = await Promise.all([ - getComponents(), - getProxiFyreSetupStatus(), - getSingBoxStatus(), - getSingBoxSetupStatus(), - ]); - setComponents(detectedComponents); - setSetupStatus(detectedSetupStatus); - setSingBoxStatus(detectedSingBoxStatus); - setSingBoxSetupStatus(detectedSingBoxSetupStatus); - setAppliedSnapshot((current) => { - if (!current || current.routeMode !== 'local-singbox' || current.selectedServerTag) return current; - return { - ...current, - selectedServerTag: normalizeServerTag(detectedSingBoxStatus.config.selectedServerTag), - }; - }); - } catch (error) { - showNotice({ - kind: 'error', - title: 'Компоненты не проверены', - text: errorMessage(error), - }); - } finally { - setIsDetectingComponents(false); - } - } - function applySavedState( profiles: Profile[], targets: Target[],