From 06f86f784f04121fd37b5fcefd26dd024595fa3c Mon Sep 17 00:00:00 2001 From: Gal Dayan Date: Thu, 12 Mar 2026 19:18:11 +0200 Subject: [PATCH] feat: add 'clawatch update' command (#55) - Check latest version from npm registry - Show current vs. latest version comparison - Prompt to update if newer version available - Auto-detect package manager (npm/yarn/pnpm) - Support --check flag for check-only mode - Graceful handling of offline/unreachable registry - Works across npm, yarn, pnpm global installs Closes #55 --- cli/src/cli.ts | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 89307e2..df8077e 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -599,4 +599,128 @@ program } }); +// --- Update command --- +program + .command('update') + .description('Check for updates and upgrade clawatch to the latest version') + .option('--check', 'Only check for updates without installing') + .action(async (opts) => { + const currentVersion = pkg.version; + console.log(chalk.bold('\nšŸ”„ ClaWatch Update\n')); + console.log(` Current version: ${chalk.cyan(currentVersion)}`); + + // 1. Fetch latest version from npm registry + console.log(chalk.gray(' Checking npm registry...')); + let latestVersion: string; + try { + latestVersion = execSync('npm view clawatch version 2>/dev/null', { encoding: 'utf-8' }).trim(); + } catch { + console.log(chalk.red('\n āŒ Could not reach npm registry.')); + console.log(chalk.yellow(' Check your internet connection and try again.\n')); + process.exit(1); + } + + if (!latestVersion) { + console.log(chalk.red('\n āŒ Could not determine latest version.\n')); + process.exit(1); + } + + console.log(` Latest version: ${chalk.cyan(latestVersion)}`); + + // 2. Compare versions + if (currentVersion === latestVersion) { + console.log(chalk.green('\n āœ… Already on the latest version!\n')); + process.exit(0); + } + + console.log(chalk.yellow(`\n ā¬†ļø Update available: ${currentVersion} → ${latestVersion}`)); + + // 3. If --check, stop here + if (opts.check) { + console.log(chalk.gray(' Run "clawatch update" (without --check) to install.\n')); + process.exit(0); + } + + // 4. Detect package manager and install type + const detectPackageManager = (): { manager: string; command: string } | null => { + // Check if installed globally via npm + try { + const npmGlobalPrefix = execSync('npm prefix -g 2>/dev/null', { encoding: 'utf-8' }).trim(); + const npmGlobalModules = path.join(npmGlobalPrefix, 'lib', 'node_modules', 'clawatch'); + if (fs.existsSync(npmGlobalModules)) { + return { manager: 'npm', command: 'npm update -g clawatch' }; + } + // Also check without /lib (Windows-style) + const npmGlobalModulesAlt = path.join(npmGlobalPrefix, 'node_modules', 'clawatch'); + if (fs.existsSync(npmGlobalModulesAlt)) { + return { manager: 'npm', command: 'npm update -g clawatch' }; + } + } catch { /* npm not available */ } + + // Check yarn global + try { + const yarnGlobalDir = execSync('yarn global dir 2>/dev/null', { encoding: 'utf-8' }).trim(); + if (yarnGlobalDir && fs.existsSync(path.join(yarnGlobalDir, 'node_modules', 'clawatch'))) { + return { manager: 'yarn', command: 'yarn global upgrade clawatch' }; + } + } catch { /* yarn not available */ } + + // Check pnpm global + try { + const pnpmGlobalDir = execSync('pnpm root -g 2>/dev/null', { encoding: 'utf-8' }).trim(); + if (pnpmGlobalDir && fs.existsSync(path.join(pnpmGlobalDir, 'clawatch'))) { + return { manager: 'pnpm', command: 'pnpm update -g clawatch' }; + } + } catch { /* pnpm not available */ } + + // Fallback: try npm if it exists + try { + execSync('npm --version 2>/dev/null', { encoding: 'utf-8' }); + return { manager: 'npm', command: 'npm install -g clawatch@latest' }; + } catch { /* no npm */ } + + return null; + }; + + const pm = detectPackageManager(); + + if (!pm) { + console.log(chalk.red('\n āŒ Could not detect package manager (npm, yarn, or pnpm).')); + console.log(chalk.yellow(' Install one first: https://nodejs.org\n')); + process.exit(1); + } + + // 5. Prompt user (unless stdin is not a TTY) + const readline = require('readline'); + if (process.stdin.isTTY) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + const answer = await new Promise((resolve) => { + rl.question(chalk.white(`\n Update via ${pm.manager}? [Y/n] `), (a: string) => { + rl.close(); + resolve(a.trim().toLowerCase()); + }); + }); + + if (answer && answer !== 'y' && answer !== 'yes') { + console.log(chalk.yellow('\n Update cancelled.\n')); + process.exit(0); + } + } + + // 6. Run update + console.log(chalk.blue(`\n Running: ${pm.command}\n`)); + try { + execSync(pm.command, { stdio: 'inherit' }); + console.log(chalk.green.bold(`\n āœ… Updated to ${latestVersion}!`)); + console.log(chalk.gray(' Restart ClaWatch to use the new version.\n')); + } catch (err) { + console.log(chalk.red(`\n āŒ Update failed. Try manually: ${pm.command}`)); + if (pm.manager === 'npm') { + console.log(chalk.yellow(' You may need: sudo npm install -g clawatch@latest')); + } + console.log(''); + process.exit(1); + } + }); + program.parse();