fix: Windows updater — defer file copy to batch script after app exits
All checks were successful
Build & Release / build (push) Successful in 2m56s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MiTHRAL 2026-04-21 23:01:35 -04:00
parent a308aa1987
commit 4f07615b13
2 changed files with 35 additions and 13 deletions

View file

@ -74,6 +74,6 @@ export function setUpdateError(msg: string) {
} }
ipcMain.on("upd-restart", () => { ipcMain.on("upd-restart", () => {
app.relaunch(); if (process.platform !== "win32") app.relaunch();
app.exit(0); app.exit(0);
}); });

View file

@ -1,6 +1,6 @@
import { Notification, app, ipcMain } from "electron"; import { Notification, app, ipcMain } from "electron";
import { exec } from "child_process"; import { exec } from "child_process";
import { createWriteStream, mkdirSync } from "fs"; import { createWriteStream, mkdirSync, writeFileSync } from "fs";
import { dirname, join } from "path"; import { dirname, join } from "path";
import { tmpdir } from "os"; import { tmpdir } from "os";
@ -96,19 +96,41 @@ async function downloadAndInstall(url: string, version: string) {
setUpdateProgress(95); setUpdateProgress(95);
console.log(`[updater] download complete, extracting to ${installDir}`); console.log(`[updater] download complete, extracting to ${installDir}`);
if (process.platform === "win32") {
// Extract zip while the app is still running (no locked files yet)
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const cmd = const cmd = `powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'"`;
process.platform === "win32" exec(cmd, (err, _stdout, stderr) => {
? `powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'; $sub = (Get-ChildItem '${extractDir}' | Select-Object -First 1).FullName; Copy-Item -Recurse -Force \\"$sub\\*\\" '${installDir}'"`
: `unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && rm -f "${installDir}/sanctum" && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`;
exec(cmd, { shell: process.platform === "win32" ? undefined : "/bin/bash" }, (err, _stdout, stderr) => {
if (err) { console.error("[updater] extract failed:", stderr); reject(err); } if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
else resolve(); else resolve();
}); });
}); });
// Write a batch script that runs after we exit: waits, copies, relaunches
const batchPath = join(tmpDir, "apply-update.bat");
const bat = [
"@echo off",
"timeout /t 3 /nobreak >nul",
`for /d %%D in ("${extractDir}\\*") do set SUB=%%D`,
`xcopy /E /Y /I "%SUB%\\*" "${installDir}\\"`,
`start "" "${join(installDir, "sanctum.exe")}"`,
`del "%~f0"`,
].join("\r\n");
writeFileSync(batchPath, bat);
// Launch batch detached so it outlives this process
exec(`cmd /C start /B "" "${batchPath}"`);
setUpdateReady(version); // restart button will just app.exit(0) — batch relaunches
} else {
await new Promise<void>((resolve, reject) => {
const cmd = `unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && rm -f "${installDir}/sanctum" && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`;
exec(cmd, { shell: "/bin/bash" }, (err, _stdout, stderr) => {
if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
else resolve();
});
});
setUpdateReady(version); setUpdateReady(version);
}
} }
function notify(title: string, body: string) { function notify(title: string, body: string) {