mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-03 17:01:15 +08:00
fix(desktop): stop sidecars before updater install
This commit is contained in:
parent
0ef020ebf8
commit
55c5958b55
4
.github/workflows/release-desktop.yml
vendored
4
.github/workflows/release-desktop.yml
vendored
@ -55,7 +55,7 @@ jobs:
|
|||||||
# Windows x64
|
# Windows x64
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
rust_target: x86_64-pc-windows-msvc
|
rust_target: x86_64-pc-windows-msvc
|
||||||
tauri_args: '--bundles msi'
|
tauri_args: '--bundles nsis'
|
||||||
label: Windows-x64
|
label: Windows-x64
|
||||||
asset_name_pattern: 'Claude-Code-Haha_[version]_windows_x64_[bundle][ext]'
|
asset_name_pattern: 'Claude-Code-Haha_[version]_windows_x64_[bundle][ext]'
|
||||||
|
|
||||||
@ -176,6 +176,6 @@ jobs:
|
|||||||
releaseBody: ${{ steps.release_notes.outputs.body }}
|
releaseBody: ${{ steps.release_notes.outputs.body }}
|
||||||
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }}
|
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
updaterJsonPreferNsis: false
|
updaterJsonPreferNsis: true
|
||||||
releaseAssetNamePattern: ${{ matrix.asset_name_pattern }}
|
releaseAssetNamePattern: ${{ matrix.asset_name_pattern }}
|
||||||
args: ${{ matrix.tauri_args }} --config src-tauri/tauri.release-ci.json
|
args: ${{ matrix.tauri_args }} --config src-tauri/tauri.release-ci.json
|
||||||
|
|||||||
@ -112,6 +112,22 @@ fn restart_adapters_sidecar(app: AppHandle) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn prepare_for_update_install(app: AppHandle) -> Result<(), String> {
|
||||||
|
stop_server_sidecar(&app);
|
||||||
|
stop_adapters_sidecar(&app);
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
kill_windows_sidecars();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give Windows a short moment to release executable file handles before the
|
||||||
|
// updater starts replacing bundled sidecars in the install directory.
|
||||||
|
std::thread::sleep(Duration::from_millis(750));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn terminal_spawn(
|
fn terminal_spawn(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
@ -721,6 +737,21 @@ fn stop_adapters_sidecar(app: &AppHandle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn kill_windows_sidecars() {
|
||||||
|
for image_name in [
|
||||||
|
"claude-sidecar-x86_64-pc-windows-msvc.exe",
|
||||||
|
"claude-sidecar-aarch64-pc-windows-msvc.exe",
|
||||||
|
"claude-sidecar.exe",
|
||||||
|
] {
|
||||||
|
let _ = StdCommand::new("taskkill")
|
||||||
|
.args(["/F", "/T", "/IM", image_name])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{decode_terminal_output, default_utf8_locale, ensure_utf8_locale, parse_env_block};
|
use super::{decode_terminal_output, default_utf8_locale, ensure_utf8_locale, parse_env_block};
|
||||||
@ -817,6 +848,7 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
get_server_url,
|
get_server_url,
|
||||||
restart_adapters_sidecar,
|
restart_adapters_sidecar,
|
||||||
|
prepare_for_update_install,
|
||||||
terminal_spawn,
|
terminal_spawn,
|
||||||
terminal_write,
|
terminal_write,
|
||||||
terminal_resize,
|
terminal_resize,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
!macro NSIS_HOOK_PREINSTALL
|
!macro NSIS_HOOK_PREINSTALL
|
||||||
DetailPrint "Stopping running Claude Code Haha processes..."
|
DetailPrint "Stopping running Claude Code Haha sidecars..."
|
||||||
nsExec::ExecToLog 'taskkill /F /T /IM claude-code-desktop.exe'
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar-x86_64-pc-windows-msvc.exe'
|
||||||
|
Pop $0
|
||||||
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar-aarch64-pc-windows-msvc.exe'
|
||||||
Pop $0
|
Pop $0
|
||||||
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar.exe'
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar.exe'
|
||||||
Pop $0
|
Pop $0
|
||||||
@ -11,6 +13,10 @@
|
|||||||
DetailPrint "Stopping running Claude Code Haha processes..."
|
DetailPrint "Stopping running Claude Code Haha processes..."
|
||||||
nsExec::ExecToLog 'taskkill /F /T /IM claude-code-desktop.exe'
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-code-desktop.exe'
|
||||||
Pop $0
|
Pop $0
|
||||||
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar-x86_64-pc-windows-msvc.exe'
|
||||||
|
Pop $0
|
||||||
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar-aarch64-pc-windows-msvc.exe'
|
||||||
|
Pop $0
|
||||||
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar.exe'
|
nsExec::ExecToLog 'taskkill /F /T /IM claude-sidecar.exe'
|
||||||
Pop $0
|
Pop $0
|
||||||
Sleep 1000
|
Sleep 1000
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
|
|
||||||
const check = vi.fn()
|
const check = vi.fn()
|
||||||
const relaunch = vi.fn()
|
const relaunch = vi.fn()
|
||||||
|
const invoke = vi.fn()
|
||||||
|
|
||||||
vi.mock('@tauri-apps/plugin-updater', () => ({
|
vi.mock('@tauri-apps/plugin-updater', () => ({
|
||||||
check,
|
check,
|
||||||
@ -11,10 +12,15 @@ vi.mock('@tauri-apps/plugin-process', () => ({
|
|||||||
relaunch,
|
relaunch,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@tauri-apps/api/core', () => ({
|
||||||
|
invoke,
|
||||||
|
}))
|
||||||
|
|
||||||
describe('updateStore', () => {
|
describe('updateStore', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
check.mockReset()
|
check.mockReset()
|
||||||
relaunch.mockReset()
|
relaunch.mockReset()
|
||||||
|
invoke.mockReset()
|
||||||
window.localStorage.clear()
|
window.localStorage.clear()
|
||||||
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
@ -89,20 +95,23 @@ describe('updateStore', () => {
|
|||||||
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
|
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('computes download progress from content length and relaunches after install', async () => {
|
it('downloads, stops sidecars, installs, and relaunches', async () => {
|
||||||
const downloadAndInstall = vi.fn(async (onEvent?: (event: unknown) => void) => {
|
const download = vi.fn(async (onEvent?: (event: unknown) => void) => {
|
||||||
onEvent?.({ event: 'Started', data: { contentLength: 200 } })
|
onEvent?.({ event: 'Started', data: { contentLength: 200 } })
|
||||||
onEvent?.({ event: 'Progress', data: { chunkLength: 50 } })
|
onEvent?.({ event: 'Progress', data: { chunkLength: 50 } })
|
||||||
onEvent?.({ event: 'Progress', data: { chunkLength: 150 } })
|
onEvent?.({ event: 'Progress', data: { chunkLength: 150 } })
|
||||||
onEvent?.({ event: 'Finished' })
|
onEvent?.({ event: 'Finished' })
|
||||||
})
|
})
|
||||||
|
const install = vi.fn().mockResolvedValue(undefined)
|
||||||
|
|
||||||
check.mockResolvedValue({
|
check.mockResolvedValue({
|
||||||
version: '0.2.0',
|
version: '0.2.0',
|
||||||
body: 'Notes',
|
body: 'Notes',
|
||||||
downloadAndInstall,
|
download,
|
||||||
|
install,
|
||||||
close: vi.fn().mockResolvedValue(undefined),
|
close: vi.fn().mockResolvedValue(undefined),
|
||||||
})
|
})
|
||||||
|
invoke.mockResolvedValue(undefined)
|
||||||
relaunch.mockResolvedValue(undefined)
|
relaunch.mockResolvedValue(undefined)
|
||||||
|
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
@ -111,7 +120,14 @@ describe('updateStore', () => {
|
|||||||
await useUpdateStore.getState().checkForUpdates()
|
await useUpdateStore.getState().checkForUpdates()
|
||||||
await useUpdateStore.getState().installUpdate()
|
await useUpdateStore.getState().installUpdate()
|
||||||
|
|
||||||
expect(downloadAndInstall).toHaveBeenCalledTimes(1)
|
expect(download).toHaveBeenCalledTimes(1)
|
||||||
|
expect(invoke).toHaveBeenCalledWith('prepare_for_update_install')
|
||||||
|
expect(install).toHaveBeenCalledTimes(1)
|
||||||
|
const prepareCallOrder = invoke.mock.invocationCallOrder[0]
|
||||||
|
const installCallOrder = install.mock.invocationCallOrder[0]
|
||||||
|
expect(prepareCallOrder).toBeDefined()
|
||||||
|
expect(installCallOrder).toBeDefined()
|
||||||
|
expect(prepareCallOrder!).toBeLessThan(installCallOrder!)
|
||||||
expect(useUpdateStore.getState().progressPercent).toBe(100)
|
expect(useUpdateStore.getState().progressPercent).toBe(100)
|
||||||
expect(useUpdateStore.getState().status).toBe('restarting')
|
expect(useUpdateStore.getState().status).toBe('restarting')
|
||||||
expect(relaunch).toHaveBeenCalledTimes(1)
|
expect(relaunch).toHaveBeenCalledTimes(1)
|
||||||
|
|||||||
@ -191,11 +191,12 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
writeDismissedUpdateVersion(null)
|
writeDismissedUpdateVersion(null)
|
||||||
|
const { invoke } = await import('@tauri-apps/api/core')
|
||||||
const { relaunch } = await import('@tauri-apps/plugin-process')
|
const { relaunch } = await import('@tauri-apps/plugin-process')
|
||||||
let totalBytes: number | null = null
|
let totalBytes: number | null = null
|
||||||
let downloadedBytes = 0
|
let downloadedBytes = 0
|
||||||
|
|
||||||
await update.downloadAndInstall((event) => {
|
await update.download((event) => {
|
||||||
if (event.event === 'Started') {
|
if (event.event === 'Started') {
|
||||||
totalBytes = event.data.contentLength ?? null
|
totalBytes = event.data.contentLength ?? null
|
||||||
downloadedBytes = 0
|
downloadedBytes = 0
|
||||||
@ -226,6 +227,9 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await invoke('prepare_for_update_install')
|
||||||
|
await update.install()
|
||||||
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
...state,
|
...state,
|
||||||
status: 'restarting',
|
status: 'restarting',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user