Compare commits
2 Commits
4caa7a4bd1
...
9ef02304fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ef02304fe | |||
| 30f0ac1559 |
@ -7,6 +7,7 @@ import process from "node:process";
|
||||
import { Utils } from "electrobun/bun";
|
||||
import { launchMacInstaller } from "./macappupdate.js";
|
||||
import { launchWindowsInstaller } from "./winappupdate.js";
|
||||
import { scheduleAppRestart } from "./updater-runner.js";
|
||||
|
||||
const SUPPORTED_TARGETS = {
|
||||
darwin: {
|
||||
@ -172,11 +173,7 @@ const downloadArtifact = async (artifact, destinationPath, sendProgress) => {
|
||||
});
|
||||
};
|
||||
|
||||
const restartApp = () => {
|
||||
Utils.quit();
|
||||
};
|
||||
|
||||
const launchInstallerAndQuit = async (
|
||||
const launchInstallerAndRestart = async (
|
||||
mainWindow,
|
||||
installerPath,
|
||||
sendProgress,
|
||||
@ -184,23 +181,25 @@ const launchInstallerAndQuit = async (
|
||||
const installerHelpers = { sendProgress, getInstallErrorMessage };
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
await launchMacInstaller(mainWindow, installerPath, sendProgress, installerHelpers);
|
||||
restartApp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
await launchMacInstaller(
|
||||
mainWindow,
|
||||
installerPath,
|
||||
sendProgress,
|
||||
installerHelpers,
|
||||
);
|
||||
} else if (process.platform === "win32") {
|
||||
await launchWindowsInstaller(
|
||||
mainWindow,
|
||||
installerPath,
|
||||
sendProgress,
|
||||
installerHelpers,
|
||||
);
|
||||
restartApp();
|
||||
return;
|
||||
} else {
|
||||
throw new Error(`App updates are not supported on ${process.platform}.`);
|
||||
}
|
||||
|
||||
throw new Error(`App updates are not supported on ${process.platform}.`);
|
||||
scheduleAppRestart();
|
||||
Utils.quit();
|
||||
};
|
||||
|
||||
const runAppUpdate = async (mainWindow, update, sendProgress) => {
|
||||
@ -229,7 +228,7 @@ const runAppUpdate = async (mainWindow, update, sendProgress) => {
|
||||
message: "Update downloaded",
|
||||
});
|
||||
|
||||
await launchInstallerAndQuit(mainWindow, installerPath, sendProgress);
|
||||
await launchInstallerAndRestart(mainWindow, installerPath, sendProgress);
|
||||
};
|
||||
|
||||
export function startAppUpdate(mainWindow, update, sendProgress) {
|
||||
|
||||
193
src/desktop/updater-runner.js
Normal file
193
src/desktop/updater-runner.js
Normal file
@ -0,0 +1,193 @@
|
||||
import { execSync, spawn } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
|
||||
const MAC_APP_NAME = 'Farm Control.app'
|
||||
const WINDOWS_INSTALL_DIR_KEY =
|
||||
'HKLM\\Software\\Tom Butcher\\Farm Control'
|
||||
const WINDOWS_DEFAULT_LAUNCHER = path.join(
|
||||
process.env.ProgramFiles || 'C:\\Program Files',
|
||||
'Farm Control',
|
||||
'bin',
|
||||
'launcher.exe'
|
||||
)
|
||||
|
||||
const quoteBatchArg = (value) => `"${String(value).replaceAll('"', '""')}"`
|
||||
|
||||
const findMacAppBundle = (startPath) => {
|
||||
let current = path.resolve(startPath)
|
||||
|
||||
while (current !== path.dirname(current)) {
|
||||
if (current.endsWith('.app')) {
|
||||
return current
|
||||
}
|
||||
|
||||
const baseName = path.basename(current)
|
||||
if (baseName === 'Contents') {
|
||||
const bundlePath = path.dirname(current)
|
||||
if (bundlePath.endsWith('.app')) {
|
||||
return bundlePath
|
||||
}
|
||||
}
|
||||
|
||||
current = path.dirname(current)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const readWindowsInstallDirFromRegistry = () => {
|
||||
try {
|
||||
const output = execSync(
|
||||
`reg query ${quoteBatchArg(WINDOWS_INSTALL_DIR_KEY)} /v InstallDir`,
|
||||
{ encoding: 'utf8', windowsHide: true }
|
||||
)
|
||||
const match = output.match(/InstallDir\s+REG_\w+\s+(.+)/i)
|
||||
return match?.[1]?.trim() || null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const resolveAppLaunchPath = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
const bundleFromExec = findMacAppBundle(process.execPath)
|
||||
if (bundleFromExec) {
|
||||
return bundleFromExec
|
||||
}
|
||||
|
||||
const applicationsPath = path.join('/Applications', MAC_APP_NAME)
|
||||
if (existsSync(applicationsPath)) {
|
||||
return applicationsPath
|
||||
}
|
||||
|
||||
return process.execPath
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const execBase = path.basename(process.execPath).toLowerCase()
|
||||
if (execBase === 'launcher.exe') {
|
||||
return process.execPath
|
||||
}
|
||||
|
||||
const launcherBesideExec = path.join(path.dirname(process.execPath), 'launcher.exe')
|
||||
if (existsSync(launcherBesideExec)) {
|
||||
return launcherBesideExec
|
||||
}
|
||||
|
||||
const launcherInBin = path.join(
|
||||
path.dirname(process.execPath),
|
||||
'bin',
|
||||
'launcher.exe'
|
||||
)
|
||||
if (existsSync(launcherInBin)) {
|
||||
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
|
||||
}
|
||||
|
||||
throw new Error(`App updates are not supported on ${process.platform}.`)
|
||||
}
|
||||
|
||||
const spawnDetachedMacRestart = ({ appLaunchPath, parentPid }) => {
|
||||
const child = spawn(
|
||||
'/bin/sh',
|
||||
[
|
||||
'-c',
|
||||
`while kill -0 ${parentPid} 2>/dev/null; do sleep 0.5; done; sleep 1; /usr/bin/open ${JSON.stringify(appLaunchPath)}`
|
||||
],
|
||||
{
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
}
|
||||
)
|
||||
child.unref()
|
||||
}
|
||||
|
||||
const spawnDetachedWindowsRestart = ({ appLaunchPath, parentPid }) => {
|
||||
const updateDir = path.join(
|
||||
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
||||
'FarmControl',
|
||||
'Updates'
|
||||
)
|
||||
const scriptPath = path.join(updateDir, `restart-${parentPid}.bat`)
|
||||
const appLaunchWin = appLaunchPath.replaceAll('/', '\\')
|
||||
const scriptWin = scriptPath.replaceAll('/', '\\')
|
||||
|
||||
const script = `@echo off
|
||||
setlocal
|
||||
|
||||
set "PARENT_PID=${parentPid}"
|
||||
set "APP_LAUNCHER=${appLaunchWin}"
|
||||
|
||||
:waitparent
|
||||
tasklist /FI "PID eq %PARENT_PID%" 2>NUL | find /I "%PARENT_PID%" >NUL && (
|
||||
timeout /t 1 /nobreak >nul
|
||||
goto waitparent
|
||||
)
|
||||
|
||||
:waitprocesses
|
||||
tasklist /FI "IMAGENAME eq launcher.exe" 2>NUL | find /I /N "launcher.exe">NUL && goto waitsleep
|
||||
tasklist /FI "IMAGENAME eq bun.exe" 2>NUL | find /I /N "bun.exe">NUL && goto waitsleep
|
||||
tasklist 2>NUL | find /I "bun Helper">NUL && goto waitsleep
|
||||
goto waitdone
|
||||
:waitsleep
|
||||
timeout /t 1 /nobreak >nul
|
||||
goto waitprocesses
|
||||
:waitdone
|
||||
|
||||
timeout /t 2 /nobreak >nul
|
||||
start "" "%APP_LAUNCHER%"
|
||||
|
||||
for /f "tokens=1" %%t in ('schtasks /query /fo list ^| findstr /i "FarmControlRestart_"') do (
|
||||
schtasks /delete /tn "%%t" /f >nul 2>&1
|
||||
)
|
||||
|
||||
ping -n 2 127.0.0.1 >nul
|
||||
del "%~f0"
|
||||
`
|
||||
|
||||
mkdirSync(updateDir, { recursive: true })
|
||||
writeFileSync(scriptPath, script, 'utf8')
|
||||
|
||||
const taskName = `FarmControlRestart_${parentPid}_${Date.now()}`
|
||||
execSync(
|
||||
`schtasks /create /tn ${quoteBatchArg(taskName)} /tr ${quoteBatchArg(`cmd /c "${scriptWin}"`)} /sc once /st 00:00 /f`,
|
||||
{ stdio: 'ignore', windowsHide: true }
|
||||
)
|
||||
execSync(`schtasks /run /tn ${quoteBatchArg(taskName)}`, {
|
||||
stdio: 'ignore',
|
||||
windowsHide: true
|
||||
})
|
||||
}
|
||||
|
||||
export const scheduleAppRestart = ({ parentPid = process.pid } = {}) => {
|
||||
const appLaunchPath = resolveAppLaunchPath()
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
spawnDetachedMacRestart({ appLaunchPath, parentPid })
|
||||
return { appLaunchPath }
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
spawnDetachedWindowsRestart({ appLaunchPath, parentPid })
|
||||
return { appLaunchPath }
|
||||
}
|
||||
|
||||
throw new Error(`App updates are not supported on ${process.platform}.`)
|
||||
}
|
||||
@ -144,7 +144,7 @@ const isValidMsiPackage = async (filePath) => {
|
||||
}
|
||||
}
|
||||
|
||||
const prepareInstallerPath = async (installerPath) => {
|
||||
export const prepareInstallerPath = async (installerPath) => {
|
||||
const fileName = path.basename(installerPath)
|
||||
const updateDir = path.join(
|
||||
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
||||
|
||||
@ -124,7 +124,7 @@ export function handleDeepLink(url) {
|
||||
return false
|
||||
}
|
||||
|
||||
openInternalUrl(path)
|
||||
sendNavigateToRenderer(path)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user