feat(FN-2663): add cached update-check setting, APIs, and banner
- Add global updateCheckEnabled setting to core schema/types and wire dashboard command to cache update checks in the CLI - Implement dashboard server update-check cache module plus REST routes for status and refresh behavior - Add dashboard client hook, legacy API helpers, and UpdateAvailableBanner UI to show cached CLI update notices - Cover update-check server routes, hook behavior, banner rendering, and route registration with focused tests - Document update-check configuration and API behavior in architecture and settings reference docs
This commit is contained in:
@@ -46,6 +46,7 @@ import {
|
||||
resolveClaudeCliExtensionPaths,
|
||||
setCachedClaudeCliResolution,
|
||||
} from "./claude-cli-extension.js";
|
||||
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { DashboardTUI, DashboardLogSink, isTTYAvailable, type SystemInfo, type GitStatus, type GitCommit, type GitCommitDetail, type GitBranch, type GitWorktree, type FileEntry, type FileReadResult, type TaskStep as TUITaskStep, type TaskLogEntry as TUITaskLogEntry, type TaskDetailData, type TaskEvent } from "./dashboard-tui/index.js";
|
||||
|
||||
@@ -1812,6 +1813,30 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
? `${baseUrl}/?token=${encodeURIComponent(dashboardAuthToken)}`
|
||||
: baseUrl;
|
||||
|
||||
const updateMessage = await (async (): Promise<string | null> => {
|
||||
try {
|
||||
const updateCheckEnabled = await Promise.race<boolean>([
|
||||
isUpdateCheckEnabled(),
|
||||
new Promise<boolean>((resolve) => {
|
||||
setTimeout(() => resolve(false), 3_000);
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!updateCheckEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachedUpdate = getCachedUpdateStatus();
|
||||
if (!cachedUpdate?.updateAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `⬆ Update available: v${cachedUpdate.latestVersion} (current: v${cachedUpdate.currentVersion})`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
// ── TTY Mode: Set system info on TUI ───────────────────────────────
|
||||
//
|
||||
// In TTY mode, we populate the TUI System panel instead of printing
|
||||
@@ -2290,6 +2315,9 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
tui.log("AI engine paused");
|
||||
}
|
||||
tui.log("File watcher active");
|
||||
if (updateMessage) {
|
||||
tui.log(updateMessage);
|
||||
}
|
||||
} else {
|
||||
// ── Non-TTY Mode: Print plain-text banner ───────────────────────────
|
||||
//
|
||||
@@ -2320,6 +2348,9 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
console.log(` • cron: scheduled task execution`);
|
||||
}
|
||||
console.log(` File watcher: ✓ active`);
|
||||
if (updateMessage) {
|
||||
console.log(` ${updateMessage}`);
|
||||
}
|
||||
console.log(` Press Ctrl+C to stop`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
48
packages/cli/src/update-cache.ts
Normal file
48
packages/cli/src/update-cache.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { GlobalSettingsStore, resolveGlobalDir } from "@fusion/core";
|
||||
|
||||
type CachedUpdateStatus = {
|
||||
updateAvailable: boolean;
|
||||
latestVersion: string;
|
||||
currentVersion: string;
|
||||
};
|
||||
|
||||
type UpdateCachePayload = {
|
||||
updateAvailable?: unknown;
|
||||
latestVersion?: unknown;
|
||||
currentVersion?: unknown;
|
||||
};
|
||||
|
||||
export function getCachedUpdateStatus(): CachedUpdateStatus | null {
|
||||
try {
|
||||
const cachePath = join(resolveGlobalDir(), "update-check.json");
|
||||
const raw = readFileSync(cachePath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as UpdateCachePayload;
|
||||
|
||||
if (
|
||||
parsed.updateAvailable === true &&
|
||||
typeof parsed.latestVersion === "string" &&
|
||||
parsed.latestVersion.length > 0 &&
|
||||
typeof parsed.currentVersion === "string" &&
|
||||
parsed.currentVersion.length > 0
|
||||
) {
|
||||
return {
|
||||
updateAvailable: true,
|
||||
latestVersion: parsed.latestVersion,
|
||||
currentVersion: parsed.currentVersion,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isUpdateCheckEnabled(): Promise<boolean> {
|
||||
const store = new GlobalSettingsStore();
|
||||
await store.init();
|
||||
const settings = await store.getSettings();
|
||||
return settings.updateCheckEnabled !== false;
|
||||
}
|
||||
Reference in New Issue
Block a user