UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

142 lines 5.31 kB
import { readFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { loadPreferences, savePreferences } from '../config/preferences.js'; import { TIMEOUT_UPDATE_CHECK_MS } from '../constants.js'; import { logError } from '../utils/message-queue.js'; import { detectInstallationMethod } from './installation-detector.js'; const UPDATE_COMMANDS = { NPM: 'npm update -g @nanocollective/nanocoder', // Check if package exists before upgrading to provide better error messages HOMEBREW: 'brew list nanocoder >/dev/null 2>&1 && brew upgrade nanocoder || (echo "Error: nanocoder not found in Homebrew. Please install it first with: brew install nanocoder" && exit 1)', }; const UPDATE_MESSAGES = { NIX: 'To update, re-run: nix run github:Nano-Collective/nanocoder (or update your flake).', UNKNOWN: 'A new version is available. Please update using your package manager.', }; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Compare two semver version strings * Returns true if latest is greater than current */ function isNewerVersion(current, latest) { const parseVersion = (version) => { const clean = version.replace(/^v/, '').split('-')[0]; // Remove 'v' prefix and pre-release info return clean.split('.').map(num => parseInt(num) || 0); }; const currentParts = parseVersion(current); const latestParts = parseVersion(latest); const maxLength = Math.max(currentParts.length, latestParts.length); for (let i = 0; i < maxLength; i++) { const currentPart = currentParts[i] || 0; const latestPart = latestParts[i] || 0; if (latestPart > currentPart) { return true; } else if (latestPart < currentPart) { return false; } } return false; } function getCurrentVersion() { try { const packageJsonPath = join(__dirname, '../../package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); return packageJson.version; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Failed to read current version: ${errorMessage}`); return '0.0.0'; } } /** * Fetch the latest version from npm registry */ async function fetchLatestVersion() { try { const response = await fetch('https://registry.npmjs.org/@nanocollective/nanocoder/latest', { method: 'GET', headers: { Accept: 'application/json', 'User-Agent': 'nanocoder-update-checker', }, // Add timeout signal: AbortSignal.timeout(TIMEOUT_UPDATE_CHECK_MS), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = (await response.json()); return data.version; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Failed to fetch latest version: ${errorMessage}`); return null; } } /** * Update the last update check timestamp in preferences */ function updateLastCheckTime() { const preferences = loadPreferences(); preferences.lastUpdateCheck = Date.now(); savePreferences(preferences); } /** * Check for package updates */ export async function checkForUpdates() { const currentVersion = getCurrentVersion(); try { const latestVersion = await fetchLatestVersion(); updateLastCheckTime(); if (!latestVersion) { return { hasUpdate: false, currentVersion, }; } const hasUpdate = isNewerVersion(currentVersion, latestVersion); function getUpdateDetails(hasUpdate) { if (!hasUpdate) { return {}; } const method = detectInstallationMethod(); // Use constants defined at top of file for maintainability switch (method) { case 'npm': return { command: UPDATE_COMMANDS.NPM }; case 'homebrew': return { command: UPDATE_COMMANDS.HOMEBREW }; case 'nix': return { message: UPDATE_MESSAGES.NIX }; default: // For 'unknown' fallback to a general message (do not attempt to run a command) return { message: UPDATE_MESSAGES.UNKNOWN }; } } const updateDetails = getUpdateDetails(hasUpdate); return { hasUpdate, currentVersion, latestVersion, updateCommand: updateDetails.command, updateMessage: updateDetails.message, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Update check failed: ${errorMessage}`); // Still update the timestamp to prevent hammering the API on repeated failures updateLastCheckTime(); return { hasUpdate: false, currentVersion, }; } } //# sourceMappingURL=update-checker.js.map