mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
Name a linked global install as the reason daemon self-update is refused (#6095)
* fix(server): name a linked global install as the reason self-update is refused npm 7 and later print no link flag in `npm ls --json --long`, so a global install made with `npm install -g <dir>` or `npm link` was never detected as linked and self-update reported that the daemon was not running from the npm global install. Detect the link from the install directory being a symlink, the same way npm decides it. Closes #6086 * fix(server): fail the self-update inspection when the install path can't be read An unreadable install path now surfaces as the update error instead of being treated as a normal install. Tests build real install directories for both the linked and the normal case. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, onTestFinished, test } from "vitest";
|
||||
import {
|
||||
DaemonSelfUpdateInProgressError,
|
||||
DaemonSelfUpdater,
|
||||
@@ -106,9 +108,11 @@ 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 prefix = mkdtempSync(path.join(tmpdir(), "custom npm prefix "));
|
||||
onTestFinished(() => rmSync(prefix, { recursive: true, force: true }));
|
||||
const root = process.platform === "win32" ? prefix : path.join(prefix, "lib");
|
||||
const packagePath = path.join(root, "node_modules", "@getpaseo", "cli");
|
||||
mkdirSync(packagePath, { recursive: true });
|
||||
const commands: string[][] = [];
|
||||
let version = "0.1.15";
|
||||
const npm = new DefaultNpmGlobalPaseoCli(async (_command, args) => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { DefaultNpmGlobalPaseoCli } from "./npm-global-cli.js";
|
||||
|
||||
interface CommandCall {
|
||||
@@ -10,25 +12,65 @@ interface CommandCall {
|
||||
}
|
||||
|
||||
const globalRoot = path.join(path.sep, "global", "lib");
|
||||
const globalNodeModules = path.join(globalRoot, "node_modules");
|
||||
const cliPackagePath = path.join(globalNodeModules, "@getpaseo", "cli");
|
||||
|
||||
function npmGlobalPaseoCliJson(version: string, options?: { linked?: boolean }): string {
|
||||
return JSON.stringify({
|
||||
name: "lib",
|
||||
path: globalRoot,
|
||||
dependencies: {
|
||||
"@getpaseo/cli": {
|
||||
version,
|
||||
path: cliPackagePath,
|
||||
link: options?.linked === true,
|
||||
},
|
||||
},
|
||||
});
|
||||
interface GlobalInstall {
|
||||
root: string;
|
||||
packagePath: string;
|
||||
// npm 7+ `ls --json --long` marks neither kind of install with a link flag.
|
||||
npmLsJson: string;
|
||||
}
|
||||
|
||||
describe("DefaultNpmGlobalPaseoCli", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function createGlobalInstall(options: { linked: boolean }): GlobalInstall {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "paseo-npm-global-"));
|
||||
tempDirs.push(dir);
|
||||
const root = path.join(dir, "prefix", "lib");
|
||||
const packagePath = path.join(root, "node_modules", "@getpaseo", "cli");
|
||||
const checkoutCli = path.join(dir, "checkout", "packages", "cli");
|
||||
if (options.linked) {
|
||||
mkdirSync(checkoutCli, { recursive: true });
|
||||
mkdirSync(path.dirname(packagePath), { recursive: true });
|
||||
symlinkSync(checkoutCli, packagePath, "junction");
|
||||
} else {
|
||||
mkdirSync(packagePath, { recursive: true });
|
||||
}
|
||||
const npmLsJson = JSON.stringify({
|
||||
name: "lib",
|
||||
path: root,
|
||||
dependencies: {
|
||||
"@getpaseo/cli": {
|
||||
version: "0.1.15",
|
||||
resolved: options.linked
|
||||
? `file:${path.relative(root, checkoutCli)}`
|
||||
: "https://registry.npmjs.org/@getpaseo/cli/-/cli-0.1.15.tgz",
|
||||
path: packagePath,
|
||||
},
|
||||
},
|
||||
});
|
||||
return { root, packagePath, npmLsJson };
|
||||
}
|
||||
|
||||
test("reports a global install linked to a local checkout as linked", async () => {
|
||||
const install = createGlobalInstall({ linked: true });
|
||||
const cli = new DefaultNpmGlobalPaseoCli(async () => ({
|
||||
exitCode: 0,
|
||||
stdout: install.npmLsJson,
|
||||
stderr: "",
|
||||
}));
|
||||
|
||||
await expect(cli.inspect()).resolves.toMatchObject({ isLinked: true });
|
||||
});
|
||||
|
||||
test("inspects the npm global cli install with npm -g ls", async () => {
|
||||
const install = createGlobalInstall({ linked: false });
|
||||
const calls: CommandCall[] = [];
|
||||
const cli = new DefaultNpmGlobalPaseoCli(async (command, args, options) => {
|
||||
calls.push({
|
||||
@@ -37,13 +79,13 @@ describe("DefaultNpmGlobalPaseoCli", () => {
|
||||
timeout: options?.timeout,
|
||||
maxBuffer: options?.maxBuffer,
|
||||
});
|
||||
return { exitCode: 0, stdout: npmGlobalPaseoCliJson("0.1.15"), stderr: "" };
|
||||
return { exitCode: 0, stdout: install.npmLsJson, stderr: "" };
|
||||
});
|
||||
|
||||
await expect(cli.inspect()).resolves.toEqual({
|
||||
version: "0.1.15",
|
||||
packagePath: cliPackagePath,
|
||||
globalRootPath: globalRoot,
|
||||
packagePath: install.packagePath,
|
||||
globalRootPath: install.root,
|
||||
isLinked: false,
|
||||
});
|
||||
expect(calls).toEqual([
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { lstatSync } from "node:fs";
|
||||
import { getErrorMessage } from "@getpaseo/protocol/error-utils";
|
||||
import { z } from "zod";
|
||||
import { execCommand } from "../../../utils/spawn.js";
|
||||
@@ -19,7 +20,6 @@ const NpmGlobalCliPackageSchema = z
|
||||
.object({
|
||||
version: z.string(),
|
||||
path: z.string(),
|
||||
link: z.boolean().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -112,7 +112,9 @@ function parseNpmGlobalPaseoInstall(stdout: string): NpmGlobalPaseoInstall | nul
|
||||
version: cliPackage.data.version,
|
||||
packagePath: cliPackage.data.path,
|
||||
globalRootPath: list.data.path ?? null,
|
||||
isLinked: cliPackage.data.link === true,
|
||||
// npm links an install by making its node_modules entry a symlink (a junction
|
||||
// on Windows), and `npm ls --json` reports no flag for it.
|
||||
isLinked: lstatSync(cliPackage.data.path).isSymbolicLink(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user