Files
openclaw/scripts/npm-runner.mts
Peter Steinberger c70aee247e refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript

* fix(ci): keep changed-scope preflight zero-install

* fix(ci): preserve zero-install script owners

* fix(ci): complete script migration follow-through

* fix(release): keep stable closeout zero-install

* fix(scripts): preserve standalone execution boundaries

* fix(scripts): repair standalone loader boundaries

* fix(scripts): normalize gateway observation ids

* fix(scripts): keep Docker packager standalone

* test(scripts): preserve rebase cleanup helpers

* test(sessions): use tracked temp directory
2026-08-09 07:21:35 -07:00

118 lines
3.5 KiB
TypeScript

// Resolves npm commands from the active Node toolchain, especially on Windows.
import fs from "node:fs";
import path from "node:path";
import {
buildCmdExeCommandLine,
resolvePathEnvKey,
resolveWindowsCmdExePath,
} from "./lib/windows-cmd-helpers-runtime.mts";
export type NpmRunnerParams = {
comSpec?: string;
env?: NodeJS.ProcessEnv;
execPath?: string;
existsSync?: (filePath: string) => boolean;
npmArgs?: string[];
platform?: NodeJS.Platform;
};
type NpmRunner = {
args: string[];
command: string;
env?: NodeJS.ProcessEnv;
shell: boolean;
windowsVerbatimArguments?: boolean;
};
type ToolchainNpmRunnerParams = Required<Omit<NpmRunnerParams, "env">> & {
nodeDir: string;
pathImpl: typeof path.posix;
};
function resolveToolchainNpmRunner(params: ToolchainNpmRunnerParams): NpmRunner | null {
const npmCliCandidates = [
params.pathImpl.resolve(params.nodeDir, "../lib/node_modules/npm/bin/npm-cli.js"),
params.pathImpl.resolve(params.nodeDir, "node_modules/npm/bin/npm-cli.js"),
];
const npmCliPath = npmCliCandidates.find((candidate) => params.existsSync(candidate));
if (npmCliPath) {
return {
command: params.execPath,
args: [npmCliPath, ...params.npmArgs],
shell: false,
};
}
if (params.platform !== "win32") {
return null;
}
const npmExePath = params.pathImpl.resolve(params.nodeDir, "npm.exe");
if (params.existsSync(npmExePath)) {
return {
command: npmExePath,
args: params.npmArgs,
shell: false,
};
}
const npmCmdPath = params.pathImpl.resolve(params.nodeDir, "npm.cmd");
if (params.existsSync(npmCmdPath)) {
return {
command: params.comSpec,
args: ["/d", "/s", "/c", buildCmdExeCommandLine(npmCmdPath, params.npmArgs)],
shell: false,
windowsVerbatimArguments: true,
};
}
return null;
}
/** Resolves a toolchain-local npm invocation for the current platform. */
export function resolveNpmRunner(params: NpmRunnerParams = {}): NpmRunner {
const execPath = params.execPath ?? process.execPath;
const npmArgs = params.npmArgs ?? [];
const existsSync = params.existsSync ?? fs.existsSync;
const env = params.env ?? process.env;
const platform = params.platform ?? process.platform;
const comSpec = params.comSpec ?? (platform === "win32" ? resolveWindowsCmdExePath(env) : "");
const pathImpl = platform === "win32" ? path.win32 : path.posix;
const nodeDir = pathImpl.dirname(execPath);
const npmToolchain = resolveToolchainNpmRunner({
comSpec,
existsSync,
execPath,
nodeDir,
npmArgs,
pathImpl,
platform,
});
if (npmToolchain) {
return npmToolchain;
}
if (platform === "win32") {
const expectedPaths = [
pathImpl.resolve(nodeDir, "../lib/node_modules/npm/bin/npm-cli.js"),
pathImpl.resolve(nodeDir, "node_modules/npm/bin/npm-cli.js"),
pathImpl.resolve(nodeDir, "npm.exe"),
pathImpl.resolve(nodeDir, "npm.cmd"),
];
throw new Error(
`failed to resolve a toolchain-local npm next to ${execPath}. ` +
`Checked: ${expectedPaths.join(", ")}. ` +
"OpenClaw refuses to shell out to bare npm on Windows; install a Node.js toolchain that bundles npm or run with a matching Node installation.",
);
}
const pathKey = resolvePathEnvKey(env);
const currentPath = env[pathKey];
return {
command: "npm",
args: npmArgs,
shell: false,
env: {
...env,
[pathKey]:
typeof currentPath === "string" && currentPath.length > 0
? `${nodeDir}${path.delimiter}${currentPath}`
: nodeDir,
},
};
}