mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(ui): identify source builds in settings footer (#116134)
* fix(ui): identify source builds in settings footer * fix(release): classify all official Control UI builds
This commit is contained in:
@@ -59,6 +59,8 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Build Control UI
|
||||
env:
|
||||
OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1"
|
||||
run: pnpm ui:build
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
|
||||
@@ -199,6 +199,8 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
if: steps.dist_build_cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1"
|
||||
run: pnpm build
|
||||
|
||||
- name: Pack publishable core packages
|
||||
@@ -244,6 +246,8 @@ jobs:
|
||||
|
||||
- name: Build Control UI
|
||||
if: steps.dist_build_cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1"
|
||||
run: pnpm ui:build
|
||||
|
||||
- name: Save preflight build outputs
|
||||
|
||||
@@ -2354,6 +2354,7 @@ const server = await createServer({
|
||||
commit: "0123456789abcdef0123456789abcdef01234567",
|
||||
commitAt: "2026-07-10T11:22:33.000Z",
|
||||
builtAt: "2026-07-10T12:34:56.000Z",
|
||||
release: false,
|
||||
buildId: "mock",
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ APP_VERSION_INPUT="${APP_VERSION:-}"
|
||||
# Default to universal binary for distribution builds (supports both Apple Silicon and Intel Macs)
|
||||
export BUILD_ARCHS="${BUILD_ARCHS:-all}"
|
||||
export BUILD_CONFIG
|
||||
export OPENCLAW_CONTROL_UI_RELEASE_BUILD=1
|
||||
DSYM_ARCHS_VALUE="$BUILD_ARCHS"
|
||||
if [[ "$DSYM_ARCHS_VALUE" == "all" ]]; then
|
||||
DSYM_ARCHS_VALUE="arm64 x86_64"
|
||||
|
||||
@@ -14,5 +14,5 @@ pnpm release:plugins:npm:check -- --selection-mode all-publishable
|
||||
pnpm release:plugins:clawhub:check -- --selection-mode all-publishable
|
||||
node --import tsx scripts/plugin-release-pretag-pack-check.ts
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
OPENCLAW_CONTROL_UI_RELEASE_BUILD=1 pnpm ui:build
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
@@ -165,10 +165,12 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
|
||||
// Only the build producers skip on a cache hit; every validation step
|
||||
// still runs against the restored artifacts.
|
||||
expect(step(preflight, "Build").if).toBe("steps.dist_build_cache.outputs.cache-hit != 'true'");
|
||||
expect(step(preflight, "Build Control UI").if).toBe(
|
||||
"steps.dist_build_cache.outputs.cache-hit != 'true'",
|
||||
);
|
||||
const build = step(preflight, "Build");
|
||||
const buildControlUi = step(preflight, "Build Control UI");
|
||||
expect(build.if).toBe("steps.dist_build_cache.outputs.cache-hit != 'true'");
|
||||
expect(build.env?.OPENCLAW_CONTROL_UI_RELEASE_BUILD).toBe("1");
|
||||
expect(buildControlUi.if).toBe("steps.dist_build_cache.outputs.cache-hit != 'true'");
|
||||
expect(buildControlUi.env?.OPENCLAW_CONTROL_UI_RELEASE_BUILD).toBe("1");
|
||||
expect(step(preflight, "Check").if).toBeUndefined();
|
||||
expect(step(preflight, "Verify release contents").if).toBeUndefined();
|
||||
expect(step(preflight, "Verify prepared npm tarball install").if).toBeUndefined();
|
||||
|
||||
@@ -4431,9 +4431,17 @@ wait_for_run plugin-clawhub-new.yml 123 "${expectedSha}" || status=$?
|
||||
"validate_macos_release_request",
|
||||
);
|
||||
const stepNames = validateJob.steps?.map((step) => step.name) ?? [];
|
||||
const buildControlUi = validateJob.steps?.find((step) => step.name === "Build Control UI");
|
||||
|
||||
expect(stepNames).not.toContain("Ensure matching GitHub release exists");
|
||||
expect(macosRelease.jobs?.validate_macos_release_request).toBeDefined();
|
||||
expect(buildControlUi?.env?.OPENCLAW_CONTROL_UI_RELEASE_BUILD).toBe("1");
|
||||
});
|
||||
|
||||
it("classifies fast pretag Control UI output as a release artifact", () => {
|
||||
const script = readFileSync("scripts/release-fast-pretag-check.sh", "utf8");
|
||||
|
||||
expect(script).toContain("OPENCLAW_CONTROL_UI_RELEASE_BUILD=1 pnpm ui:build");
|
||||
});
|
||||
|
||||
it("keeps every tracked repository skill visible to Git-aware syncs", () => {
|
||||
|
||||
@@ -91,6 +91,15 @@ describe("package-mac-dist plist validation", () => {
|
||||
expect(releaseBlock).not.toContain("*.debug");
|
||||
});
|
||||
|
||||
it("marks the distributed Control UI as an official release artifact", () => {
|
||||
const script = readFileSync(scriptPath, "utf8");
|
||||
const releaseMarkerIndex = script.indexOf("export OPENCLAW_CONTROL_UI_RELEASE_BUILD=1");
|
||||
const packageAppIndex = script.indexOf('"$ROOT_DIR/scripts/package-mac-app.sh"');
|
||||
|
||||
expect(releaseMarkerIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(packageAppIndex).toBeGreaterThan(releaseMarkerIndex);
|
||||
});
|
||||
|
||||
it("does not mask canonical Sparkle build failures for release packaging", () => {
|
||||
const script = readFileSync(scriptPath, "utf8");
|
||||
|
||||
|
||||
@@ -2033,7 +2033,7 @@ class OpenClawShell extends OpenClawLightDomElement {
|
||||
offline: gatewaySnapshot.offlineStable,
|
||||
queuedOutboxCount: storedOutboxes?.total ?? 0,
|
||||
lastError: gatewaySnapshot.lastError,
|
||||
version:
|
||||
gatewayVersion:
|
||||
context.config.current.serverVersion ?? gatewaySnapshot.hello?.server?.version ?? "",
|
||||
updateAvailable: navigationSurfaceHidden ? null : overlaySnapshot.updateAvailable,
|
||||
updateRunning: overlaySnapshot.updateRunning,
|
||||
|
||||
@@ -18,6 +18,7 @@ vi.mock("../build-info.ts", () => ({
|
||||
builtAt: null,
|
||||
branch: null,
|
||||
dirty: null,
|
||||
release: false,
|
||||
buildId: "test",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -62,6 +62,7 @@ describe("Control UI Vite config", () => {
|
||||
builtAt: "2026-07-10T12:34:56.000Z",
|
||||
branch: null,
|
||||
dirty: null,
|
||||
release: false,
|
||||
buildId: "2026.7.10-0123456789ab-2026-07-10T12-34-56.000Z",
|
||||
});
|
||||
expect(readGitCommit).not.toHaveBeenCalled();
|
||||
@@ -86,10 +87,44 @@ describe("Control UI Vite config", () => {
|
||||
builtAt: "2026-07-10T13:14:15.000Z",
|
||||
branch: null,
|
||||
dirty: null,
|
||||
release: false,
|
||||
buildId: "aaaaaaaaaaaa-2026-07-10T13-14-15.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("records release packaging as an explicit artifact fact", () => {
|
||||
expect(
|
||||
resolveControlUiBuildInfo({
|
||||
env: {
|
||||
OPENCLAW_CONTROL_UI_RELEASE_BUILD: "1",
|
||||
OPENCLAW_BUILD_TIMESTAMP: "2026-07-10T13:14:15.000Z",
|
||||
},
|
||||
readGitCommit: () => "a".repeat(40),
|
||||
readGitCommitTimestamp: () => null,
|
||||
readGitBranch: () => "release/2026.7.10",
|
||||
readGitDirty: () => false,
|
||||
readPackageVersion: () => "2026.7.10",
|
||||
}),
|
||||
).toMatchObject({
|
||||
version: "2026.7.10",
|
||||
commit: "a".repeat(40),
|
||||
branch: "release/2026.7.10",
|
||||
dirty: false,
|
||||
release: true,
|
||||
buildId: "2026.7.10-release-aaaaaaaaaaaa-2026-07-10T13-14-15.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects malformed release-build identity", () => {
|
||||
expect(() =>
|
||||
resolveControlUiBuildInfo({
|
||||
env: { OPENCLAW_CONTROL_UI_RELEASE_BUILD: "true" },
|
||||
readGitCommit: () => null,
|
||||
readPackageVersion: () => "2026.7.10",
|
||||
}),
|
||||
).toThrow("OPENCLAW_CONTROL_UI_RELEASE_BUILD must be 1 when set");
|
||||
});
|
||||
|
||||
it("uses checked-out Git instead of unverified GitHub workflow context", () => {
|
||||
const readGitCommit = vi.fn(() => "c".repeat(40));
|
||||
expect(
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import { truncateUtf16Safe } from "../../packages/normalization-core/src/utf16-slice.js";
|
||||
import type { ControlUiBuildInfo } from "./build-info-types.ts";
|
||||
|
||||
type ControlUiBuildMetadata = Pick<ControlUiBuildInfo, "version" | "commit" | "builtAt">;
|
||||
type ControlUiBuildMetadata = Pick<
|
||||
ControlUiBuildInfo,
|
||||
"version" | "commit" | "builtAt" | "release"
|
||||
>;
|
||||
|
||||
const FULL_GIT_SHA = /^[0-9a-f]{40}$/u;
|
||||
const UTC_BUILD_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u;
|
||||
@@ -44,7 +47,12 @@ function normalizeControlUiBuildId(value: unknown): string {
|
||||
}
|
||||
|
||||
function deriveControlUiBuildId(info: ControlUiBuildMetadata): string {
|
||||
const identity = [info.version, info.commit?.slice(0, 12), info.builtAt]
|
||||
const identity = [
|
||||
info.version,
|
||||
info.release ? "release" : null,
|
||||
info.commit?.slice(0, 12),
|
||||
info.builtAt,
|
||||
]
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.join("-");
|
||||
return normalizeControlUiBuildId(identity);
|
||||
@@ -57,7 +65,8 @@ export function normalizeControlUiBuildInfo(value: unknown): ControlUiBuildInfo
|
||||
const version = optionalString(record.version);
|
||||
const commit = normalizeControlUiCommit(record.commit);
|
||||
const builtAt = normalizeControlUiBuildTimestamp(record.builtAt);
|
||||
const metadata = { version, commit, builtAt };
|
||||
const release = record.release === true;
|
||||
const metadata = { version, commit, builtAt, release };
|
||||
return {
|
||||
...metadata,
|
||||
commitAt: normalizeControlUiBuildTimestamp(record.commitAt),
|
||||
|
||||
@@ -5,5 +5,6 @@ export type ControlUiBuildInfo = Readonly<{
|
||||
builtAt: string | null;
|
||||
branch: string | null;
|
||||
dirty: boolean | null;
|
||||
release: boolean;
|
||||
buildId: string;
|
||||
}>;
|
||||
|
||||
@@ -77,6 +77,7 @@ describe("Control UI build info", () => {
|
||||
builtAt: "later",
|
||||
branch: "HEAD",
|
||||
dirty: "yes",
|
||||
release: "yes",
|
||||
buildId: "",
|
||||
}),
|
||||
).toEqual({
|
||||
@@ -86,14 +87,18 @@ describe("Control UI build info", () => {
|
||||
builtAt: null,
|
||||
branch: null,
|
||||
dirty: null,
|
||||
release: false,
|
||||
buildId: "dev",
|
||||
});
|
||||
});
|
||||
|
||||
it("passes through normalized branch and boolean dirty state", () => {
|
||||
expect(normalizeControlUiBuildInfo({ branch: " feature/x ", dirty: false })).toMatchObject({
|
||||
it("passes through normalized branch, dirty state, and release identity", () => {
|
||||
expect(
|
||||
normalizeControlUiBuildInfo({ branch: " feature/x ", dirty: false, release: true }),
|
||||
).toMatchObject({
|
||||
branch: "feature/x",
|
||||
dirty: false,
|
||||
release: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,7 +108,8 @@ describe("Control UI build info", () => {
|
||||
version: "2026.7.10",
|
||||
commit: COMMIT,
|
||||
builtAt: "2026-07-10T12:34:56.000Z",
|
||||
release: true,
|
||||
}).buildId,
|
||||
).toBe("2026.7.10-0123456789ab-2026-07-10T12-34-56.000Z");
|
||||
).toBe("2026.7.10-release-0123456789ab-2026-07-10T12-34-56.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "model-setup",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -71,7 +71,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -101,7 +101,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -139,7 +139,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -194,7 +194,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -242,7 +242,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "agents",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -273,7 +273,7 @@ describe("settings sidebar search", () => {
|
||||
activeHash: "#memory-backend",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -318,7 +318,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -399,7 +399,7 @@ describe("settings sidebar search", () => {
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "",
|
||||
gatewayVersion: "",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
@@ -424,13 +424,14 @@ describe("settings sidebar search", () => {
|
||||
|
||||
it("keeps the update card above the settings footer", async () => {
|
||||
const onUpdate = vi.fn();
|
||||
const onNavigate = vi.fn();
|
||||
render(
|
||||
renderSettingsSidebar({
|
||||
basePath: "",
|
||||
activeRouteId: "appearance",
|
||||
offline: false,
|
||||
lastError: null,
|
||||
version: "1.0.0",
|
||||
gatewayVersion: "1.0.0",
|
||||
updateAvailable: {
|
||||
currentVersion: "1.0.0",
|
||||
latestVersion: "2.0.0",
|
||||
@@ -441,7 +442,7 @@ describe("settings sidebar search", () => {
|
||||
searchQuery: "",
|
||||
onExit: vi.fn(),
|
||||
onRetryConnect: vi.fn(),
|
||||
onNavigate: vi.fn(),
|
||||
onNavigate,
|
||||
onSearchQueryChange: vi.fn(),
|
||||
preloadTimers: new Map(),
|
||||
saveIndicator: saveIndicator(),
|
||||
@@ -456,6 +457,19 @@ describe("settings sidebar search", () => {
|
||||
expect(card?.nextElementSibling?.classList.contains("settings-sidebar__footer")).toBe(true);
|
||||
card?.querySelector<HTMLButtonElement>(".sidebar-update-card__action")?.click();
|
||||
expect(onUpdate).toHaveBeenCalledOnce();
|
||||
|
||||
const buildChip = container.querySelector<
|
||||
HTMLElement & {
|
||||
gatewayVersion: string | null;
|
||||
variant: string;
|
||||
updateComplete: Promise<boolean>;
|
||||
}
|
||||
>("openclaw-sidebar-build-chip");
|
||||
await buildChip?.updateComplete;
|
||||
expect(buildChip?.gatewayVersion).toBe("1.0.0");
|
||||
expect(buildChip?.variant).toBe("settings");
|
||||
buildChip?.querySelector<HTMLAnchorElement>(".sidebar-footer-build")?.click();
|
||||
expect(onNavigate).toHaveBeenCalledWith("about");
|
||||
});
|
||||
|
||||
it("shows the offline retry action without an online status", () => {
|
||||
@@ -468,7 +482,7 @@ describe("settings sidebar search", () => {
|
||||
offline,
|
||||
queuedOutboxCount,
|
||||
lastError,
|
||||
version: "1.0.0",
|
||||
gatewayVersion: "1.0.0",
|
||||
updateAvailable: null,
|
||||
updateRunning: false,
|
||||
onUpdate: vi.fn(),
|
||||
|
||||
@@ -23,6 +23,7 @@ import { redactLoginFailureError } from "./login-gate.ts";
|
||||
import { renderOfflineSidebarStatus } from "./session-row-badges.ts";
|
||||
import type { SettingsSaveIndicatorProps } from "./settings-save-indicator.ts";
|
||||
import "./settings-save-indicator.ts";
|
||||
import "./sidebar-build-chip.ts";
|
||||
import "./sidebar-update-card.ts";
|
||||
|
||||
type SettingsSidebarProps = {
|
||||
@@ -34,7 +35,7 @@ type SettingsSidebarProps = {
|
||||
offline: boolean;
|
||||
queuedOutboxCount?: number;
|
||||
lastError: string | null;
|
||||
version: string;
|
||||
gatewayVersion: string;
|
||||
updateAvailable: UpdateAvailable | null;
|
||||
updateRunning: boolean;
|
||||
onUpdate: () => void;
|
||||
@@ -329,9 +330,12 @@ export function renderSettingsSidebar(props: SettingsSidebarProps) {
|
||||
: html`<openclaw-settings-save-indicator
|
||||
.props=${props.saveIndicator}
|
||||
></openclaw-settings-save-indicator>`}
|
||||
${props.version
|
||||
? html`<span class="settings-sidebar__footer-version">${props.version}</span>`
|
||||
: nothing}
|
||||
<openclaw-sidebar-build-chip
|
||||
.basePath=${props.basePath}
|
||||
.gatewayVersion=${props.gatewayVersion || null}
|
||||
.variant=${"settings"}
|
||||
.onNavigate=${() => props.onNavigate("about")}
|
||||
></openclaw-sidebar-build-chip>
|
||||
</footer>
|
||||
</aside>
|
||||
`;
|
||||
|
||||
@@ -25,6 +25,31 @@ export function formatBuildChipText(info: ControlUiBuildInfo): string | null {
|
||||
return `${branch}${commit}`;
|
||||
}
|
||||
|
||||
export function formatSettingsBuildLabel(
|
||||
info: ControlUiBuildInfo,
|
||||
gatewayVersion: string | null,
|
||||
): string | null {
|
||||
const version = info.version ?? gatewayVersion;
|
||||
const commit = info.commit;
|
||||
if (!commit) {
|
||||
return version;
|
||||
}
|
||||
const compactBuild = formatBuildChipText(info);
|
||||
if (!compactBuild) {
|
||||
return version;
|
||||
}
|
||||
|
||||
if (info.release) {
|
||||
return version;
|
||||
}
|
||||
|
||||
const gitIdentity =
|
||||
info.branch && info.branch !== "main"
|
||||
? compactBuild
|
||||
: `git@${commit.slice(0, 7)}${info.dirty === true ? "*" : ""}`;
|
||||
return [version, gitIdentity].filter((value): value is string => Boolean(value)).join(" · ");
|
||||
}
|
||||
|
||||
function formatBuildCardDetails(info: ControlUiBuildInfo, gatewayVersion: string | null) {
|
||||
return {
|
||||
summary: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ControlUiBuildInfo } from "../build-info.ts";
|
||||
import { formatBuildChipText } from "./sidebar-build-chip-format.ts";
|
||||
import { formatBuildChipText, formatSettingsBuildLabel } from "./sidebar-build-chip-format.ts";
|
||||
|
||||
const COMMIT = "e8cbc62f0123456789abcdef0123456789abcdef";
|
||||
const BUILT_AT = "2026-07-10T12:00:00.000Z";
|
||||
@@ -13,6 +13,7 @@ function buildInfo(overrides: Partial<ControlUiBuildInfo> = {}): ControlUiBuildI
|
||||
builtAt: BUILT_AT,
|
||||
branch: "main",
|
||||
dirty: false,
|
||||
release: false,
|
||||
buildId: "test",
|
||||
...overrides,
|
||||
};
|
||||
@@ -67,3 +68,34 @@ describe("formatBuildChipText", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("formatSettingsBuildLabel", () => {
|
||||
it("keeps official release artifacts version-only", () => {
|
||||
expect(formatSettingsBuildLabel(buildInfo({ release: true }), "2026.7.9")).toBe("2026.7.10");
|
||||
});
|
||||
|
||||
it("adds a Git identity for clean main builds", () => {
|
||||
expect(formatSettingsBuildLabel(buildInfo(), "2026.7.9")).toBe("2026.7.10 · git@e8cbc62");
|
||||
});
|
||||
|
||||
it("adds branch and dirty provenance for development builds", () => {
|
||||
expect(formatSettingsBuildLabel(buildInfo({ branch: "feat/x", dirty: true }), "2026.7.9")).toBe(
|
||||
"2026.7.10 · feat/x@e8cbc62*",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to the Gateway version when artifact metadata is unavailable", () => {
|
||||
expect(
|
||||
formatSettingsBuildLabel(
|
||||
buildInfo({ version: null, commit: null, branch: null, dirty: null }),
|
||||
"2026.7.9",
|
||||
),
|
||||
).toBe("2026.7.9");
|
||||
});
|
||||
|
||||
it("keeps detached clean source builds distinguishable from releases", () => {
|
||||
expect(formatSettingsBuildLabel(buildInfo({ branch: null, dirty: false }), "2026.7.9")).toBe(
|
||||
"2026.7.10 · git@e8cbc62",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,11 @@ import { pathForRoute } from "../app-route-paths.ts";
|
||||
import { CONTROL_UI_BUILD_INFO } from "../build-info.ts";
|
||||
import { t } from "../i18n/index.ts";
|
||||
import { OpenClawLightDomContentsElement } from "../lit/openclaw-element.ts";
|
||||
import { formatBuildChipText, renderSidebarServerDetails } from "./sidebar-build-chip-format.ts";
|
||||
import {
|
||||
formatBuildChipText,
|
||||
formatSettingsBuildLabel,
|
||||
renderSidebarServerDetails,
|
||||
} from "./sidebar-build-chip-format.ts";
|
||||
import "./tooltip.ts";
|
||||
|
||||
function shouldHandleNavigationClick(event: MouseEvent): boolean {
|
||||
@@ -23,9 +27,13 @@ class SidebarBuildChip extends OpenClawLightDomContentsElement {
|
||||
@property({ attribute: false }) basePath = "";
|
||||
@property({ attribute: false }) gatewayVersion: string | null = null;
|
||||
@property({ attribute: false }) onNavigate?: (routeId: "about") => void;
|
||||
@property({ attribute: false }) variant: "compact" | "settings" = "compact";
|
||||
|
||||
override render() {
|
||||
const text = formatBuildChipText(CONTROL_UI_BUILD_INFO);
|
||||
const text =
|
||||
this.variant === "settings"
|
||||
? formatSettingsBuildLabel(CONTROL_UI_BUILD_INFO, this.gatewayVersion)
|
||||
: formatBuildChipText(CONTROL_UI_BUILD_INFO);
|
||||
if (!text) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ const BUILD_INFO: ControlUiBuildInfo = {
|
||||
builtAt: "2026-07-20T10:30:00.000Z",
|
||||
branch: "main",
|
||||
dirty: true,
|
||||
release: false,
|
||||
buildId: "test",
|
||||
};
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ describeControlUiE2e("Control UI Unicode build identity mocked Gateway E2E", ()
|
||||
builtAt: "2026-07-10T12:34:56.000Z",
|
||||
branch: RAW_BRANCH,
|
||||
dirty: true,
|
||||
release: false,
|
||||
buildId: "build-info-unicode-e2e",
|
||||
});
|
||||
browser = await chromium.launch({ executablePath: chromiumExecutablePath });
|
||||
|
||||
@@ -20,6 +20,7 @@ function createProps(overrides: Partial<AboutProps> = {}): AboutProps {
|
||||
builtAt: BUILT_AT,
|
||||
branch: "feature/build-chip",
|
||||
dirty: true,
|
||||
release: false,
|
||||
buildId: "test",
|
||||
},
|
||||
gatewayVersion: "2026.7.9",
|
||||
@@ -162,6 +163,7 @@ describe("renderAbout", () => {
|
||||
builtAt: null,
|
||||
branch: null,
|
||||
dirty: null,
|
||||
release: false,
|
||||
buildId: "dev",
|
||||
},
|
||||
gatewayVersion: null,
|
||||
|
||||
@@ -681,7 +681,7 @@ openclaw-settings-save-indicator {
|
||||
}
|
||||
}
|
||||
|
||||
.settings-sidebar__footer-version {
|
||||
.settings-sidebar__footer .sidebar-footer-build {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
@@ -306,6 +306,7 @@ export async function startControlUiE2eServer(
|
||||
builtAt: "2026-07-10T12:34:56.000Z",
|
||||
branch: null,
|
||||
dirty: false,
|
||||
release: false,
|
||||
buildId: "e2e",
|
||||
},
|
||||
): Promise<ControlUiE2eServer> {
|
||||
|
||||
+6
-1
@@ -223,7 +223,12 @@ export function resolveControlUiBuildInfo(
|
||||
normalizeControlUiBuildInfo({ branch: githubBranch }).branch ??
|
||||
normalizeControlUiBuildInfo({ branch: (sources.readGitBranch ?? readGitBranch)() }).branch;
|
||||
const dirty = (sources.readGitDirty ?? readGitDirty)();
|
||||
const metadata = { version, commit, builtAt };
|
||||
const releaseFlag = env.OPENCLAW_CONTROL_UI_RELEASE_BUILD?.trim();
|
||||
if (releaseFlag && releaseFlag !== "1") {
|
||||
throw new Error("OPENCLAW_CONTROL_UI_RELEASE_BUILD must be 1 when set");
|
||||
}
|
||||
const release = releaseFlag === "1";
|
||||
const metadata = { version, commit, builtAt, release };
|
||||
const explicitBuildId = env.OPENCLAW_CONTROL_UI_BUILD_ID?.trim();
|
||||
return {
|
||||
...metadata,
|
||||
|
||||
Reference in New Issue
Block a user