UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

156 lines (155 loc) 5.38 kB
/** * Update State Persistence * Manages persistent state for the proxy auto-update feature. * Tracks check timestamps, suppressed versions, and update history. * * State file location: ~/.neurolink/update-state.json * Suppressed versions expire after 24 hours. */ import fs from "fs"; import os from "os"; import path from "path"; // ============================================ // Constants // ============================================ const STATE_FILENAME = "update-state.json"; const SUPPRESSION_TTL_MS = 86_400_000; // 24 hours // ============================================ // Internal Helpers // ============================================ /** * Resolve the path to the update state file. * Accepts an override for testing; defaults to ~/.neurolink/update-state.json. */ function resolveStatePath(overridePath) { if (overridePath) { return overridePath; } return path.join(os.homedir(), ".neurolink", STATE_FILENAME); } /** * Ensure the parent directory of the given file path exists. */ function ensureParentDir(filePath) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } // ============================================ // Exported Functions // ============================================ /** * Return an empty/initial UpdateState. */ export function getDefaultUpdateState() { return { lastCheckAt: new Date(0).toISOString(), lastCheckVersion: "", suppressedVersions: {}, lastUpdateAt: null, lastUpdateVersion: null, }; } /** * Load the update state from disk. * Returns null if the file does not exist. * Returns the default state if the file contains corrupt JSON. * * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json) */ export function loadUpdateState(stateFilePath) { const filePath = resolveStatePath(stateFilePath); try { if (!fs.existsSync(filePath)) { return null; } const content = fs.readFileSync(filePath, "utf8"); const parsed = JSON.parse(content); // Minimal shape check — reject valid JSON that isn't an UpdateState // Note: typeof null === "object", so we check both if (typeof parsed !== "object" || parsed === null || typeof parsed.suppressedVersions !== "object" || parsed.suppressedVersions === null || Array.isArray(parsed.suppressedVersions) || typeof parsed.lastCheckAt !== "string") { return getDefaultUpdateState(); } return parsed; } catch { // Corrupt or unreadable JSON — return default state return getDefaultUpdateState(); } } /** * Save the update state to disk. * * @param state - The UpdateState to persist * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json) */ export function saveUpdateState(state, stateFilePath) { const filePath = resolveStatePath(stateFilePath); ensureParentDir(filePath); // Atomic write: write to temp file then rename to prevent corruption on crash const tmpPath = filePath + ".tmp"; fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2)); fs.renameSync(tmpPath, filePath); } /** * Check whether a version is currently suppressed (i.e., suppressed AND within the 24-hour window). * * @param version - Semver version string to check * @param stateFilePath - Override path for testing */ export function isVersionSuppressed(version, stateFilePath) { const state = loadUpdateState(stateFilePath); if (!state) { return false; } const entry = state.suppressedVersions[version]; if (!entry) { return false; } return Date.now() - Date.parse(entry.suppressedAt) < SUPPRESSION_TTL_MS; } /** * Add a version to the suppressed list and persist. * * @param version - Semver version string to suppress * @param reason - Human-readable reason for suppression * @param stateFilePath - Override path for testing */ export function suppressVersion(version, reason, stateFilePath) { const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState(); state.suppressedVersions[version] = { suppressedAt: new Date().toISOString(), reason, }; saveUpdateState(state, stateFilePath); } /** * Record a successful update: set lastUpdateAt and lastUpdateVersion, then persist. * * @param version - The version that was successfully installed * @param stateFilePath - Override path for testing */ export function recordSuccessfulUpdate(version, stateFilePath) { const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState(); state.lastUpdateAt = new Date().toISOString(); state.lastUpdateVersion = version; saveUpdateState(state, stateFilePath); } /** * Record an update check: set lastCheckAt and lastCheckVersion, then persist. * * @param latestVersion - The latest version found during the check * @param stateFilePath - Override path for testing */ export function recordCheck(latestVersion, stateFilePath) { const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState(); state.lastCheckAt = new Date().toISOString(); state.lastCheckVersion = latestVersion; saveUpdateState(state, stateFilePath); }