feat: add windows update package spec override

This commit is contained in:
Peter Steinberger
2026-03-12 23:56:48 +00:00
parent 91b701e183
commit d96069f0df
5 changed files with 97 additions and 2 deletions

View File

@@ -521,6 +521,32 @@ describe("update-cli", () => {
expect(updateCall?.[1]?.env?.NODE_LLAMA_CPP_SKIP_DOWNLOAD).toBe("1");
});
it("uses OPENCLAW_UPDATE_PACKAGE_SPEC for package updates", async () => {
const tempDir = createCaseDir("openclaw-update");
mockPackageInstallStatus(tempDir);
await withEnvAsync(
{ OPENCLAW_UPDATE_PACKAGE_SPEC: "http://10.211.55.2:8138/openclaw-next.tgz" },
async () => {
await updateCommand({ yes: true, tag: "latest" });
},
);
expect(runGatewayUpdate).not.toHaveBeenCalled();
expect(runCommandWithTimeout).toHaveBeenCalledWith(
[
"npm",
"i",
"-g",
"http://10.211.55.2:8138/openclaw-next.tgz",
"--no-fund",
"--no-audit",
"--loglevel=error",
],
expect.any(Object),
);
});
it("updateCommand outputs JSON when --json is set", async () => {
vi.mocked(runGatewayUpdate).mockResolvedValue(makeOkUpdateResult());
vi.mocked(defaultRuntime.log).mockClear();

View File

@@ -27,6 +27,7 @@ import {
createGlobalInstallEnv,
cleanupGlobalRenameDirs,
globalInstallArgs,
resolveGlobalInstallSpec,
resolveGlobalPackageRoot,
} from "../../infra/update-global.js";
import { runGatewayUpdate, type UpdateRunResult } from "../../infra/update-runner.js";
@@ -277,6 +278,11 @@ async function runPackageInstallUpdate(params: {
const packageName =
(pkgRoot ? await readPackageName(pkgRoot) : await readPackageName(params.root)) ??
DEFAULT_PACKAGE_NAME;
const installSpec = resolveGlobalInstallSpec({
packageName,
tag: params.tag,
env: installEnv,
});
const beforeVersion = pkgRoot ? await readPackageVersion(pkgRoot) : null;
if (pkgRoot) {
@@ -288,7 +294,7 @@ async function runPackageInstallUpdate(params: {
const updateStep = await runUpdateStep({
name: "global update",
argv: globalInstallArgs(manager, `${packageName}@${params.tag}`),
argv: globalInstallArgs(manager, installSpec),
env: installEnv,
timeoutMs: params.timeoutMs,
progress: params.progress,

View File

@@ -57,6 +57,20 @@ function applyWindowsPackageInstallEnv(env: Record<string, string>) {
env.NODE_LLAMA_CPP_SKIP_DOWNLOAD = "1";
}
export function resolveGlobalInstallSpec(params: {
packageName: string;
tag: string;
env?: NodeJS.ProcessEnv;
}): string {
const override =
params.env?.OPENCLAW_UPDATE_PACKAGE_SPEC?.trim() ||
process.env.OPENCLAW_UPDATE_PACKAGE_SPEC?.trim();
if (override) {
return override;
}
return `${params.packageName}@${params.tag}`;
}
export async function createGlobalInstallEnv(
env?: NodeJS.ProcessEnv,
): Promise<NodeJS.ProcessEnv | undefined> {

View File

@@ -583,6 +583,50 @@ describe("runGatewayUpdate", () => {
expect(installEnv?.NODE_LLAMA_CPP_SKIP_DOWNLOAD).toBe("1");
});
it("uses OPENCLAW_UPDATE_PACKAGE_SPEC for global package updates", async () => {
const nodeModules = path.join(tempDir, "node_modules");
const pkgRoot = path.join(nodeModules, "openclaw");
await seedGlobalPackageRoot(pkgRoot);
const calls: string[] = [];
const runCommand = async (argv: string[]): Promise<CommandResult> => {
const key = argv.join(" ");
calls.push(key);
if (key === `git -C ${pkgRoot} rev-parse --show-toplevel`) {
return { stdout: "", stderr: "not a git repository", code: 128 };
}
if (key === "npm root -g") {
return { stdout: nodeModules, stderr: "", code: 0 };
}
if (key === "pnpm root -g") {
return { stdout: "", stderr: "", code: 1 };
}
if (
key ===
"npm i -g http://10.211.55.2:8138/openclaw-next.tgz --no-fund --no-audit --loglevel=error"
) {
await fs.writeFile(
path.join(pkgRoot, "package.json"),
JSON.stringify({ name: "openclaw", version: "2.0.0" }),
"utf-8",
);
}
return { stdout: "ok", stderr: "", code: 0 };
};
await withEnvAsync(
{ OPENCLAW_UPDATE_PACKAGE_SPEC: "http://10.211.55.2:8138/openclaw-next.tgz" },
async () => {
const result = await runWithCommand(runCommand, { cwd: pkgRoot });
expect(result.status).toBe("ok");
},
);
expect(calls).toContain(
"npm i -g http://10.211.55.2:8138/openclaw-next.tgz --no-fund --no-audit --loglevel=error",
);
});
it("updates global bun installs when detected", async () => {
const bunInstall = path.join(tempDir, "bun-install");
await withEnvAsync({ BUN_INSTALL: bunInstall }, async () => {

View File

@@ -26,6 +26,7 @@ import {
detectGlobalInstallManagerForRoot,
globalInstallArgs,
globalInstallFallbackArgs,
resolveGlobalInstallSpec,
} from "./update-global.js";
export type UpdateStepResult = {
@@ -872,9 +873,13 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
});
const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL;
const tag = normalizeTag(opts.tag ?? channelToNpmTag(channel));
const spec = `${packageName}@${tag}`;
const steps: UpdateStepResult[] = [];
const globalInstallEnv = await createGlobalInstallEnv();
const spec = resolveGlobalInstallSpec({
packageName,
tag,
env: globalInstallEnv,
});
const updateStep = await runStep({
runCommand,
name: "global update",