Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Comment on lines +615 to +616
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using shell redirection (2>/dev/null) inside execSync(...) is not portable (e.g., Windows uses 2>nul) and also implicitly relies on a shell. Prefer passing stdio options (e.g., ignore stderr) or using execFileSync with arguments to avoid shell-specific syntax.

Copilot uses AI. Check for mistakes.
} 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);
}
Comment on lines +612 to +621
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update check shells out to npm before any package-manager detection. If npm isn't installed/available (but pnpm/yarn is), clawatch update will always exit with “Could not reach npm registry”. Consider detecting an available package manager first and then querying the registry with that tool (or do a direct HTTPS registry request) so the command works in non-npm setups.

Copilot uses AI. Check for mistakes.

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}`));

Comment on lines +630 to +637
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version comparison only checks string equality. If the user is on a newer pre-release/dev build (or a different tag), this will still report an “update available” and potentially trigger a downgrade when running the install step. Consider doing a proper semver comparison (and handling non-semver strings) so you only offer an update when latest > current, and show a separate message when the current version is ahead.

Copilot uses AI. Check for mistakes.
// 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' };
}
Comment on lines +648 to +652
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These execSync calls include shell redirection (2>/dev/null) which isn’t portable and can break detection depending on the user’s shell/OS (same pattern appears in the yarn/pnpm checks below). Prefer suppressing stderr via execSync stdio options or using execFileSync rather than embedding redirects in the command string.

Copilot uses AI. Check for mistakes.
// 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' };
}
Comment on lines +661 to +665
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The yarn probe uses shell redirection (2>/dev/null) in the execSync command string. This isn’t portable across platforms/shells; prefer suppressing stderr via execSync stdio options or switching to execFileSync with args.

Copilot uses AI. Check for mistakes.
} 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' };
Comment on lines +669 to +672
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pnpm probe uses 2>/dev/null in the command string, which is Unix-shell specific and can break detection depending on OS/shell. Prefer suppressing stderr via execSync stdio options or use execFileSync with args.

Copilot uses AI. Check for mistakes.
}
} 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' };
Comment on lines +676 to +679
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probe also uses shell redirection (2>/dev/null). To keep the fallback detection cross-platform, prefer configuring stdio to ignore stderr (or use execFileSync('npm', ['--version'])) instead of relying on shell-specific redirect syntax.

Copilot uses AI. Check for mistakes.
} 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<string>((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();
Loading