import type { EnvironmentId, ServerConfig, ServerSelfUpdateCapability } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; import { APP_VERSION } from "./branding"; import { getLocalStorageItem, setLocalStorageItem } from "./hooks/useLocalStorage"; export interface VersionMismatch { readonly clientVersion: string; readonly serverVersion: string; readonly hint: string; } export const VERSION_MISMATCH_DISMISSALS_STORAGE_KEY = "t3code:version-mismatch-dismissals:v1"; const VersionMismatchDismissalsSchema = Schema.Struct({ keys: Schema.Array(Schema.String), }); type VersionMismatchDismissals = typeof VersionMismatchDismissalsSchema.Type; function normalizeVersion(version: string | null | undefined): string | null { const trimmed = version?.trim(); return trimmed && trimmed.length > 0 ? trimmed : null; } export function resolveVersionMismatch( serverVersion: string | null | undefined, ): VersionMismatch | null { const normalizedClientVersion = normalizeVersion(APP_VERSION); const normalizedServerVersion = normalizeVersion(serverVersion); if ( !normalizedClientVersion || !normalizedServerVersion || normalizedClientVersion === normalizedServerVersion ) { return null; } return { clientVersion: normalizedClientVersion, serverVersion: normalizedServerVersion, hint: "Version mismatch. Try syncing the client and server to the same T3 Code version.", }; } export function resolveServerConfigVersionMismatch( serverConfig: Pick | null | undefined, ): VersionMismatch | null { return resolveVersionMismatch(serverConfig?.environment.serverVersion); } /** The update path the connected server offers, or null when it only supports a manual relaunch (older servers, dev checkouts, Windows). */ export function resolveServerSelfUpdateCapability( serverConfig: Pick | null | undefined, ): ServerSelfUpdateCapability | null { return serverConfig?.environment.capabilities.serverSelfUpdate ?? null; } /** The command to hand users whose server cannot update itself. */ export function manualServerUpdateCommand(targetVersion: string): string { return `npx t3@${targetVersion}`; } /** One sentence telling the user how to resolve version skew for a server, matched to the update path it offers. */ export function serverUpdateGuidance( capability: ServerSelfUpdateCapability | null, serverLabel: string, ): string { switch (capability) { case "boot-service": case "respawn": return `Update the ${serverLabel} so they stay in sync.`; case "desktop-managed": return `The ${serverLabel} is run by the T3 Code desktop app on its machine — update the desktop app there to sync them.`; default: return `Relaunch the ${serverLabel} with the copied command to sync them.`; } } export function buildVersionMismatchDismissalKey( environmentId: EnvironmentId, mismatch: Pick, ): string { return `${environmentId}:${mismatch.clientVersion}:${mismatch.serverVersion}`; } function readVersionMismatchDismissals(): VersionMismatchDismissals { try { return ( getLocalStorageItem( VERSION_MISMATCH_DISMISSALS_STORAGE_KEY, VersionMismatchDismissalsSchema, ) ?? { keys: [] } ); } catch (error) { console.error("Could not read version-mismatch dismissals.", error); return { keys: [] }; } } function writeVersionMismatchDismissals(document: VersionMismatchDismissals): void { try { setLocalStorageItem( VERSION_MISMATCH_DISMISSALS_STORAGE_KEY, document, VersionMismatchDismissalsSchema, ); } catch (error) { console.error("Could not persist version-mismatch dismissals.", error); } } export function isVersionMismatchDismissed(dismissalKey: string | null | undefined): boolean { if (!dismissalKey) { return false; } return readVersionMismatchDismissals().keys.includes(dismissalKey); } export function dismissVersionMismatch(dismissalKey: string | null | undefined): void { if (!dismissalKey) { return; } const document = readVersionMismatchDismissals(); if (document.keys.includes(dismissalKey)) { return; } writeVersionMismatchDismissals({ keys: [...document.keys, dismissalKey], }); } export function appendVersionMismatchHint( message: string | null | undefined, mismatch: VersionMismatch | null | undefined, ): string | null { const normalizedMessage = normalizeVersion(message); if (!normalizedMessage) { return mismatch?.hint ?? null; } if (!mismatch) { return normalizedMessage; } return `${normalizedMessage} Hint: ${mismatch.hint}`; }