mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 03:15:46 -06:00
fix(update): align metadata with install npm
This commit is contained in:
@@ -3728,6 +3728,12 @@ describe("update-cli", () => {
|
||||
"i",
|
||||
),
|
||||
);
|
||||
expect(vi.mocked(resolveNpmChannelTag)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ command: installCommand }),
|
||||
);
|
||||
expect(vi.mocked(fetchNpmPackageTargetStatus)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ command: installCommand }),
|
||||
);
|
||||
const installOptions = requiredInstallCall[1] as { timeoutMs?: number };
|
||||
expect(typeof installOptions.timeoutMs).toBe("number");
|
||||
});
|
||||
@@ -6397,7 +6403,9 @@ describe("update-cli", () => {
|
||||
expect(
|
||||
vi
|
||||
.mocked(runCommandWithTimeout)
|
||||
.mock.calls.some((call) => Array.isArray(call[0]) && call[0][0] === "npm"),
|
||||
.mock.calls.some(
|
||||
(call) => Array.isArray(call[0]) && call[0][0] === "npm" && call[0][1] === "i",
|
||||
),
|
||||
).toBe(shouldRunPackageUpdate);
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export { readPackageName, readPackageVersion };
|
||||
export async function resolveTargetVersion(
|
||||
tag: string,
|
||||
timeoutMs?: number,
|
||||
options: { spec?: string; cwd?: string; env?: NodeJS.ProcessEnv } = {},
|
||||
options: { spec?: string; command?: string; cwd?: string; env?: NodeJS.ProcessEnv } = {},
|
||||
): Promise<string | null> {
|
||||
if (!canResolveRegistryVersionForPackageTarget(tag)) {
|
||||
return null;
|
||||
@@ -111,6 +111,7 @@ export async function resolveTargetVersion(
|
||||
tag,
|
||||
timeoutMs,
|
||||
spec: options.spec,
|
||||
command: options.command,
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
});
|
||||
|
||||
@@ -91,6 +91,7 @@ import {
|
||||
resolveGlobalInstallTarget,
|
||||
resolveGlobalInstallSpec,
|
||||
resolvePnpmGlobalDirFromGlobalRoot,
|
||||
type ResolvedGlobalInstallTarget,
|
||||
} from "../../infra/update-global.js";
|
||||
import { cleanupStaleManagedServiceUpdateHandoffs } from "../../infra/update-managed-service-handoff-cleanup.js";
|
||||
import { runGatewayUpdate, type UpdateRunResult } from "../../infra/update-runner.js";
|
||||
@@ -1046,6 +1047,7 @@ async function resolvePackageRuntimePreflightError(params: {
|
||||
timeoutMs?: number;
|
||||
nodeRunner?: string;
|
||||
spec?: string;
|
||||
command?: string;
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<string | null> {
|
||||
@@ -1063,6 +1065,7 @@ async function resolvePackageRuntimePreflightError(params: {
|
||||
target,
|
||||
spec: params.spec,
|
||||
timeoutMs: params.timeoutMs,
|
||||
command: params.command,
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
});
|
||||
@@ -1513,21 +1516,25 @@ async function runPackageInstallUpdate(params: {
|
||||
honorPackageRoot?: boolean;
|
||||
nodeRunner?: string;
|
||||
installEnv?: NodeJS.ProcessEnv;
|
||||
installTarget?: ResolvedGlobalInstallTarget;
|
||||
}): Promise<UpdateRunResult> {
|
||||
const manager = await resolveGlobalManager({
|
||||
root: params.root,
|
||||
installKind: params.installKind,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
const installEnv = params.installEnv ?? (await createGlobalInstallEnv());
|
||||
const runCommand = createGlobalCommandRunner();
|
||||
const installTarget = await resolveGlobalInstallTarget({
|
||||
manager,
|
||||
runCommand,
|
||||
timeoutMs: params.timeoutMs,
|
||||
pkgRoot: params.root,
|
||||
honorPackageRoot: params.honorPackageRoot === true,
|
||||
});
|
||||
let installTarget = params.installTarget;
|
||||
if (!installTarget) {
|
||||
const manager = await resolveGlobalManager({
|
||||
root: params.root,
|
||||
installKind: params.installKind,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
installTarget = await resolveGlobalInstallTarget({
|
||||
manager,
|
||||
runCommand,
|
||||
timeoutMs: params.timeoutMs,
|
||||
pkgRoot: params.root,
|
||||
honorPackageRoot: params.honorPackageRoot === true,
|
||||
});
|
||||
}
|
||||
const pkgRoot = installTarget.packageRoot;
|
||||
const packageName =
|
||||
(pkgRoot ? await readPackageName(pkgRoot) : await readPackageName(params.root)) ??
|
||||
@@ -1630,7 +1637,7 @@ async function runPackageInstallUpdate(params: {
|
||||
|
||||
return {
|
||||
status: packageUpdate.failedStep ? "error" : "ok",
|
||||
mode: manager,
|
||||
mode: installTarget.manager,
|
||||
root: packageUpdate.verifiedPackageRoot ?? params.root,
|
||||
reason: packageUpdate.failedStep ? packageUpdate.failedStep.name : undefined,
|
||||
before: { version: beforeVersion },
|
||||
@@ -3298,6 +3305,7 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
let packageInstallSpec: string | null = null;
|
||||
let packageInstallEnv: NodeJS.ProcessEnv | undefined;
|
||||
let packageInstallCwd: string | undefined;
|
||||
let packageInstallTarget: ResolvedGlobalInstallTarget | undefined;
|
||||
let packageAlreadyCurrent = false;
|
||||
let managedServiceRootRedirect: ManagedServiceRootRedirect | null = null;
|
||||
// Resolved independently of the root redirect so it covers the common case
|
||||
@@ -3354,6 +3362,23 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
if (updateInstallKind !== "git") {
|
||||
packageInstallEnv = await createGlobalInstallEnv();
|
||||
packageInstallCwd = tryResolveInvocationCwd();
|
||||
if (updateInstallKind === "package") {
|
||||
const manager = await resolveGlobalManager({
|
||||
root,
|
||||
installKind,
|
||||
timeoutMs: updateStepTimeoutMs,
|
||||
});
|
||||
packageInstallTarget = await resolveGlobalInstallTarget({
|
||||
manager,
|
||||
runCommand: createGlobalCommandRunner(),
|
||||
timeoutMs: updateStepTimeoutMs,
|
||||
pkgRoot: root,
|
||||
honorPackageRoot:
|
||||
managedServiceRootRedirect !== null || managedServiceNodeRunner !== undefined,
|
||||
});
|
||||
}
|
||||
const npmMetadataCommand =
|
||||
packageInstallTarget?.manager === "npm" ? packageInstallTarget.command : undefined;
|
||||
currentVersion = switchToPackage ? null : await readPackageVersion(root);
|
||||
if (explicitTag) {
|
||||
const explicitSpec = resolveGlobalInstallSpec({
|
||||
@@ -3363,6 +3388,7 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
});
|
||||
targetVersion = await resolveTargetVersion(tag, timeoutMs, {
|
||||
spec: explicitSpec,
|
||||
command: npmMetadataCommand,
|
||||
cwd: packageInstallCwd,
|
||||
env: packageInstallEnv,
|
||||
});
|
||||
@@ -3370,6 +3396,7 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
targetVersion = await resolveNpmChannelTag({
|
||||
channel,
|
||||
timeoutMs,
|
||||
command: npmMetadataCommand,
|
||||
cwd: packageInstallCwd,
|
||||
env: packageInstallEnv,
|
||||
}).then((resolved) => {
|
||||
@@ -3516,6 +3543,7 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
spec: packageInstallSpec ?? undefined,
|
||||
timeoutMs,
|
||||
nodeRunner: managedServiceNodeRunner,
|
||||
command: packageInstallTarget?.manager === "npm" ? packageInstallTarget.command : undefined,
|
||||
cwd: packageInstallCwd,
|
||||
env: packageInstallEnv,
|
||||
});
|
||||
@@ -3623,6 +3651,7 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
|
||||
managedServiceRootRedirect !== null || managedServiceNodeRunner !== undefined,
|
||||
nodeRunner: managedServiceNodeRunner,
|
||||
installEnv: packageInstallEnv,
|
||||
installTarget: packageInstallTarget,
|
||||
})
|
||||
: await runGitUpdate({
|
||||
root,
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
||||
import http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
@@ -74,6 +74,10 @@ describe("resolveNpmChannelTag", () => {
|
||||
runCommand = runCommandMock as unknown as NpmMetadataCommandRunner;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("delegates package target metadata to npm view with global config scope", async () => {
|
||||
versionByTag.latest = "1.0.4";
|
||||
const env = { ...process.env, NPM_CONFIG_USERCONFIG: "/tmp/openclaw-user-npmrc" };
|
||||
@@ -82,6 +86,7 @@ describe("resolveNpmChannelTag", () => {
|
||||
fetchNpmPackageTargetStatus({
|
||||
target: "latest",
|
||||
spec: "openclaw@latest",
|
||||
command: "/opt/openclaw/node/bin/npm",
|
||||
timeoutMs: 1000,
|
||||
cwd: "/tmp/openclaw-project",
|
||||
env,
|
||||
@@ -94,7 +99,15 @@ describe("resolveNpmChannelTag", () => {
|
||||
});
|
||||
|
||||
expect(runCommandMock).toHaveBeenCalledWith(
|
||||
["npm", "view", "openclaw@latest", "version", "engines.node", "--json", "--global"],
|
||||
[
|
||||
"/opt/openclaw/node/bin/npm",
|
||||
"view",
|
||||
"openclaw@latest",
|
||||
"version",
|
||||
"engines.node",
|
||||
"--json",
|
||||
"--global",
|
||||
],
|
||||
expect.objectContaining({
|
||||
timeoutMs: 1000,
|
||||
cwd: "/tmp/openclaw-project",
|
||||
@@ -153,6 +166,7 @@ describe("resolveNpmChannelTag", () => {
|
||||
await expect(
|
||||
fetchNpmPackageTargetStatus({
|
||||
target: "latest",
|
||||
command: "npm",
|
||||
timeoutMs: 10_000,
|
||||
cwd: project,
|
||||
env: {
|
||||
@@ -180,6 +194,31 @@ describe("resolveNpmChannelTag", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the public registry when no npm command is available", async () => {
|
||||
const fetch = vi.fn(async () => {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
version: "2026.6.8",
|
||||
engines: { node: ">=22.19.0" },
|
||||
}),
|
||||
} as Response;
|
||||
});
|
||||
vi.stubGlobal("fetch", fetch);
|
||||
|
||||
await expect(
|
||||
fetchNpmPackageTargetStatus({ target: "latest", timeoutMs: 1000 }),
|
||||
).resolves.toEqual({
|
||||
target: "latest",
|
||||
version: "2026.6.8",
|
||||
nodeEngine: ">=22.19.0",
|
||||
});
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"https://registry.npmjs.org/openclaw/latest",
|
||||
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to latest when beta is older", async () => {
|
||||
versionByTag.beta = "1.0.0-beta.1";
|
||||
versionByTag.latest = "1.0.1-1";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { fetchWithTimeout } from "../utils/fetch-timeout.js";
|
||||
import { detectPackageManager as detectPackageManagerImpl } from "./detect-package-manager.js";
|
||||
import { compareOpenClawReleaseVersions } from "./npm-registry-spec.js";
|
||||
import { compareComparableSemver, parseComparableSemver } from "./semver-compare.js";
|
||||
@@ -105,6 +106,38 @@ function packageTargetSpec(params: { target: string; spec?: string }): string {
|
||||
return spec || `openclaw@${params.target.trim() || "latest"}`;
|
||||
}
|
||||
|
||||
async function fetchPublicNpmPackageTargetStatus(params: {
|
||||
target: string;
|
||||
timeoutMs: number;
|
||||
}): Promise<NpmPackageTargetStatus> {
|
||||
try {
|
||||
const res = await fetchWithTimeout(
|
||||
`https://registry.npmjs.org/openclaw/${encodeURIComponent(params.target)}`,
|
||||
{},
|
||||
Math.max(250, params.timeoutMs),
|
||||
);
|
||||
if (!res.ok) {
|
||||
return {
|
||||
target: params.target,
|
||||
version: null,
|
||||
nodeEngine: null,
|
||||
error: `HTTP ${res.status}`,
|
||||
};
|
||||
}
|
||||
const json = (await res.json()) as {
|
||||
version?: unknown;
|
||||
engines?: { node?: unknown };
|
||||
};
|
||||
return {
|
||||
target: params.target,
|
||||
version: toOptionalTrimmedString(json.version),
|
||||
nodeEngine: toOptionalTrimmedString(json.engines?.node),
|
||||
};
|
||||
} catch (err) {
|
||||
return { target: params.target, version: null, nodeEngine: null, error: String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
export function formatGitInstallLabel(update: UpdateCheckResult): string | null {
|
||||
if (update.installKind !== "git") {
|
||||
return null;
|
||||
@@ -387,17 +420,21 @@ export async function fetchNpmPackageTargetStatus(params: {
|
||||
target: string;
|
||||
timeoutMs?: number;
|
||||
spec?: string;
|
||||
command?: string;
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runCommand?: NpmMetadataCommandRunner;
|
||||
}): Promise<NpmPackageTargetStatus> {
|
||||
const timeoutMs = params.timeoutMs ?? 3500;
|
||||
const target = params.target;
|
||||
if (!params.command && !params.runCommand) {
|
||||
return await fetchPublicNpmPackageTargetStatus({ target, timeoutMs });
|
||||
}
|
||||
const runCommand = params.runCommand ?? runCommandWithTimeout;
|
||||
try {
|
||||
const res = await runCommand(
|
||||
[
|
||||
"npm",
|
||||
params.command ?? "npm",
|
||||
"view",
|
||||
packageTargetSpec({ target, spec: params.spec }),
|
||||
"version",
|
||||
@@ -431,6 +468,7 @@ export async function fetchNpmTagVersion(params: {
|
||||
tag: string;
|
||||
timeoutMs?: number;
|
||||
spec?: string;
|
||||
command?: string;
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runCommand?: NpmMetadataCommandRunner;
|
||||
@@ -439,6 +477,7 @@ export async function fetchNpmTagVersion(params: {
|
||||
target: params.tag,
|
||||
timeoutMs: params.timeoutMs,
|
||||
spec: params.spec,
|
||||
command: params.command,
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
runCommand: params.runCommand,
|
||||
@@ -453,6 +492,7 @@ export async function fetchNpmTagVersion(params: {
|
||||
export async function resolveNpmChannelTag(params: {
|
||||
channel: UpdateChannel;
|
||||
timeoutMs?: number;
|
||||
command?: string;
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runCommand?: NpmMetadataCommandRunner;
|
||||
@@ -461,6 +501,7 @@ export async function resolveNpmChannelTag(params: {
|
||||
const channelStatus = await fetchNpmTagVersion({
|
||||
tag: channelTag,
|
||||
timeoutMs: params.timeoutMs,
|
||||
command: params.command,
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
runCommand: params.runCommand,
|
||||
@@ -472,6 +513,7 @@ export async function resolveNpmChannelTag(params: {
|
||||
const latestStatus = await fetchNpmTagVersion({
|
||||
tag: "latest",
|
||||
timeoutMs: params.timeoutMs,
|
||||
command: params.command,
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
runCommand: params.runCommand,
|
||||
|
||||
Reference in New Issue
Block a user