Managed deployments can now suppress self-update offers while unsupported installs receive actionable guidance. - Add an install-level environment policy for externally managed updates across CLI, server, and dashboard surfaces. - Detect missing npm shell failures and replace raw stderr with deployment-aware remediation. - Document managed-update behavior and add regression coverage for checks, installs, automation, and UI actions. Files changed: .changeset/fn-9188-managed-updates.md | 7 +++ docs/architecture.md | 6 +-- docs/cli-reference.md | 2 +- docs/dashboard-guide.md | 2 +- docs/settings-reference.md | 4 +- packages/cli/src/__tests__/update-cache.test.ts | 11 ++++- .../update-command-externally-managed.test.ts | 39 +++++++++++++++ packages/cli/src/commands/update.ts | 42 +++++++++++----- packages/cli/src/update-cache.ts | 3 +- .../core/src/__tests__/update-management.test.ts | 15 ++++++ packages/core/src/config/update-management.ts | 21 ++++++++ packages/core/src/index.gate.ts | 5 ++ packages/core/src/index.ts | 5 ++ packages/dashboard/app/api/client/health.ts | 3 ++ .../dashboard/app/components/SettingsModal.tsx | 4 ++ .../__tests__/SettingsModal.general.test.tsx | 20 ++++++++ .../__tests__/UpdateAvailableBanner.test.tsx | 26 ++++++++++ .../components/__tests__/settings-mobile.test.tsx | 24 +++++++++- .../__tests__/SystemControlsArea.test.tsx | 20 ++++++++ .../command-center/areas/SystemControlsArea.tsx | 6 +++ .../app/hooks/__tests__/useUpdateCheck.test.ts | 1 + packages/dashboard/app/hooks/useUpdateCheck.ts | 3 +- .../dashboard/src/__tests__/auto-update.test.ts | 10 ++++ .../dashboard/src/__tests__/update-check.test.ts | 56 ++++++++++++++++++++++ packages/dashboard/src/auto-update.ts | 9 +++- .../__tests__/register-update-check-routes.test.ts | 24 +++++++++- .../src/routes/register-update-check-routes.ts | 47 +++++++++++++++++- packages/dashboard/src/update-check.ts | 50 ++++++++++++++++--- 28 files changed, 433 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-9188 Fusion-Task-Lineage: 60b114b9-8bf7-434f-9d26-0c954aea45ba Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
87 lines
2.6 KiB
TypeScript
87 lines
2.6 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { GlobalSettingsStore, resolveGlobalDir, resolveUpdatesExternallyManaged } from "@fusion/core";
|
|
import type { UpdateChannel } from "@fusion/core";
|
|
|
|
type CachedUpdateStatus = {
|
|
updateAvailable: boolean;
|
|
latestVersion: string;
|
|
currentVersion: string;
|
|
channel?: UpdateChannel;
|
|
};
|
|
|
|
type UpdateCachePayload = {
|
|
updateAvailable?: unknown;
|
|
latestVersion?: unknown;
|
|
currentVersion?: unknown;
|
|
channel?: unknown;
|
|
};
|
|
|
|
export function getCachedUpdateStatus(currentVersion?: string): 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
|
|
) {
|
|
if (
|
|
typeof currentVersion === "string" &&
|
|
currentVersion.length > 0 &&
|
|
parsed.currentVersion !== currentVersion
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
updateAvailable: true,
|
|
latestVersion: parsed.latestVersion,
|
|
currentVersion: parsed.currentVersion,
|
|
channel: parsed.channel === "beta" ? "beta" : parsed.channel === "stable" ? "stable" : undefined,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function isUpdateCheckEnabled(): Promise<boolean> {
|
|
if (resolveUpdatesExternallyManaged()) return false;
|
|
const store = new GlobalSettingsStore();
|
|
await store.init();
|
|
const settings = await store.getSettings();
|
|
return settings.updateCheckEnabled !== false;
|
|
}
|
|
|
|
/*
|
|
FNXC:UpdateChannels 2026-07-19-13:00:
|
|
The persisted `updateChannel` global setting selects the release track for every
|
|
update surface. `fn update --channel <stable|beta>` both uses AND persists the
|
|
choice so the dashboard and desktop follow along. Absent/invalid = "stable".
|
|
*/
|
|
export async function getConfiguredUpdateChannel(): Promise<UpdateChannel> {
|
|
try {
|
|
const store = new GlobalSettingsStore();
|
|
await store.init();
|
|
const settings = await store.getSettings();
|
|
return settings.updateChannel === "beta" ? "beta" : "stable";
|
|
} catch {
|
|
return "stable";
|
|
}
|
|
}
|
|
|
|
export async function persistUpdateChannel(channel: UpdateChannel): Promise<void> {
|
|
const store = new GlobalSettingsStore();
|
|
await store.init();
|
|
const settings = await store.getSettings();
|
|
if (settings.updateChannel === channel) return;
|
|
await store.updateSettings({ updateChannel: channel });
|
|
}
|