import Conf from "conf"; import chalk from "chalk"; import { isRefineUptoDate } from "@commands/check-updates"; import { printUpdateWarningTable } from "@components/update-warning-table"; import { RefinePackageInstalledVersionData } from "@definitions/package"; import { getInstalledRefinePackages } from "@utils/package"; import { ENV } from "@utils/env"; import { stringToBase64 } from "@utils/encode"; const STORE_NAME = "refine-update-notifier"; export interface Store { key: string; lastUpdated: number; packages: RefinePackageInstalledVersionData[]; } export const store = new Conf({ projectName: STORE_NAME, defaults: { key: "", lastUpdated: 0, packages: [], }, }); // update notifier should not throw any unhandled error to prevent breaking user workflow. export const updateNotifier = async () => { if (isUpdateNotifierDisabled()) return; const shouldUpdate = await shouldUpdatePackagesCache(); if (shouldUpdate === null) return; if (shouldUpdate) { updatePackagesCache(); return; } showWarning(); updatePackagesCache(); }; /** * renders outdated packages table if there is any */ const showWarning = async () => { const packages = store.get("packages"); if (!packages?.length) return; await printUpdateWarningTable({ data: packages }); console.log("\n"); }; /** * @returns `null` It's mean something went wrong while checking key or cache. so we should not update cache. * @returns `boolean` if cache should be updated or not * if cache is expired or key is invalid, update cache in background and not show warning */ export const shouldUpdatePackagesCache = async () => { const isKeyValid = await validateKey(); const isExpired = isPackagesCacheExpired(); if (isKeyValid === null) return null; if (isExpired || !isKeyValid) return true; return false; }; /** * @returns `null` something went wrong * @returns `packages` if packages updated */ export const updatePackagesCache = async () => { try { const packages = await isRefineUptoDate(); store.set("packages", packages); store.set("lastUpdated", Date.now()); store.set("key", await generateKeyFromPackages()); return packages; } catch (error) { // invalidate store store.set("packages", []); store.set("lastUpdated", Date.now()); store.set("key", ""); return null; } }; export const isPackagesCacheExpired = () => { const lastUpdated = store.get("lastUpdated"); if (!lastUpdated) return true; const now = Date.now(); const diff = now - lastUpdated; const cacheTTL = Number(ENV.UPDATE_NOTIFIER_CACHE_TTL); return diff >= cacheTTL; }; /** * @returns `true` if key is valid * @returns `false` if key is invalid * @returns `null` if there is an error */ export const validateKey = async () => { const key = store.get("key"); const newKey = await generateKeyFromPackages(); if (newKey === null) return null; return key === newKey; }; /** * @returns `null` if there is an error * @returns `string` if key is generated */ export const generateKeyFromPackages = async () => { const packages = await getInstalledRefinePackages(); if (!packages) { console.error( chalk.red( `Something went wrong when trying to get installed \`refine\` packages.`, ), ); return null; } const currentVersionsWithName = packages.map( (p) => `${p.name}@${p.version}`, ); const hash = stringToBase64(currentVersionsWithName.toString()); return hash; }; export const isUpdateNotifierDisabled = () => { return ENV.UPDATE_NOTIFIER_IS_DISABLED.toLocaleLowerCase() === "true"; };