fix: keep machine restore snappy

This commit is contained in:
Federico Jaramillo Martinez
2026-06-09 17:06:18 +02:00
parent 65b4c76513
commit 4bc390a33a
9 changed files with 252 additions and 58 deletions
+43 -31
View File
@@ -1,6 +1,7 @@
import { spawnSync } from "node:child_process";
import { execFile } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { readFile, realpath, stat } from "node:fs/promises";
import { promisify } from "node:util";
import { homedir } from "node:os";
import { dirname, join, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
@@ -33,6 +34,8 @@ interface NativeServiceCommands {
status?: string;
}
const execFileAsync = promisify(execFile);
const serviceRefs: Record<ServiceId, NativeServiceRef> = {
sessiond: {
id: "sessiond",
@@ -126,7 +129,7 @@ export async function getPiWebStatus(daemon: PiWebStatusDaemon = new SessionDaem
const { web, sessiond } = versionStatus.components;
const release = await getLatestReleaseStatus(web.installedVersion ?? web.runtimeVersion ?? DEFAULT_VERSION);
const components = { web, sessiond };
const commands = commandsFor(components);
const commands = await commandsFor(components);
const messages = buildMessages(components, release, commands);
return {
...versionStatus,
@@ -213,18 +216,21 @@ async function detectPiPackageInstallation(realRoot: string, displayPath: string
}
async function detectNpmGlobalInstallation(realRoot: string, displayPath: string): Promise<PiWebInstallationInfo | undefined> {
const npmRoot = npmGlobalRoot();
const npmRoot = await npmGlobalRoot();
if (npmRoot === undefined) return undefined;
const realNpmRoot = await realPathOrSelf(npmRoot);
if (!isSameOrWithin(realNpmRoot, realRoot)) return undefined;
return { kind: "npm-global", path: displayPath, npmRoot };
}
function npmGlobalRoot(): string | undefined {
const result = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
if (result.status !== 0) return undefined;
const root = result.stdout.trim();
return root === "" ? undefined : root;
async function npmGlobalRoot(): Promise<string | undefined> {
try {
const { stdout } = await execFileAsync("npm", ["root", "-g"], { encoding: "utf8" });
const root = stdout.trim();
return root === "" ? undefined : root;
} catch {
return undefined;
}
}
function packageRootPath(): string {
@@ -365,15 +371,17 @@ async function fetchLatestNpmVersion(currentVersion: string): Promise<string> {
return version;
}
function commandsFor(components: PiWebStatusResponse["components"]): PiWebStatusResponse["commands"] {
async function commandsFor(components: PiWebStatusResponse["components"]): Promise<PiWebStatusResponse["commands"]> {
const installation = preferredInstallation(components);
const serviceCommands = nativeServiceCommands();
const cliCommands = piWebCliCommands(installation);
const [serviceCommands, cliCommands] = await Promise.all([
nativeServiceCommands(),
piWebCliCommands(installation),
]);
const restart = restartCommandFor(installation, serviceCommands, cliCommands);
const restartWeb = serviceCommands.restartWeb ?? cliCommands.restart;
const restartSessiond = serviceCommands.restartSessiond ?? cliCommands.restart;
const status = serviceCommands.status ?? cliCommands.status;
const update = updateCommandFor(installation, restart);
const update = await updateCommandFor(installation, restart);
return {
...(update === undefined ? {} : { update }),
@@ -391,8 +399,8 @@ function preferredInstallation(components: PiWebStatusResponse["components"]): P
return web ?? sessiond;
}
function piWebCliCommands(installation: PiWebInstallationInfo | undefined): NativeServiceCommands {
if (installation?.kind !== "npm-global" || !hasCommand("pi-web")) return {};
async function piWebCliCommands(installation: PiWebInstallationInfo | undefined): Promise<NativeServiceCommands> {
if (installation?.kind !== "npm-global" || !(await hasCommand("pi-web"))) return {};
return { restart: "pi-web restart", status: "pi-web status" };
}
@@ -401,22 +409,22 @@ function restartCommandFor(installation: PiWebInstallationInfo | undefined, serv
return cliCommands.restart ?? serviceCommands.restart;
}
function updateCommandFor(installation: PiWebInstallationInfo | undefined, restartCommand: string | undefined): string | undefined {
async function updateCommandFor(installation: PiWebInstallationInfo | undefined, restartCommand: string | undefined): Promise<string | undefined> {
if (restartCommand === undefined) return undefined;
if (installation?.kind === "pi-package") {
if (!hasCommand("pi")) return undefined;
if (!(await hasCommand("pi"))) return undefined;
return `pi update ${installation.source ?? PI_WEB_NPM_SOURCE} && ${restartCommand}`;
}
if (installation?.kind === "local" && installation.path !== undefined) {
if (!hasCommand("npm") || !isGitCheckoutWithUpstream(installation.path)) return undefined;
if (!(await hasCommand("npm")) || !(await isGitCheckoutWithUpstream(installation.path))) return undefined;
return `cd ${shellQuote(installation.path)} && git pull --ff-only && npm install && npm run build && ${restartCommand}`;
}
if (installation?.kind !== "npm-global" || !hasCommand("npm")) return undefined;
if (installation?.kind !== "npm-global" || !(await hasCommand("npm"))) return undefined;
return `npm install -g ${PI_WEB_PACKAGE_NAME} && ${restartCommand}`;
}
function nativeServiceCommands(): NativeServiceCommands {
const backend = nativeServiceBackend();
async function nativeServiceCommands(): Promise<NativeServiceCommands> {
const backend = await nativeServiceBackend();
if (backend === undefined) return {};
const installed = installedServiceIds(backend);
if (installed.size === 0) return {};
@@ -432,9 +440,9 @@ function nativeServiceCommands(): NativeServiceCommands {
};
}
function nativeServiceBackend(): NativeServiceBackendKind | undefined {
if (process.platform === "linux" && hasCommand("systemctl")) return "systemd";
if (process.platform === "darwin" && hasCommand("launchctl")) return "launchd";
async function nativeServiceBackend(): Promise<NativeServiceBackendKind | undefined> {
if (process.platform === "linux" && await hasCommand("systemctl")) return "systemd";
if (process.platform === "darwin" && await hasCommand("launchctl")) return "launchd";
return undefined;
}
@@ -468,19 +476,23 @@ function statusNativeServicesCommand(backend: NativeServiceBackendKind, refs: Na
return refs.map((ref) => `launchctl print gui/$(id -u)/${ref.launchdLabel}`).join(" && ");
}
function isGitCheckoutWithUpstream(path: string): boolean {
return hasCommand("git")
&& commandSucceeds("git", ["-C", path, "rev-parse", "--is-inside-work-tree"])
&& commandSucceeds("git", ["-C", path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
async function isGitCheckoutWithUpstream(path: string): Promise<boolean> {
return await hasCommand("git")
&& await commandSucceeds("git", ["-C", path, "rev-parse", "--is-inside-work-tree"])
&& await commandSucceeds("git", ["-C", path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
}
function hasCommand(command: string): boolean {
function hasCommand(command: string): Promise<boolean> {
return commandSucceeds("/usr/bin/env", ["sh", "-c", `command -v ${command}`]);
}
function commandSucceeds(command: string, args: string[]): boolean {
const result = spawnSync(command, args, { encoding: "utf8" });
return result.status === 0;
async function commandSucceeds(command: string, args: string[]): Promise<boolean> {
try {
await execFileAsync(command, args, { encoding: "utf8" });
return true;
} catch {
return false;
}
}
function shellQuote(value: string): string {