feat: add portable mode support and improve update handling
- Add portable.ini marker file creation in GitHub Actions for portable builds - Implement is_portable_mode() command to detect portable execution - Redirect portable users to GitHub releases page for manual updates - Change update URL to point to latest releases page - Integrate portable mode detection in Settings UI
This commit is contained in:
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -226,6 +226,12 @@ jobs:
|
|||||||
$portableDir = 'release-assets/CC-Switch-Portable'
|
$portableDir = 'release-assets/CC-Switch-Portable'
|
||||||
New-Item -ItemType Directory -Force -Path $portableDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $portableDir | Out-Null
|
||||||
Copy-Item $exePath $portableDir
|
Copy-Item $exePath $portableDir
|
||||||
|
$portableIniPath = Join-Path $portableDir 'portable.ini'
|
||||||
|
$portableContent = @(
|
||||||
|
'# CC Switch portable build marker',
|
||||||
|
'portable=true'
|
||||||
|
)
|
||||||
|
$portableContent | Set-Content -Path $portableIniPath -Encoding UTF8
|
||||||
Compress-Archive -Path "$portableDir/*" -DestinationPath 'release-assets/CC-Switch-Windows-Portable.zip' -Force
|
Compress-Archive -Path "$portableDir/*" -DestinationPath 'release-assets/CC-Switch-Windows-Portable.zip' -Force
|
||||||
Remove-Item -Recurse -Force $portableDir
|
Remove-Item -Recurse -Force $portableDir
|
||||||
Write-Host 'Windows portable zip created'
|
Write-Host 'Windows portable zip created'
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ pub async fn check_for_updates(handle: tauri::AppHandle) -> Result<bool, String>
|
|||||||
handle
|
handle
|
||||||
.opener()
|
.opener()
|
||||||
.open_url(
|
.open_url(
|
||||||
"https://github.com/farion1231/cc-switch/releases",
|
"https://github.com/farion1231/cc-switch/releases/latest",
|
||||||
None::<String>,
|
None::<String>,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("打开更新页面失败: {}", e))?;
|
.map_err(|e| format!("打开更新页面失败: {}", e))?;
|
||||||
@@ -682,6 +682,17 @@ pub async fn check_for_updates(handle: tauri::AppHandle) -> Result<bool, String>
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 判断是否为便携版(绿色版)运行
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn is_portable_mode() -> Result<bool, String> {
|
||||||
|
let exe_path = std::env::current_exe().map_err(|e| format!("获取可执行路径失败: {}", e))?;
|
||||||
|
if let Some(dir) = exe_path.parent() {
|
||||||
|
Ok(dir.join("portable.ini").is_file())
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// VS Code: 获取用户 settings.json 状态
|
/// VS Code: 获取用户 settings.json 状态
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_vscode_settings_status() -> Result<ConfigStatus, String> {
|
pub async fn get_vscode_settings_status() -> Result<ConfigStatus, String> {
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ pub fn run() {
|
|||||||
commands::get_settings,
|
commands::get_settings,
|
||||||
commands::save_settings,
|
commands::save_settings,
|
||||||
commands::check_for_updates,
|
commands::check_for_updates,
|
||||||
|
commands::is_portable_mode,
|
||||||
commands::get_vscode_settings_status,
|
commands::get_vscode_settings_status,
|
||||||
commands::read_vscode_settings,
|
commands::read_vscode_settings,
|
||||||
commands::write_vscode_settings,
|
commands::write_vscode_settings,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
const [resolvedClaudeDir, setResolvedClaudeDir] = useState<string>("");
|
const [resolvedClaudeDir, setResolvedClaudeDir] = useState<string>("");
|
||||||
const [resolvedCodexDir, setResolvedCodexDir] = useState<string>("");
|
const [resolvedCodexDir, setResolvedCodexDir] = useState<string>("");
|
||||||
|
const [isPortable, setIsPortable] = useState(false);
|
||||||
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
||||||
useUpdate();
|
useUpdate();
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
loadConfigPath();
|
loadConfigPath();
|
||||||
loadVersion();
|
loadVersion();
|
||||||
loadResolvedDirs();
|
loadResolvedDirs();
|
||||||
|
loadPortableFlag();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadVersion = async () => {
|
const loadVersion = async () => {
|
||||||
@@ -103,6 +105,15 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadPortableFlag = async () => {
|
||||||
|
try {
|
||||||
|
const portable = await window.api.isPortable();
|
||||||
|
setIsPortable(portable);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("检测便携模式失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async () => {
|
||||||
try {
|
try {
|
||||||
const payload: Settings = {
|
const payload: Settings = {
|
||||||
@@ -126,6 +137,10 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
|
|
||||||
const handleCheckUpdate = async () => {
|
const handleCheckUpdate = async () => {
|
||||||
if (hasUpdate && updateHandle) {
|
if (hasUpdate && updateHandle) {
|
||||||
|
if (isPortable) {
|
||||||
|
await window.api.checkForUpdates();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 已检测到更新:直接复用 updateHandle 下载并安装,避免重复检查
|
// 已检测到更新:直接复用 updateHandle 下载并安装,避免重复检查
|
||||||
setIsDownloading(true);
|
setIsDownloading(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -246,6 +246,16 @@ export const tauriAPI = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 判断是否为便携模式
|
||||||
|
isPortable: async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
return await invoke<boolean>("is_portable_mode");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("检测便携模式失败:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 获取应用配置文件路径
|
// 获取应用配置文件路径
|
||||||
getAppConfigPath: async (): Promise<string> => {
|
getAppConfigPath: async (): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -39,6 +39,7 @@ declare global {
|
|||||||
getSettings: () => Promise<Settings>;
|
getSettings: () => Promise<Settings>;
|
||||||
saveSettings: (settings: Settings) => Promise<boolean>;
|
saveSettings: (settings: Settings) => Promise<boolean>;
|
||||||
checkForUpdates: () => Promise<void>;
|
checkForUpdates: () => Promise<void>;
|
||||||
|
isPortable: () => Promise<boolean>;
|
||||||
getAppConfigPath: () => Promise<string>;
|
getAppConfigPath: () => Promise<string>;
|
||||||
openAppConfigFolder: () => Promise<void>;
|
openAppConfigFolder: () => Promise<void>;
|
||||||
// VS Code settings.json 能力
|
// VS Code settings.json 能力
|
||||||
|
|||||||
Reference in New Issue
Block a user