mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(status): report beta registry channel
This commit is contained in:
@@ -6,12 +6,14 @@ import {
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import {
|
||||
normalizeUpdateChannel,
|
||||
resolveRegistryUpdateChannel,
|
||||
resolveUpdateChannelDisplay,
|
||||
} from "../../infra/update-channels.js";
|
||||
import { checkUpdateStatus } from "../../infra/update-check.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { getTerminalTableWidth, renderTable } from "../../terminal/table.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
import { parseTimeoutMsOrExit, resolveUpdateRoot, type UpdateStatusOptions } from "./shared.js";
|
||||
|
||||
function formatGitStatusLine(params: {
|
||||
@@ -47,10 +49,15 @@ export async function updateStatusCommand(opts: UpdateStatusOptions): Promise<vo
|
||||
timeoutMs: timeoutMs ?? 3500,
|
||||
fetchGit: true,
|
||||
includeRegistry: true,
|
||||
registryChannel: resolveRegistryUpdateChannel({
|
||||
configChannel,
|
||||
currentVersion: VERSION,
|
||||
}),
|
||||
});
|
||||
|
||||
const channelInfo = resolveUpdateChannelDisplay({
|
||||
configChannel,
|
||||
currentVersion: VERSION,
|
||||
installKind: update.installKind,
|
||||
gitTag: update.git?.tag ?? null,
|
||||
gitBranch: update.git?.branch ?? null,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "../../infra/update-channels.js";
|
||||
import { formatGitInstallLabel, type UpdateCheckResult } from "../../infra/update-check.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
import { formatUpdateOneLiner, resolveUpdateAvailability } from "../status.update.js";
|
||||
|
||||
export { formatTimeAgo } from "../../infra/format-time/format-relative.ts";
|
||||
@@ -68,6 +69,7 @@ export function resolveStatusUpdateChannelInfo(params: {
|
||||
}) {
|
||||
return resolveUpdateChannelDisplay({
|
||||
configChannel: normalizeUpdateChannel(params.updateConfigChannel),
|
||||
currentVersion: VERSION,
|
||||
installKind: params.update.installKind ?? "unknown",
|
||||
gitTag: params.update.git?.tag ?? null,
|
||||
gitBranch: params.update.git?.branch ?? null,
|
||||
|
||||
@@ -41,6 +41,7 @@ describe("status-json-payload", () => {
|
||||
expect(mocks.normalizeUpdateChannel).toHaveBeenCalledWith("beta");
|
||||
expect(mocks.resolveUpdateChannelDisplay).toHaveBeenCalledWith({
|
||||
configChannel: "beta",
|
||||
currentVersion: expect.any(String),
|
||||
installKind: "package",
|
||||
gitTag: "v1.2.3",
|
||||
gitBranch: "main",
|
||||
|
||||
@@ -67,6 +67,7 @@ type StatusScanCoreBootstrapParams<TAgentStatus> = {
|
||||
timeoutMs: number;
|
||||
fetchGit: boolean;
|
||||
includeRegistry: boolean;
|
||||
updateConfigChannel?: string | null;
|
||||
}) => Promise<UpdateCheckResult>;
|
||||
getAgentLocalStatuses: (cfg: OpenClawConfig) => Promise<TAgentStatus>;
|
||||
};
|
||||
@@ -95,6 +96,7 @@ export async function createStatusScanCoreBootstrap<TAgentStatus>(
|
||||
timeoutMs: updateTimeoutMs,
|
||||
fetchGit: true,
|
||||
includeRegistry: true,
|
||||
updateConfigChannel: params.cfg.update?.channel ?? null,
|
||||
});
|
||||
const agentStatusPromise = skipColdStartNetworkChecks
|
||||
? Promise.resolve(buildColdStartAgentLocalStatuses() as TAgentStatus)
|
||||
|
||||
@@ -140,6 +140,24 @@ describe("formatUpdateOneLiner", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("renders beta registry tags instead of calling them npm latest", () => {
|
||||
const update = buildUpdate({
|
||||
installKind: "package",
|
||||
packageManager: "npm",
|
||||
registry: { latestVersion: VERSION, tag: "beta" },
|
||||
deps: {
|
||||
manager: "npm",
|
||||
status: "ok",
|
||||
lockfilePath: "package-lock.json",
|
||||
markerPath: "node_modules",
|
||||
},
|
||||
});
|
||||
|
||||
expect(formatUpdateOneLiner(update)).toBe(
|
||||
`Update: npm · up to date · npm beta ${VERSION} · deps ok`,
|
||||
);
|
||||
});
|
||||
|
||||
it("renders package-manager mode with registry error", () => {
|
||||
const update = buildUpdate({
|
||||
installKind: "package",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { resolveOpenClawPackageRoot } from "../infra/openclaw-root.js";
|
||||
import { normalizeUpdateChannel, resolveRegistryUpdateChannel } from "../infra/update-channels.js";
|
||||
import {
|
||||
checkUpdateStatus,
|
||||
compareSemverStrings,
|
||||
@@ -11,7 +12,9 @@ export async function getUpdateCheckResult(params: {
|
||||
timeoutMs: number;
|
||||
fetchGit: boolean;
|
||||
includeRegistry: boolean;
|
||||
updateConfigChannel?: string | null;
|
||||
}): Promise<UpdateCheckResult> {
|
||||
const configChannel = normalizeUpdateChannel(params.updateConfigChannel);
|
||||
const root = await resolveOpenClawPackageRoot({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
@@ -22,6 +25,10 @@ export async function getUpdateCheckResult(params: {
|
||||
timeoutMs: params.timeoutMs,
|
||||
fetchGit: params.fetchGit,
|
||||
includeRegistry: params.includeRegistry,
|
||||
registryChannel: resolveRegistryUpdateChannel({
|
||||
configChannel,
|
||||
currentVersion: VERSION,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,22 +80,30 @@ export function formatUpdateOneLiner(update: UpdateCheckResult): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
const appendRegistryUpdateSummary = () => {
|
||||
const registryLabel =
|
||||
update.registry?.tag && update.registry.tag !== "latest"
|
||||
? `npm ${update.registry.tag}`
|
||||
: "npm latest";
|
||||
if (update.registry?.latestVersion) {
|
||||
const cmp = compareSemverStrings(VERSION, update.registry.latestVersion);
|
||||
if (cmp === 0) {
|
||||
if (update.installKind !== "git") {
|
||||
parts.push("up to date");
|
||||
}
|
||||
parts.push(`npm latest ${update.registry.latestVersion}`);
|
||||
parts.push(`${registryLabel} ${update.registry.latestVersion}`);
|
||||
} else if (cmp != null && cmp < 0) {
|
||||
parts.push(`npm update ${update.registry.latestVersion}`);
|
||||
parts.push(
|
||||
update.registry.tag && update.registry.tag !== "latest"
|
||||
? `${registryLabel} update ${update.registry.latestVersion}`
|
||||
: `npm update ${update.registry.latestVersion}`,
|
||||
);
|
||||
} else {
|
||||
parts.push(`npm latest ${update.registry.latestVersion} (local newer)`);
|
||||
parts.push(`${registryLabel} ${update.registry.latestVersion} (local newer)`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (update.registry?.error) {
|
||||
parts.push("npm latest unknown");
|
||||
parts.push(`${registryLabel} unknown`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isStableTag,
|
||||
normalizeUpdateChannel,
|
||||
resolveEffectiveUpdateChannel,
|
||||
resolveRegistryUpdateChannel,
|
||||
resolveUpdateChannelDisplay,
|
||||
type UpdateChannel,
|
||||
type UpdateChannelSource,
|
||||
@@ -66,6 +67,15 @@ describe("resolveEffectiveUpdateChannel", () => {
|
||||
},
|
||||
expected: { channel: "beta", source: "config" },
|
||||
},
|
||||
{
|
||||
name: "uses installed beta version over stale stable config",
|
||||
params: {
|
||||
configChannel: "stable",
|
||||
currentVersion: "2026.5.2-beta.1",
|
||||
installKind: "package" as const,
|
||||
},
|
||||
expected: { channel: "beta", source: "installed-version" },
|
||||
},
|
||||
{
|
||||
name: "uses beta git tag",
|
||||
params: {
|
||||
@@ -152,6 +162,11 @@ describe("formatUpdateChannelLabel", () => {
|
||||
params: { channel: "dev", source: "git-branch" as const },
|
||||
expected: "dev (branch)",
|
||||
},
|
||||
{
|
||||
name: "formats installed-version labels",
|
||||
params: { channel: "beta", source: "installed-version" as const },
|
||||
expected: "beta (installed version)",
|
||||
},
|
||||
{
|
||||
name: "formats default labels",
|
||||
params: { channel: "stable", source: "default" as const },
|
||||
@@ -167,6 +182,20 @@ describe("formatUpdateChannelLabel", () => {
|
||||
});
|
||||
|
||||
describe("resolveUpdateChannelDisplay", () => {
|
||||
it("labels stale stable config on a beta install from the installed version", () => {
|
||||
expect(
|
||||
resolveUpdateChannelDisplay({
|
||||
configChannel: "stable",
|
||||
currentVersion: "2026.5.2-beta.1",
|
||||
installKind: "package",
|
||||
}),
|
||||
).toEqual({
|
||||
channel: "beta",
|
||||
source: "installed-version",
|
||||
label: "beta (installed version)",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes the derived label for git branches", () => {
|
||||
expect(
|
||||
resolveUpdateChannelDisplay({
|
||||
@@ -206,3 +235,14 @@ describe("resolveUpdateChannelDisplay", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveRegistryUpdateChannel", () => {
|
||||
it("queries beta when the installed version is beta even if config is stale stable", () => {
|
||||
expect(
|
||||
resolveRegistryUpdateChannel({
|
||||
configChannel: "stable",
|
||||
currentVersion: "2026.5.2-beta.1",
|
||||
}),
|
||||
).toBe("beta");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
|
||||
export type UpdateChannel = "stable" | "beta" | "dev";
|
||||
export type UpdateChannelSource = "config" | "git-tag" | "git-branch" | "default";
|
||||
export type UpdateChannelSource =
|
||||
| "config"
|
||||
| "git-tag"
|
||||
| "git-branch"
|
||||
| "installed-version"
|
||||
| "default";
|
||||
|
||||
export const DEFAULT_PACKAGE_CHANNEL: UpdateChannel = "stable";
|
||||
export const DEFAULT_GIT_CHANNEL: UpdateChannel = "dev";
|
||||
@@ -36,11 +41,36 @@ export function isStableTag(tag: string): boolean {
|
||||
return !isBetaTag(tag);
|
||||
}
|
||||
|
||||
export function resolveRegistryUpdateChannel(params: {
|
||||
configChannel?: UpdateChannel | null;
|
||||
currentVersion?: string | null;
|
||||
}): UpdateChannel {
|
||||
if (
|
||||
params.currentVersion &&
|
||||
isBetaTag(params.currentVersion) &&
|
||||
params.configChannel !== "beta" &&
|
||||
params.configChannel !== "dev"
|
||||
) {
|
||||
return "beta";
|
||||
}
|
||||
return params.configChannel ?? DEFAULT_PACKAGE_CHANNEL;
|
||||
}
|
||||
|
||||
export function resolveEffectiveUpdateChannel(params: {
|
||||
configChannel?: UpdateChannel | null;
|
||||
currentVersion?: string | null;
|
||||
installKind: "git" | "package" | "unknown";
|
||||
git?: { tag?: string | null; branch?: string | null };
|
||||
}): { channel: UpdateChannel; source: UpdateChannelSource } {
|
||||
if (
|
||||
params.currentVersion &&
|
||||
isBetaTag(params.currentVersion) &&
|
||||
params.configChannel !== "beta" &&
|
||||
params.configChannel !== "dev"
|
||||
) {
|
||||
return { channel: "beta", source: "installed-version" };
|
||||
}
|
||||
|
||||
if (params.configChannel) {
|
||||
return { channel: params.configChannel, source: "config" };
|
||||
}
|
||||
@@ -81,17 +111,22 @@ export function formatUpdateChannelLabel(params: {
|
||||
? `${params.channel} (${params.gitBranch})`
|
||||
: `${params.channel} (branch)`;
|
||||
}
|
||||
if (params.source === "installed-version") {
|
||||
return "beta (installed version)";
|
||||
}
|
||||
return `${params.channel} (default)`;
|
||||
}
|
||||
|
||||
export function resolveUpdateChannelDisplay(params: {
|
||||
configChannel?: UpdateChannel | null;
|
||||
currentVersion?: string | null;
|
||||
installKind: "git" | "package" | "unknown";
|
||||
gitTag?: string | null;
|
||||
gitBranch?: string | null;
|
||||
}): { channel: UpdateChannel; source: UpdateChannelSource; label: string } {
|
||||
const channelInfo = resolveEffectiveUpdateChannel({
|
||||
configChannel: params.configChannel,
|
||||
currentVersion: params.currentVersion,
|
||||
installKind: params.installKind,
|
||||
git:
|
||||
params.gitTag || params.gitBranch
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
compareSemverStrings,
|
||||
fetchNpmLatestVersion,
|
||||
fetchNpmPackageTargetStatus,
|
||||
fetchNpmRegistryVersionForChannel,
|
||||
fetchNpmTagVersion,
|
||||
formatGitInstallLabel,
|
||||
resolveNpmChannelTag,
|
||||
@@ -116,8 +117,20 @@ describe("resolveNpmChannelTag", () => {
|
||||
latestVersion: "1.0.4",
|
||||
error: undefined,
|
||||
});
|
||||
versionByTag.beta = "1.0.5-beta.1";
|
||||
await expect(
|
||||
fetchNpmRegistryVersionForChannel({ channel: "beta", timeoutMs: 1000 }),
|
||||
).resolves.toEqual({
|
||||
latestVersion: "1.0.5-beta.1",
|
||||
tag: "beta",
|
||||
});
|
||||
await expect(fetchNpmTagVersion({ tag: "beta", timeoutMs: 1000 })).resolves.toEqual({
|
||||
tag: "beta",
|
||||
version: "1.0.5-beta.1",
|
||||
error: undefined,
|
||||
});
|
||||
await expect(fetchNpmTagVersion({ tag: "missing", timeoutMs: 1000 })).resolves.toEqual({
|
||||
tag: "missing",
|
||||
version: null,
|
||||
error: "HTTP 404",
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ export type DepsStatus = {
|
||||
|
||||
export type RegistryStatus = {
|
||||
latestVersion: string | null;
|
||||
tag?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
@@ -302,6 +303,20 @@ export async function fetchNpmLatestVersion(params?: {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchNpmRegistryVersionForChannel(params: {
|
||||
channel: UpdateChannel;
|
||||
timeoutMs?: number;
|
||||
}): Promise<RegistryStatus> {
|
||||
const res = await resolveNpmChannelTag({
|
||||
channel: params.channel,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
return {
|
||||
latestVersion: res.version,
|
||||
tag: res.tag,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchNpmPackageTargetStatus(params: {
|
||||
target: string;
|
||||
timeoutMs?: number;
|
||||
@@ -380,15 +395,23 @@ export async function checkUpdateStatus(params: {
|
||||
timeoutMs?: number;
|
||||
fetchGit?: boolean;
|
||||
includeRegistry?: boolean;
|
||||
registryChannel?: UpdateChannel;
|
||||
}): Promise<UpdateCheckResult> {
|
||||
const timeoutMs = params.timeoutMs ?? 6000;
|
||||
const fetchRegistry = () =>
|
||||
params.registryChannel
|
||||
? fetchNpmRegistryVersionForChannel({
|
||||
channel: params.registryChannel,
|
||||
timeoutMs,
|
||||
})
|
||||
: fetchNpmLatestVersion({ timeoutMs });
|
||||
const root = params.root ? path.resolve(params.root) : null;
|
||||
if (!root) {
|
||||
return {
|
||||
root: null,
|
||||
installKind: "unknown",
|
||||
packageManager: "unknown",
|
||||
registry: params.includeRegistry ? await fetchNpmLatestVersion({ timeoutMs }) : undefined,
|
||||
registry: params.includeRegistry ? await fetchRegistry() : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -396,7 +419,7 @@ export async function checkUpdateStatus(params: {
|
||||
const [pm, gitRoot, registry] = await Promise.all([
|
||||
detectPackageManager(root),
|
||||
detectGitRoot(root),
|
||||
params.includeRegistry ? fetchNpmLatestVersion({ timeoutMs }) : Promise.resolve(undefined),
|
||||
params.includeRegistry ? fetchRegistry() : Promise.resolve(undefined),
|
||||
]);
|
||||
const isGit = gitRoot && path.resolve(gitRoot) === path.resolve(rootRealpath);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user