fix: v2.5.1 — plugin npm runs no longer depend on PATH (Windows update failure)
A Windows self-update failed at "bootstrap": 'npm.cmd' is not recognized. The update chain runs with the dashboard server's environment, whose PATH need not reach npm.cmd, and the bootstrap looked npm up through cmd.exe (shell: true, which also printed DEP0190). - npmCommand() in scripts/plugin-bootstrap.js runs the npm bundled with the running node (npm-cli.js beside process.execPath, Windows and Unix layouts) with no shell, and puts that node's directory first on PATH (kept under the env's own Path/PATH key) so npm scripts find the same node; npm on PATH only when that node ships none - Used by the runtime install, the UI build (plugin-open.js) and the doctor's npm row (plugin-doctor.js)
This commit is contained in:
@@ -383,6 +383,29 @@ function linkCli(dir = cliDir(), root = PLUGIN_ROOT, nodePath = process.execPath
|
||||
|
||||
/* ------------------------------------------------------------- dependencies */
|
||||
|
||||
/**
|
||||
* npm, run by the node executing this file: `{ file, args, env, shell }` for
|
||||
* `spawnSync`. Its bundled `npm-cli.js` is found from `process.execPath`, so
|
||||
* nothing depends on PATH — the self-update chain runs with the dashboard
|
||||
* server's environment, and on Windows that PATH need not hold the directory
|
||||
* of `npm.cmd` ("'npm.cmd' is not recognized"), even though node itself runs.
|
||||
* PATH is still prefixed with that node's directory so npm's scripts (the UI
|
||||
* build's `tsc`/`vite` shims) find the same node. A node with no bundled npm
|
||||
* (a distro's split nodejs/npm packages) falls back to `npm` on PATH.
|
||||
*/
|
||||
function npmCommand(args, { execPath = process.execPath, env = process.env } = {}) {
|
||||
const dir = path.dirname(execPath);
|
||||
const pathKey = Object.keys(env).find((k) => k.toUpperCase() === "PATH") || "PATH";
|
||||
const childEnv = { ...env, [pathKey]: [dir, env[pathKey]].filter(Boolean).join(path.delimiter) };
|
||||
const cli = [
|
||||
path.join(dir, "node_modules", "npm", "bin", "npm-cli.js"), // Windows layout
|
||||
path.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"), // Unix layout
|
||||
].find((p) => fs.existsSync(p));
|
||||
if (cli) return { file: execPath, args: [cli, ...args], env: childEnv, shell: false };
|
||||
const win = process.platform === "win32";
|
||||
return { file: win ? "npm.cmd" : "npm", args, env: childEnv, shell: win };
|
||||
}
|
||||
|
||||
/**
|
||||
* Install runtime dependencies into the runtime dir. `--ignore-scripts` keeps
|
||||
* the root postinstall from pulling the whole Vite client toolchain; the
|
||||
@@ -395,12 +418,13 @@ function installDeps(rt = runtimeDir(), root = PLUGIN_ROOT) {
|
||||
const src = path.join(root, f);
|
||||
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(rt, f));
|
||||
}
|
||||
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const res = spawnSync(
|
||||
npm,
|
||||
["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
|
||||
{ cwd: rt, stdio: "inherit", shell: process.platform === "win32" }
|
||||
);
|
||||
const npm = npmCommand(["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"]);
|
||||
const res = spawnSync(npm.file, npm.args, {
|
||||
cwd: rt,
|
||||
stdio: "inherit",
|
||||
env: npm.env,
|
||||
shell: npm.shell,
|
||||
});
|
||||
return res.status === 0;
|
||||
}
|
||||
|
||||
@@ -878,6 +902,7 @@ module.exports = {
|
||||
stripLegacyHooks,
|
||||
hasLegacyHooks,
|
||||
linkCli,
|
||||
npmCommand,
|
||||
installDeps,
|
||||
serverIsLive,
|
||||
liveServers,
|
||||
|
||||
@@ -125,10 +125,9 @@ function runtimeDepsStatus(rt) {
|
||||
|
||||
/** No npm, no dashboard bundle and no runtime install — both go through it. */
|
||||
function npmStatus() {
|
||||
const res = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", ["-v"], {
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
});
|
||||
// The same npm the bootstrap and the UI build run (`npmCommand`), not PATH's.
|
||||
const npm = boot.npmCommand(["-v"]);
|
||||
const res = spawnSync(npm.file, npm.args, { encoding: "utf8", env: npm.env, shell: npm.shell });
|
||||
if (res.status !== 0) {
|
||||
return ["fail", "npm", "not runnable — the runtime install and the UI build both need it"];
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ const boot = require("./plugin-bootstrap");
|
||||
const { resolveDashboardPort } = require("../server/lib/server-info");
|
||||
|
||||
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
||||
const NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const NPM = "npm";
|
||||
|
||||
function run(cmd, args, cwd, stdio) {
|
||||
const res = spawnSync(cmd, args, { cwd, stdio, shell: process.platform === "win32" });
|
||||
/** `cmd` is always NPM: run through `boot.npmCommand`, never a PATH lookup. */
|
||||
function run(_cmd, args, cwd, stdio) {
|
||||
const npm = boot.npmCommand(args);
|
||||
const res = spawnSync(npm.file, npm.args, { cwd, stdio, env: npm.env, shell: npm.shell });
|
||||
return res.status === 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user