From 6f2f7ed5c2ae648679886b8a54661f15f9367d67 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Tue, 4 Aug 2026 00:24:38 +0100 Subject: [PATCH] Enhance app update process with improved restart handling and Windows launcher resolution - Added a delay in the app update process to ensure the UI reflects completion before quitting. - Updated the Windows launcher resolution logic to prefer the installed launcher, improving post-update behavior. - Refactored the restart script to use `.cmd` extension and optimized process handling for better reliability. --- src/desktop/appupdate.js | 2 ++ src/desktop/updater-runner.js | 57 ++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/desktop/appupdate.js b/src/desktop/appupdate.js index 93084a1..c36518b 100644 --- a/src/desktop/appupdate.js +++ b/src/desktop/appupdate.js @@ -207,6 +207,8 @@ const launchInstallerAndRestart = async ( } scheduleAppRestart(); + // Give the detached restart watcher a moment to start, and let the UI show 100%. + await new Promise((resolve) => setTimeout(resolve, 1000)); Utils.quit(); }; diff --git a/src/desktop/updater-runner.js b/src/desktop/updater-runner.js index 22ef8b6..e9dbd4a 100644 --- a/src/desktop/updater-runner.js +++ b/src/desktop/updater-runner.js @@ -77,6 +77,20 @@ export const resolveAppLaunchPath = () => { } if (process.platform === 'win32') { + // Prefer the installed launcher so post-update restart targets the new binary + // (in-app updates may leave the running process under bin.old). + const installDir = readWindowsInstallDirFromRegistry() + if (installDir) { + const launcherFromRegistry = path.join(installDir, 'bin', 'launcher.exe') + if (existsSync(launcherFromRegistry)) { + return launcherFromRegistry + } + } + + if (existsSync(WINDOWS_DEFAULT_LAUNCHER)) { + return WINDOWS_DEFAULT_LAUNCHER + } + const execBase = path.basename(process.execPath).toLowerCase() if (execBase === 'launcher.exe') { return process.execPath @@ -96,18 +110,6 @@ export const resolveAppLaunchPath = () => { return launcherInBin } - const installDir = readWindowsInstallDirFromRegistry() - if (installDir) { - const launcherFromRegistry = path.join(installDir, 'bin', 'launcher.exe') - if (existsSync(launcherFromRegistry)) { - return launcherFromRegistry - } - } - - if (existsSync(WINDOWS_DEFAULT_LAUNCHER)) { - return WINDOWS_DEFAULT_LAUNCHER - } - return process.execPath } @@ -137,39 +139,46 @@ const spawnDetachedWindowsRestart = ({ appLaunchPath, parentPid }) => { 'FarmControl', 'Updates' ) - const scriptPath = path.join(updateDir, `restart-${parentPid}.bat`) + const scriptPath = path.join(updateDir, `restart-${parentPid}.cmd`) const appLaunchWin = appLaunchPath.replaceAll('/', '\\') const appDirWin = path.dirname(appLaunchWin) + // Separate process: wait until Farm Control's PID exits, then relaunch launcher.exe. const script = `@echo off -setlocal +setlocal EnableExtensions set "PARENT_PID=${parentPid}" set "APP_LAUNCHER=${appLaunchWin}" set "APP_DIR=${appDirWin}" :waitparent -tasklist /FI "PID eq %PARENT_PID%" 2>NUL | find "%PARENT_PID%" >NUL && ( - timeout /t 1 /nobreak >nul +tasklist /FI "PID eq %PARENT_PID%" 2>NUL | findstr /I /C:"%PARENT_PID%" >NUL +if not errorlevel 1 ( + timeout /t 1 /nobreak >NUL goto waitparent ) -timeout /t 2 /nobreak >nul +timeout /t 1 /nobreak >NUL cd /d "%APP_DIR%" start "" "%APP_LAUNCHER%" -ping -n 2 127.0.0.1 >nul -del "%~f0" +timeout /t 1 /nobreak >NUL +del "%~f0" >NUL 2>&1 ` mkdirSync(updateDir, { recursive: true }) writeFileSync(scriptPath, script, 'utf8') - const child = spawn('cmd.exe', ['/c', scriptPath], { - detached: true, - stdio: 'ignore', - windowsHide: true - }) + const child = spawn( + process.env.ComSpec || 'cmd.exe', + ['/d', '/c', scriptPath], + { + detached: true, + stdio: 'ignore', + windowsHide: true, + cwd: updateDir + } + ) child.unref() }