import fs from "node:fs/promises" ;
import os from "node:os" ;
import path from "node:path" ;
import { detectPackageManager as detectPackageManagerImpl } from "./detect-package-manager.js" ;
import { applyPathPrepend } from "./path-prepend.js" ;
export type BuildManager = "pnpm" | "bun" | "npm" ;
export type UpdatePackageManagerRequirement = "allow-fallback" | "require-preferred" ;
export type UpdatePackageManagerFailureReason =
| "preferred-manager-unavailable"
| "pnpm-corepack-enable-failed"
| "pnpm-corepack-missing"
| "pnpm-npm-bootstrap-failed" ;
export type PackageManagerCommandRunner = (
argv: string[],
options: { timeoutMs: number; env?: NodeJS.ProcessEnv },
) => Promise<{ stdout: string; stderr: string; code: number | null }>;
export type ResolvedBuildManager =
| {
kind: "resolved" ;
manager: BuildManager;
preferred: BuildManager;
fallback: boolean ;
env?: NodeJS.ProcessEnv;
cleanup?: () => Promise<void >;
}
| {
kind: "missing-required" ;
preferred: BuildManager;
reason: UpdatePackageManagerFailureReason;
};
export async function detectBuildManager(root: string): Promise<BuildManager> {
return (await detectPackageManagerImpl(root)) ?? "npm" ;
}
function managerPreferenceOrder(preferred: BuildManager): BuildManager[] {
if (preferred === "pnpm" ) {
return ["pnpm" , "npm" , "bun" ];
}
if (preferred === "bun" ) {
return ["bun" , "npm" , "pnpm" ];
}
return ["npm" , "pnpm" , "bun" ];
}
function managerVersionArgs(manager: BuildManager): string[] {
if (manager === "pnpm" ) {
return ["pnpm" , "--version" ];
}
if (manager === "bun" ) {
return ["bun" , "--version" ];
}
return ["npm" , "--version" ];
}
async function isManagerAvailable(
runCommand: PackageManagerCommandRunner,
manager: BuildManager,
timeoutMs: number,
env?: NodeJS.ProcessEnv,
): Promise<boolean > {
try {
const res = await runCommand(managerVersionArgs(manager), { timeoutMs, env });
return res.code === 0 ;
} catch {
return false ;
}
}
async function isCommandAvailable(
runCommand: PackageManagerCommandRunner,
argv: string[],
timeoutMs: number,
env?: NodeJS.ProcessEnv,
): Promise<boolean > {
try {
const res = await runCommand(argv, { timeoutMs, env });
return res.code === 0 ;
} catch {
return false ;
}
}
function cloneCommandEnv(env?: NodeJS.ProcessEnv): Record<string, string> {
return Object.fromEntries(
Object.entries(env ?? process.env)
.filter(([, value]) => value != null )
.map(([key, value]) => [key, String(value)]),
) as Record<string, string>;
}
async function enablePnpmViaCorepack(
runCommand: PackageManagerCommandRunner,
timeoutMs: number,
env?: NodeJS.ProcessEnv,
): Promise<"enabled" | "missing" | "failed" > {
if (!(await isCommandAvailable(runCommand, ["corepack" , "--version" ], timeoutMs, env))) {
return "missing" ;
}
try {
const res = await runCommand(["corepack" , "enable" ], { timeoutMs, env });
if (res.code !== 0 ) {
return "failed" ;
}
} catch {
return "failed" ;
}
return (await isManagerAvailable(runCommand, "pnpm" , timeoutMs, env)) ? "enabled" : "failed" ;
}
async function bootstrapPnpmViaNpm(params: {
runCommand: PackageManagerCommandRunner;
timeoutMs: number;
baseEnv?: NodeJS.ProcessEnv;
}): Promise<{ env: NodeJS.ProcessEnv; cleanup: () => Promise<void > } | null > {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-pnpm-" ));
const cleanup = async () => {
await fs.rm(tempRoot, { recursive: true , force: true }).catch (() => {});
};
try {
const installResult = await params.runCommand(
["npm" , "install" , "--prefix" , tempRoot, "pnpm@10" ],
{
timeoutMs: params.timeoutMs,
env: params.baseEnv,
},
);
if (installResult.code !== 0 ) {
await cleanup();
return null ;
}
const env = cloneCommandEnv(params.baseEnv);
applyPathPrepend(env, [path.join(tempRoot, "node_modules" , ".bin" )]);
if (!(await isManagerAvailable(params.runCommand, "pnpm" , params.timeoutMs, env))) {
await cleanup();
return null ;
}
return { env, cleanup };
} catch {
await cleanup();
return null ;
}
}
export async function resolveUpdateBuildManager(
runCommand: PackageManagerCommandRunner,
root: string,
timeoutMs: number,
baseEnv?: NodeJS.ProcessEnv,
requirement: UpdatePackageManagerRequirement = "allow-fallback" ,
): Promise<ResolvedBuildManager> {
const preferred = await detectBuildManager(root);
if (preferred === "pnpm" ) {
if (await isManagerAvailable(runCommand, "pnpm" , timeoutMs, baseEnv)) {
return { kind: "resolved" , manager: "pnpm" , preferred, fallback: false };
}
const corepackStatus = await enablePnpmViaCorepack(runCommand, timeoutMs, baseEnv);
if (corepackStatus === "enabled" ) {
return { kind: "resolved" , manager: "pnpm" , preferred, fallback: false };
}
const npmAvailable = await isManagerAvailable(runCommand, "npm" , timeoutMs, baseEnv);
if (npmAvailable) {
const pnpmBootstrap = await bootstrapPnpmViaNpm({
runCommand,
timeoutMs,
baseEnv,
});
if (pnpmBootstrap) {
return {
kind: "resolved" ,
manager: "pnpm" ,
preferred,
fallback: false ,
env: pnpmBootstrap.env,
cleanup: pnpmBootstrap.cleanup,
};
}
if (requirement === "require-preferred" ) {
return { kind: "missing-required" , preferred, reason: "pnpm-npm-bootstrap-failed" };
}
}
if (requirement === "require-preferred" ) {
if (corepackStatus === "missing" ) {
return { kind: "missing-required" , preferred, reason: "pnpm-corepack-missing" };
}
if (corepackStatus === "failed" ) {
return { kind: "missing-required" , preferred, reason: "pnpm-corepack-enable-failed" };
}
return { kind: "missing-required" , preferred, reason: "preferred-manager-unavailable" };
}
}
for (const manager of managerPreferenceOrder(preferred)) {
if (await isManagerAvailable(runCommand, manager, timeoutMs, baseEnv)) {
return { kind: "resolved" , manager, preferred, fallback: manager !== preferred };
}
}
if (requirement === "require-preferred" ) {
return { kind: "missing-required" , preferred, reason: "preferred-manager-unavailable" };
}
return { kind: "resolved" , manager: "npm" , preferred, fallback: preferred !== "npm" };
}
export function managerScriptArgs(manager: BuildManager, script: string, args: string[] = []) {
if (manager === "pnpm" ) {
return ["pnpm" , script, ...args];
}
if (manager === "bun" ) {
return ["bun" , "run" , script, ...args];
}
if (args.length > 0 ) {
return ["npm" , "run" , script, "--" , ...args];
}
return ["npm" , "run" , script];
}
export function managerInstallArgs(manager: BuildManager, opts?: { compatFallback?: boolean }) {
if (manager === "pnpm" ) {
return ["pnpm" , "install" ];
}
if (manager === "bun" ) {
return ["bun" , "install" ];
}
if (opts?.compatFallback) {
return ["npm" , "install" , "--no-package-lock" , "--legacy-peer-deps" ];
}
return ["npm" , "install" ];
}
export function managerInstallIgnoreScriptsArgs(manager: BuildManager): string[] | null {
if (manager === "pnpm" ) {
return ["pnpm" , "install" , "--ignore-scripts" ];
}
if (manager === "bun" ) {
return ["bun" , "install" , "--ignore-scripts" ];
}
return ["npm" , "install" , "--ignore-scripts" ];
}
Messung V0.5 in Prozent C=95 H=98 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-06)
¤
*© Formatika GbR, Deutschland