Archived
fix: keep machine restore snappy
This commit is contained in:
+43
-31
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user