From 3a79c73c4470dda9a233648cb9e2dfb6485556e8 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 27 Nov 2025 20:13:31 -0500 Subject: [PATCH] refactor(ccs): extract handleUpdateCommand to separate module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract handleUpdateCommand logic from ccs.ts into new src/commands/update-command.ts module. Reduces ccs.ts from 343→92 lines in this refactoring, continuing monolithic file breakdown. Maintains all 39 passing tests and zero critical issues. --- src/ccs.ts | 251 +--------------------------- src/commands/update-command.ts | 296 +++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 250 deletions(-) create mode 100644 src/commands/update-command.ts diff --git a/src/ccs.ts b/src/ccs.ts index 23b3fe7f..04bf9179 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -10,7 +10,6 @@ import { spawn, ChildProcess } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; -import { colored } from './utils/helpers'; import { detectClaudeCli } from './utils/claude-detector'; import { getSettingsPath } from './utils/config-manager'; import { ErrorManager } from './utils/error-manager'; @@ -22,258 +21,10 @@ import { handleInstallCommand, handleUninstallCommand } from './commands/install import { handleDoctorCommand } from './commands/doctor-command'; import { handleSyncCommand } from './commands/sync-command'; import { handleShellCompletionCommand } from './commands/shell-completion-command'; +import { handleUpdateCommand } from './commands/update-command'; // Import extracted utility functions import { execClaude, escapeShellArg } from './utils/shell-executor'; -import { detectInstallationMethod, detectPackageManager } from './utils/package-manager-detector'; - -// Version (sync with package.json) -const CCS_VERSION = JSON.parse( - fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8') -).version; - -// ========== Profile Detection ========== - -async function handleUpdateCommand(): Promise { - const { checkForUpdates } = await import('./utils/update-checker'); - - console.log(''); - console.log(colored('Checking for updates...', 'cyan')); - console.log(''); - - const installMethod = detectInstallationMethod(); - const isNpmInstall = installMethod === 'npm'; - - const updateResult = await checkForUpdates(CCS_VERSION, true, installMethod); - - if (updateResult.status === 'check_failed') { - console.log(colored(`[X] ${updateResult.message}`, 'red')); - console.log(''); - console.log(colored('[i] Possible causes:', 'yellow')); - console.log(' - Network connection issues'); - console.log(' - Firewall blocking requests'); - console.log(' - GitHub/npm API temporarily unavailable'); - console.log(''); - console.log('Try again later or update manually:'); - if (isNpmInstall) { - const packageManager = detectPackageManager(); - let manualCommand: string; - - switch (packageManager) { - case 'npm': - manualCommand = 'npm install -g @kaitranntt/ccs@latest'; - break; - case 'yarn': - manualCommand = 'yarn global add @kaitranntt/ccs@latest'; - break; - case 'pnpm': - manualCommand = 'pnpm add -g @kaitranntt/ccs@latest'; - break; - case 'bun': - manualCommand = 'bun add -g @kaitranntt/ccs@latest'; - break; - default: - manualCommand = 'npm install -g @kaitranntt/ccs@latest'; - } - - console.log(colored(` ${manualCommand}`, 'yellow')); - } else { - const isWindows = process.platform === 'win32'; - if (isWindows) { - console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); - } else { - console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); - } - } - console.log(''); - process.exit(1); - } - - if (updateResult.status === 'no_update') { - let message = `You are already on the latest version (${CCS_VERSION})`; - - switch (updateResult.reason) { - case 'dismissed': - message = `Update dismissed. You are on version ${CCS_VERSION}`; - console.log(colored(`[i] ${message}`, 'yellow')); - break; - case 'cached': - message = `No updates available (cached result). You are on version ${CCS_VERSION}`; - console.log(colored(`[i] ${message}`, 'cyan')); - break; - default: - console.log(colored(`[OK] ${message}`, 'green')); - } - console.log(''); - process.exit(0); - } - - // Update available - console.log( - colored(`[i] Update available: ${updateResult.current} -> ${updateResult.latest}`, 'yellow') - ); - console.log(''); - - if (isNpmInstall) { - const packageManager = detectPackageManager(); - let updateCommand: string; - let updateArgs: string[]; - let cacheCommand: string | null; - let cacheArgs: string[] | null; - - switch (packageManager) { - case 'npm': - updateCommand = 'npm'; - updateArgs = ['install', '-g', '@kaitranntt/ccs@latest']; - cacheCommand = 'npm'; - cacheArgs = ['cache', 'clean', '--force']; - break; - case 'yarn': - updateCommand = 'yarn'; - updateArgs = ['global', 'add', '@kaitranntt/ccs@latest']; - cacheCommand = 'yarn'; - cacheArgs = ['cache', 'clean']; - break; - case 'pnpm': - updateCommand = 'pnpm'; - updateArgs = ['add', '-g', '@kaitranntt/ccs@latest']; - cacheCommand = 'pnpm'; - cacheArgs = ['store', 'prune']; - break; - case 'bun': - updateCommand = 'bun'; - updateArgs = ['add', '-g', '@kaitranntt/ccs@latest']; - cacheCommand = null; - cacheArgs = null; - break; - default: - updateCommand = 'npm'; - updateArgs = ['install', '-g', '@kaitranntt/ccs@latest']; - cacheCommand = 'npm'; - cacheArgs = ['cache', 'clean', '--force']; - } - - console.log(colored(`Updating via ${packageManager}...`, 'cyan')); - console.log(''); - - const performUpdate = (): void => { - const child = spawn(updateCommand, updateArgs, { - stdio: 'inherit', - }); - - child.on('exit', (code) => { - if (code === 0) { - console.log(''); - console.log(colored('[OK] Update successful!', 'green')); - console.log(''); - console.log(`Run ${colored('ccs --version', 'yellow')} to verify`); - console.log(''); - } else { - console.log(''); - console.log(colored('[X] Update failed', 'red')); - console.log(''); - console.log('Try manually:'); - console.log(colored(` ${updateCommand} ${updateArgs.join(' ')}`, 'yellow')); - console.log(''); - } - process.exit(code || 0); - }); - - child.on('error', () => { - console.log(''); - console.log(colored(`[X] Failed to run ${packageManager} update`, 'red')); - console.log(''); - console.log('Try manually:'); - console.log(colored(` ${updateCommand} ${updateArgs.join(' ')}`, 'yellow')); - console.log(''); - process.exit(1); - }); - }; - - if (cacheCommand && cacheArgs) { - console.log(colored('Clearing package cache...', 'cyan')); - const cacheChild = spawn(cacheCommand, cacheArgs, { - stdio: 'inherit', - }); - - cacheChild.on('exit', (code) => { - if (code !== 0) { - console.log(colored('[!] Cache clearing failed, proceeding anyway...', 'yellow')); - } - performUpdate(); - }); - - cacheChild.on('error', () => { - console.log(colored('[!] Cache clearing failed, proceeding anyway...', 'yellow')); - performUpdate(); - }); - } else { - performUpdate(); - } - } else { - // Direct installation - re-run installer - console.log(colored('Updating via installer...', 'cyan')); - console.log(''); - - const isWindows = process.platform === 'win32'; - let command: string; - let args: string[]; - - if (isWindows) { - command = 'powershell.exe'; - args = [ - '-NoProfile', - '-ExecutionPolicy', - 'Bypass', - '-Command', - 'irm ccs.kaitran.ca/install | iex', - ]; - } else { - command = '/bin/bash'; - args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash']; - } - - const child = spawn(command, args, { - stdio: 'inherit', - }); - - child.on('exit', (code) => { - if (code === 0) { - console.log(''); - console.log(colored('[OK] Update successful!', 'green')); - console.log(''); - console.log(`Run ${colored('ccs --version', 'yellow')} to verify`); - console.log(''); - } else { - console.log(''); - console.log(colored('[X] Update failed', 'red')); - console.log(''); - console.log('Try manually:'); - if (isWindows) { - console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); - } else { - console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); - } - console.log(''); - } - process.exit(code || 0); - }); - - child.on('error', () => { - console.log(''); - console.log(colored('[X] Failed to run installer', 'red')); - console.log(''); - console.log('Try manually:'); - if (isWindows) { - console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); - } else { - console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); - } - console.log(''); - process.exit(1); - }); - } -} // ========== Profile Detection ========== diff --git a/src/commands/update-command.ts b/src/commands/update-command.ts new file mode 100644 index 00000000..e360ce55 --- /dev/null +++ b/src/commands/update-command.ts @@ -0,0 +1,296 @@ +/** + * Update Command Handler + * + * Handles `ccs update` command - checks for updates and installs latest version. + * Supports both npm and direct installation methods. + */ + +import { spawn } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; +import { colored } from '../utils/helpers'; +import { detectInstallationMethod, detectPackageManager } from '../utils/package-manager-detector'; + +// Version (sync with package.json) +const CCS_VERSION = JSON.parse( + fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8') +).version; + +/** + * Handle the update command + * Checks for updates and installs the latest version + */ +export async function handleUpdateCommand(): Promise { + const { checkForUpdates } = await import('../utils/update-checker'); + + console.log(''); + console.log(colored('Checking for updates...', 'cyan')); + console.log(''); + + const installMethod = detectInstallationMethod(); + const isNpmInstall = installMethod === 'npm'; + + const updateResult = await checkForUpdates(CCS_VERSION, true, installMethod); + + if (updateResult.status === 'check_failed') { + handleCheckFailed(updateResult.message ?? 'Update check failed', isNpmInstall); + return; + } + + if (updateResult.status === 'no_update') { + handleNoUpdate(updateResult.reason); + return; + } + + // Update available + console.log( + colored(`[i] Update available: ${updateResult.current} -> ${updateResult.latest}`, 'yellow') + ); + console.log(''); + + if (isNpmInstall) { + await performNpmUpdate(); + } else { + await performDirectUpdate(); + } +} + +/** + * Handle failed update check + */ +function handleCheckFailed(message: string, isNpmInstall: boolean): void { + console.log(colored(`[X] ${message}`, 'red')); + console.log(''); + console.log(colored('[i] Possible causes:', 'yellow')); + console.log(' - Network connection issues'); + console.log(' - Firewall blocking requests'); + console.log(' - GitHub/npm API temporarily unavailable'); + console.log(''); + console.log('Try again later or update manually:'); + + if (isNpmInstall) { + const packageManager = detectPackageManager(); + let manualCommand: string; + + switch (packageManager) { + case 'npm': + manualCommand = 'npm install -g @kaitranntt/ccs@latest'; + break; + case 'yarn': + manualCommand = 'yarn global add @kaitranntt/ccs@latest'; + break; + case 'pnpm': + manualCommand = 'pnpm add -g @kaitranntt/ccs@latest'; + break; + case 'bun': + manualCommand = 'bun add -g @kaitranntt/ccs@latest'; + break; + default: + manualCommand = 'npm install -g @kaitranntt/ccs@latest'; + } + + console.log(colored(` ${manualCommand}`, 'yellow')); + } else { + const isWindows = process.platform === 'win32'; + if (isWindows) { + console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); + } else { + console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); + } + } + console.log(''); + process.exit(1); +} + +/** + * Handle no update available + */ +function handleNoUpdate(reason: string | undefined): void { + const CCS_VERSION_LOCAL = JSON.parse( + fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8') + ).version; + + let message = `You are already on the latest version (${CCS_VERSION_LOCAL})`; + + switch (reason) { + case 'dismissed': + message = `Update dismissed. You are on version ${CCS_VERSION_LOCAL}`; + console.log(colored(`[i] ${message}`, 'yellow')); + break; + case 'cached': + message = `No updates available (cached result). You are on version ${CCS_VERSION_LOCAL}`; + console.log(colored(`[i] ${message}`, 'cyan')); + break; + default: + console.log(colored(`[OK] ${message}`, 'green')); + } + console.log(''); + process.exit(0); +} + +/** + * Perform update via npm/yarn/pnpm/bun + */ +async function performNpmUpdate(): Promise { + const packageManager = detectPackageManager(); + let updateCommand: string; + let updateArgs: string[]; + let cacheCommand: string | null; + let cacheArgs: string[] | null; + + switch (packageManager) { + case 'npm': + updateCommand = 'npm'; + updateArgs = ['install', '-g', '@kaitranntt/ccs@latest']; + cacheCommand = 'npm'; + cacheArgs = ['cache', 'clean', '--force']; + break; + case 'yarn': + updateCommand = 'yarn'; + updateArgs = ['global', 'add', '@kaitranntt/ccs@latest']; + cacheCommand = 'yarn'; + cacheArgs = ['cache', 'clean']; + break; + case 'pnpm': + updateCommand = 'pnpm'; + updateArgs = ['add', '-g', '@kaitranntt/ccs@latest']; + cacheCommand = 'pnpm'; + cacheArgs = ['store', 'prune']; + break; + case 'bun': + updateCommand = 'bun'; + updateArgs = ['add', '-g', '@kaitranntt/ccs@latest']; + cacheCommand = null; + cacheArgs = null; + break; + default: + updateCommand = 'npm'; + updateArgs = ['install', '-g', '@kaitranntt/ccs@latest']; + cacheCommand = 'npm'; + cacheArgs = ['cache', 'clean', '--force']; + } + + console.log(colored(`Updating via ${packageManager}...`, 'cyan')); + console.log(''); + + const performUpdate = (): void => { + const child = spawn(updateCommand, updateArgs, { + stdio: 'inherit', + }); + + child.on('exit', (code) => { + if (code === 0) { + console.log(''); + console.log(colored('[OK] Update successful!', 'green')); + console.log(''); + console.log(`Run ${colored('ccs --version', 'yellow')} to verify`); + console.log(''); + } else { + console.log(''); + console.log(colored('[X] Update failed', 'red')); + console.log(''); + console.log('Try manually:'); + console.log(colored(` ${updateCommand} ${updateArgs.join(' ')}`, 'yellow')); + console.log(''); + } + process.exit(code || 0); + }); + + child.on('error', () => { + console.log(''); + console.log(colored(`[X] Failed to run ${packageManager} update`, 'red')); + console.log(''); + console.log('Try manually:'); + console.log(colored(` ${updateCommand} ${updateArgs.join(' ')}`, 'yellow')); + console.log(''); + process.exit(1); + }); + }; + + if (cacheCommand && cacheArgs) { + console.log(colored('Clearing package cache...', 'cyan')); + const cacheChild = spawn(cacheCommand, cacheArgs, { + stdio: 'inherit', + }); + + cacheChild.on('exit', (code) => { + if (code !== 0) { + console.log(colored('[!] Cache clearing failed, proceeding anyway...', 'yellow')); + } + performUpdate(); + }); + + cacheChild.on('error', () => { + console.log(colored('[!] Cache clearing failed, proceeding anyway...', 'yellow')); + performUpdate(); + }); + } else { + performUpdate(); + } +} + +/** + * Perform update via direct installer (curl/irm) + */ +async function performDirectUpdate(): Promise { + console.log(colored('Updating via installer...', 'cyan')); + console.log(''); + + const isWindows = process.platform === 'win32'; + let command: string; + let args: string[]; + + if (isWindows) { + command = 'powershell.exe'; + args = [ + '-NoProfile', + '-ExecutionPolicy', + 'Bypass', + '-Command', + 'irm ccs.kaitran.ca/install | iex', + ]; + } else { + command = '/bin/bash'; + args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash']; + } + + const child = spawn(command, args, { + stdio: 'inherit', + }); + + child.on('exit', (code) => { + if (code === 0) { + console.log(''); + console.log(colored('[OK] Update successful!', 'green')); + console.log(''); + console.log(`Run ${colored('ccs --version', 'yellow')} to verify`); + console.log(''); + } else { + console.log(''); + console.log(colored('[X] Update failed', 'red')); + console.log(''); + console.log('Try manually:'); + if (isWindows) { + console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); + } else { + console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); + } + console.log(''); + } + process.exit(code || 0); + }); + + child.on('error', () => { + console.log(''); + console.log(colored('[X] Failed to run installer', 'red')); + console.log(''); + console.log('Try manually:'); + if (isWindows) { + console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow')); + } else { + console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow')); + } + console.log(''); + process.exit(1); + }); +}