import { requestServerShutdown } from './cli-http.js'; import { discoverRunningInstances } from './cli-lifecycle.js'; import { readInstanceOptions, removePidFile, stopInstanceProcess, } from './cli-process.js'; import { intro as clackIntro, outro as clackOutro, isJsonMode, isQuietMode, shouldRenderHumanOutput, createSpinner, printJson, logStatus, } from '../cli-output.js'; function createUpdateCommand({ importFromFilePath, packageManagerPath, serveCommand }) { return async function updateCommand(options = {}) { const showOutput = shouldRenderHumanOutput(options); const updateSpin = createSpinner(options); const { checkForUpdates, executeUpdate, detectPackageManager, getCurrentVersion, } = await importFromFilePath(packageManagerPath); const runningInstances = await discoverRunningInstances(); const currentVersion = getCurrentVersion(); if (showOutput) { clackIntro('OpenChamber Update'); } if (showOutput && !updateSpin) { logStatus('info', `current version: ${currentVersion}`); } updateSpin?.start('Checking for updates...'); const updateInfo = await checkForUpdates(); if (updateInfo.error) { updateSpin?.error('Update check failed'); if (showOutput) { clackOutro('update failed'); } throw new Error(updateInfo.error); } if (!updateInfo.available) { if (isJsonMode(options)) { printJson({ currentVersion, latestVersion: updateInfo.version || currentVersion, updated: false, }); return; } if (showOutput && !updateSpin) { logStatus('success', 'you are running the latest version'); } updateSpin?.stop('Already up to date'); if (showOutput) { clackOutro('no update needed'); } else if (isQuietMode(options)) { process.stdout.write(`up-to-date ${currentVersion}\n`); } return; } if (showOutput && !updateSpin) { logStatus('info', `updating ${updateInfo.currentVersion || currentVersion} -> ${updateInfo.version || 'latest'}`); } updateSpin?.message(`Updating to ${updateInfo.version || 'latest'}...`); if (runningInstances.length > 0) { updateSpin?.message(`Stopping ${runningInstances.length} running instance(s)...`); for (const instance of runningInstances) { try { const requested = await requestServerShutdown(instance.port, instance.host); await stopInstanceProcess(instance.pid, { shutdownWaitMs: requested ? 5000 : 0, gracefulTimeoutMs: 2500, forceTimeoutMs: 3000, }); removePidFile(instance.pidFilePath); } catch { } } } const pm = detectPackageManager(); const result = executeUpdate(pm, { silent: isJsonMode(options) || isQuietMode(options) }); if (!result.success) { updateSpin?.error('Update failed'); if (showOutput) { clackOutro('update failed'); } throw new Error(`Update failed with exit code ${result.exitCode}`); } if (runningInstances.length > 0) { updateSpin?.message(`Restarting ${runningInstances.length} instance(s)...`); for (const instance of runningInstances) { const storedOptions = readInstanceOptions(instance.instanceFilePath) || { port: instance.port }; await serveCommand({ port: storedOptions.port || instance.port, host: storedOptions.host, explicitPort: true, uiPassword: storedOptions.uiPassword, suppressStartupSummary: true, suppressUiPasswordWarning: true, quiet: true, }); } } if (showOutput && !updateSpin) { logStatus('success', `updated to ${updateInfo.version || 'latest'}`); } updateSpin?.stop(`Updated to ${updateInfo.version || 'latest'}`); if (isJsonMode(options)) { printJson({ currentVersion, latestVersion: updateInfo.version || 'latest', updated: true, restartedCount: runningInstances.length, }); return; } if (showOutput) { clackOutro('update complete'); } else if (isQuietMode(options)) { process.stdout.write(`updated ${updateInfo.version || 'latest'}\n`); } }; } export { createUpdateCommand };