mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 02:32:47 +00:00
fix(server): update the running npm installation prefix (#5649)
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import path from "node:path";
|
||||
import {
|
||||
DaemonSelfUpdateInProgressError,
|
||||
DaemonSelfUpdater,
|
||||
type DaemonSelfUpdateRuntime,
|
||||
type DaemonSelfUpdatePhase,
|
||||
} from "./daemon-self-updater.js";
|
||||
import type { CommandResult, NpmGlobalPaseoInstall } from "./npm-global-cli.js";
|
||||
import {
|
||||
DefaultNpmGlobalPaseoCli,
|
||||
type CommandResult,
|
||||
type NpmGlobalPaseoInstall,
|
||||
} from "./npm-global-cli.js";
|
||||
|
||||
interface TestLogger {
|
||||
errors: Array<{ obj: object; msg?: string }>;
|
||||
@@ -100,6 +105,45 @@ async function runUpdate(input: {
|
||||
}
|
||||
|
||||
describe("DaemonSelfUpdater", () => {
|
||||
test("probes and updates the running install's prefix when npm defaults elsewhere", async () => {
|
||||
const prefix = path.resolve("custom npm prefix");
|
||||
const root = process.platform === "win32" ? prefix : path.join(prefix, "lib");
|
||||
const packagePath = path.join(root, "node_modules", "@getpaseo", "cli");
|
||||
const commands: string[][] = [];
|
||||
let version = "0.1.15";
|
||||
const npm = new DefaultNpmGlobalPaseoCli(async (_command, args) => {
|
||||
commands.push(args);
|
||||
if (args[args.indexOf("--prefix") + 1] !== prefix) {
|
||||
return { exitCode: 1, stdout: "{}", stderr: "No CLI in npm's default prefix" };
|
||||
}
|
||||
if (args.includes("install")) version = "0.1.96";
|
||||
return {
|
||||
exitCode: 0,
|
||||
stderr: "",
|
||||
stdout: JSON.stringify({
|
||||
path: root,
|
||||
dependencies: { "@getpaseo/cli": { version, path: packagePath } },
|
||||
}),
|
||||
};
|
||||
});
|
||||
const { result } = await runUpdate({
|
||||
runtime: {
|
||||
npm,
|
||||
installOrigin: {
|
||||
resolveCurrentServerPackageRoot: () =>
|
||||
path.join(packagePath, "node_modules", "@getpaseo", "server"),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({ success: true, error: null, newVersion: "0.1.96" });
|
||||
expect(commands).toHaveLength(3);
|
||||
expect(commands.map((args) => args.slice(-2))).toEqual([
|
||||
["--prefix", prefix],
|
||||
["--prefix", prefix],
|
||||
["--prefix", prefix],
|
||||
]);
|
||||
});
|
||||
|
||||
test("refuses a Desktop-managed daemon without touching npm", async () => {
|
||||
const calls: RuntimeCall[] = [];
|
||||
const runtime = createRuntime({ calls, inspections: [] });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getErrorMessage } from "@getpaseo/protocol/error-utils";
|
||||
import {
|
||||
daemonInstallOriginRuntime,
|
||||
resolveNpmGlobalPrefix,
|
||||
validateDaemonInstallOrigin,
|
||||
type DaemonInstallOriginRuntime,
|
||||
} from "./install-origin.js";
|
||||
@@ -63,7 +64,12 @@ export class DaemonSelfUpdater {
|
||||
this.inProgress = true;
|
||||
try {
|
||||
input.onProgress("starting");
|
||||
const install = await this.runtime.npm.inspect();
|
||||
const npmOptions = {
|
||||
prefix: resolveNpmGlobalPrefix(
|
||||
this.runtime.installOrigin.resolveCurrentServerPackageRoot(),
|
||||
),
|
||||
};
|
||||
const install = await this.runtime.npm.inspect(npmOptions);
|
||||
const unsupportedReason = validateDaemonInstallOrigin(
|
||||
install,
|
||||
input.daemonVersion,
|
||||
@@ -76,7 +82,7 @@ export class DaemonSelfUpdater {
|
||||
input.onProgress("downloading");
|
||||
input.onProgress("installing");
|
||||
|
||||
const result = await this.runtime.npm.installLatest();
|
||||
const result = await this.runtime.npm.installLatest(npmOptions);
|
||||
if (result.exitCode !== 0) {
|
||||
const error =
|
||||
result.stderr.trim() || result.stdout.trim() || `npm exited with code ${result.exitCode}`;
|
||||
@@ -87,7 +93,7 @@ export class DaemonSelfUpdater {
|
||||
return { success: false, error, newVersion: null };
|
||||
}
|
||||
|
||||
const updatedInstall = await this.runtime.npm.inspect().catch((error: unknown) => {
|
||||
const updatedInstall = await this.runtime.npm.inspect(npmOptions).catch((error: unknown) => {
|
||||
input.logger.warn({ err: error }, "Unable to read updated npm package version");
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -15,6 +15,21 @@ export const daemonInstallOriginRuntime: DaemonInstallOriginRuntime = {
|
||||
resolveCurrentServerPackageRoot,
|
||||
};
|
||||
|
||||
export function resolveNpmGlobalPrefix(serverPackageRoot: string | null): string | undefined {
|
||||
if (!serverPackageRoot) return undefined;
|
||||
let directory = serverPackageRoot;
|
||||
let prefix: string | undefined;
|
||||
while (path.dirname(directory) !== directory) {
|
||||
if (path.basename(directory) === "node_modules") {
|
||||
const root = path.dirname(directory);
|
||||
if (process.platform === "win32") prefix = root;
|
||||
else if (path.basename(root) === "lib") prefix = path.dirname(root);
|
||||
}
|
||||
directory = path.dirname(directory);
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
export function validateDaemonInstallOrigin(
|
||||
install: NpmGlobalPaseoInstall,
|
||||
daemonVersion: string | null,
|
||||
|
||||
@@ -50,8 +50,12 @@ export interface NpmGlobalPaseoInstall {
|
||||
}
|
||||
|
||||
export interface NpmGlobalPaseoCli {
|
||||
inspect(): Promise<NpmGlobalPaseoInstall>;
|
||||
installLatest(): Promise<CommandResult>;
|
||||
inspect(options?: NpmGlobalOptions): Promise<NpmGlobalPaseoInstall>;
|
||||
installLatest(options?: NpmGlobalOptions): Promise<CommandResult>;
|
||||
}
|
||||
|
||||
interface NpmGlobalOptions {
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
export type CommandRunner = (
|
||||
@@ -115,10 +119,11 @@ function parseNpmGlobalPaseoInstall(stdout: string): NpmGlobalPaseoInstall | nul
|
||||
export class DefaultNpmGlobalPaseoCli implements NpmGlobalPaseoCli {
|
||||
constructor(private readonly runCommand: CommandRunner = runExternalCommand) {}
|
||||
|
||||
async inspect(): Promise<NpmGlobalPaseoInstall> {
|
||||
async inspect(options: NpmGlobalOptions = {}): Promise<NpmGlobalPaseoInstall> {
|
||||
const prefixArgs = options.prefix ? ["--prefix", options.prefix] : [];
|
||||
const result = await this.runCommand(
|
||||
"npm",
|
||||
["-g", "ls", PASEO_CLI_PACKAGE, "--json", "--depth=0", "--long"],
|
||||
["-g", "ls", PASEO_CLI_PACKAGE, "--json", "--depth=0", "--long", ...prefixArgs],
|
||||
{
|
||||
timeout: NPM_PROBE_TIMEOUT_MS,
|
||||
maxBuffer: NPM_MAX_BUFFER_BYTES,
|
||||
@@ -136,8 +141,9 @@ export class DefaultNpmGlobalPaseoCli implements NpmGlobalPaseoCli {
|
||||
return install;
|
||||
}
|
||||
|
||||
installLatest(): Promise<CommandResult> {
|
||||
return this.runCommand("npm", ["install", "-g", `${PASEO_CLI_PACKAGE}@latest`], {
|
||||
installLatest(options: NpmGlobalOptions = {}): Promise<CommandResult> {
|
||||
const prefixArgs = options.prefix ? ["--prefix", options.prefix] : [];
|
||||
return this.runCommand("npm", ["install", "-g", `${PASEO_CLI_PACKAGE}@latest`, ...prefixArgs], {
|
||||
timeout: NPM_INSTALL_TIMEOUT_MS,
|
||||
maxBuffer: NPM_MAX_BUFFER_BYTES,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user