source dump of claude code
at main 422 lines 14 kB view raw
1import chalk from 'chalk' 2import { logEvent } from 'src/services/analytics/index.js' 3import { 4 getLatestVersion, 5 type InstallStatus, 6 installGlobalPackage, 7} from 'src/utils/autoUpdater.js' 8import { regenerateCompletionCache } from 'src/utils/completionCache.js' 9import { 10 getGlobalConfig, 11 type InstallMethod, 12 saveGlobalConfig, 13} from 'src/utils/config.js' 14import { logForDebugging } from 'src/utils/debug.js' 15import { getDoctorDiagnostic } from 'src/utils/doctorDiagnostic.js' 16import { gracefulShutdown } from 'src/utils/gracefulShutdown.js' 17import { 18 installOrUpdateClaudePackage, 19 localInstallationExists, 20} from 'src/utils/localInstaller.js' 21import { 22 installLatest as installLatestNative, 23 removeInstalledSymlink, 24} from 'src/utils/nativeInstaller/index.js' 25import { getPackageManager } from 'src/utils/nativeInstaller/packageManagers.js' 26import { writeToStdout } from 'src/utils/process.js' 27import { gte } from 'src/utils/semver.js' 28import { getInitialSettings } from 'src/utils/settings/settings.js' 29 30export async function update() { 31 logEvent('tengu_update_check', {}) 32 writeToStdout(`Current version: ${MACRO.VERSION}\n`) 33 34 const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest' 35 writeToStdout(`Checking for updates to ${channel} version...\n`) 36 37 logForDebugging('update: Starting update check') 38 39 // Run diagnostic to detect potential issues 40 logForDebugging('update: Running diagnostic') 41 const diagnostic = await getDoctorDiagnostic() 42 logForDebugging(`update: Installation type: ${diagnostic.installationType}`) 43 logForDebugging( 44 `update: Config install method: ${diagnostic.configInstallMethod}`, 45 ) 46 47 // Check for multiple installations 48 if (diagnostic.multipleInstallations.length > 1) { 49 writeToStdout('\n') 50 writeToStdout(chalk.yellow('Warning: Multiple installations found') + '\n') 51 for (const install of diagnostic.multipleInstallations) { 52 const current = 53 diagnostic.installationType === install.type 54 ? ' (currently running)' 55 : '' 56 writeToStdout(`- ${install.type} at ${install.path}${current}\n`) 57 } 58 } 59 60 // Display warnings if any exist 61 if (diagnostic.warnings.length > 0) { 62 writeToStdout('\n') 63 for (const warning of diagnostic.warnings) { 64 logForDebugging(`update: Warning detected: ${warning.issue}`) 65 66 // Don't skip PATH warnings - they're always relevant 67 // The user needs to know that 'which claude' points elsewhere 68 logForDebugging(`update: Showing warning: ${warning.issue}`) 69 70 writeToStdout(chalk.yellow(`Warning: ${warning.issue}\n`)) 71 72 writeToStdout(chalk.bold(`Fix: ${warning.fix}\n`)) 73 } 74 } 75 76 // Update config if installMethod is not set (but skip for package managers) 77 const config = getGlobalConfig() 78 if ( 79 !config.installMethod && 80 diagnostic.installationType !== 'package-manager' 81 ) { 82 writeToStdout('\n') 83 writeToStdout('Updating configuration to track installation method...\n') 84 let detectedMethod: 'local' | 'native' | 'global' | 'unknown' = 'unknown' 85 86 // Map diagnostic installation type to config install method 87 switch (diagnostic.installationType) { 88 case 'npm-local': 89 detectedMethod = 'local' 90 break 91 case 'native': 92 detectedMethod = 'native' 93 break 94 case 'npm-global': 95 detectedMethod = 'global' 96 break 97 default: 98 detectedMethod = 'unknown' 99 } 100 101 saveGlobalConfig(current => ({ 102 ...current, 103 installMethod: detectedMethod, 104 })) 105 writeToStdout(`Installation method set to: ${detectedMethod}\n`) 106 } 107 108 // Check if running from development build 109 if (diagnostic.installationType === 'development') { 110 writeToStdout('\n') 111 writeToStdout( 112 chalk.yellow('Warning: Cannot update development build') + '\n', 113 ) 114 await gracefulShutdown(1) 115 } 116 117 // Check if running from a package manager 118 if (diagnostic.installationType === 'package-manager') { 119 const packageManager = await getPackageManager() 120 writeToStdout('\n') 121 122 if (packageManager === 'homebrew') { 123 writeToStdout('Claude is managed by Homebrew.\n') 124 const latest = await getLatestVersion(channel) 125 if (latest && !gte(MACRO.VERSION, latest)) { 126 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 127 writeToStdout('\n') 128 writeToStdout('To update, run:\n') 129 writeToStdout(chalk.bold(' brew upgrade claude-code') + '\n') 130 } else { 131 writeToStdout('Claude is up to date!\n') 132 } 133 } else if (packageManager === 'winget') { 134 writeToStdout('Claude is managed by winget.\n') 135 const latest = await getLatestVersion(channel) 136 if (latest && !gte(MACRO.VERSION, latest)) { 137 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 138 writeToStdout('\n') 139 writeToStdout('To update, run:\n') 140 writeToStdout( 141 chalk.bold(' winget upgrade Anthropic.ClaudeCode') + '\n', 142 ) 143 } else { 144 writeToStdout('Claude is up to date!\n') 145 } 146 } else if (packageManager === 'apk') { 147 writeToStdout('Claude is managed by apk.\n') 148 const latest = await getLatestVersion(channel) 149 if (latest && !gte(MACRO.VERSION, latest)) { 150 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 151 writeToStdout('\n') 152 writeToStdout('To update, run:\n') 153 writeToStdout(chalk.bold(' apk upgrade claude-code') + '\n') 154 } else { 155 writeToStdout('Claude is up to date!\n') 156 } 157 } else { 158 // pacman, deb, and rpm don't get specific commands because they each have 159 // multiple frontends (pacman: yay/paru/makepkg, deb: apt/apt-get/aptitude/nala, 160 // rpm: dnf/yum/zypper) 161 writeToStdout('Claude is managed by a package manager.\n') 162 writeToStdout('Please use your package manager to update.\n') 163 } 164 165 await gracefulShutdown(0) 166 } 167 168 // Check for config/reality mismatch (skip for package-manager installs) 169 if ( 170 config.installMethod && 171 diagnostic.configInstallMethod !== 'not set' && 172 diagnostic.installationType !== 'package-manager' 173 ) { 174 const runningType = diagnostic.installationType 175 const configExpects = diagnostic.configInstallMethod 176 177 // Map installation types for comparison 178 const typeMapping: Record<string, string> = { 179 'npm-local': 'local', 180 'npm-global': 'global', 181 native: 'native', 182 development: 'development', 183 unknown: 'unknown', 184 } 185 186 const normalizedRunningType = typeMapping[runningType] || runningType 187 188 if ( 189 normalizedRunningType !== configExpects && 190 configExpects !== 'unknown' 191 ) { 192 writeToStdout('\n') 193 writeToStdout(chalk.yellow('Warning: Configuration mismatch') + '\n') 194 writeToStdout(`Config expects: ${configExpects} installation\n`) 195 writeToStdout(`Currently running: ${runningType}\n`) 196 writeToStdout( 197 chalk.yellow( 198 `Updating the ${runningType} installation you are currently using`, 199 ) + '\n', 200 ) 201 202 // Update config to match reality 203 saveGlobalConfig(current => ({ 204 ...current, 205 installMethod: normalizedRunningType as InstallMethod, 206 })) 207 writeToStdout( 208 `Config updated to reflect current installation method: ${normalizedRunningType}\n`, 209 ) 210 } 211 } 212 213 // Handle native installation updates first 214 if (diagnostic.installationType === 'native') { 215 logForDebugging( 216 'update: Detected native installation, using native updater', 217 ) 218 try { 219 const result = await installLatestNative(channel, true) 220 221 // Handle lock contention gracefully 222 if (result.lockFailed) { 223 const pidInfo = result.lockHolderPid 224 ? ` (PID ${result.lockHolderPid})` 225 : '' 226 writeToStdout( 227 chalk.yellow( 228 `Another Claude process${pidInfo} is currently running. Please try again in a moment.`, 229 ) + '\n', 230 ) 231 await gracefulShutdown(0) 232 } 233 234 if (!result.latestVersion) { 235 process.stderr.write('Failed to check for updates\n') 236 await gracefulShutdown(1) 237 } 238 239 if (result.latestVersion === MACRO.VERSION) { 240 writeToStdout( 241 chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', 242 ) 243 } else { 244 writeToStdout( 245 chalk.green( 246 `Successfully updated from ${MACRO.VERSION} to version ${result.latestVersion}`, 247 ) + '\n', 248 ) 249 await regenerateCompletionCache() 250 } 251 await gracefulShutdown(0) 252 } catch (error) { 253 process.stderr.write('Error: Failed to install native update\n') 254 process.stderr.write(String(error) + '\n') 255 process.stderr.write('Try running "claude doctor" for diagnostics\n') 256 await gracefulShutdown(1) 257 } 258 } 259 260 // Fallback to existing JS/npm-based update logic 261 // Remove native installer symlink since we're not using native installation 262 // But only if user hasn't migrated to native installation 263 if (config.installMethod !== 'native') { 264 await removeInstalledSymlink() 265 } 266 267 logForDebugging('update: Checking npm registry for latest version') 268 logForDebugging(`update: Package URL: ${MACRO.PACKAGE_URL}`) 269 const npmTag = channel === 'stable' ? 'stable' : 'latest' 270 const npmCommand = `npm view ${MACRO.PACKAGE_URL}@${npmTag} version` 271 logForDebugging(`update: Running: ${npmCommand}`) 272 const latestVersion = await getLatestVersion(channel) 273 logForDebugging( 274 `update: Latest version from npm: ${latestVersion || 'FAILED'}`, 275 ) 276 277 if (!latestVersion) { 278 logForDebugging('update: Failed to get latest version from npm registry') 279 process.stderr.write(chalk.red('Failed to check for updates') + '\n') 280 process.stderr.write('Unable to fetch latest version from npm registry\n') 281 process.stderr.write('\n') 282 process.stderr.write('Possible causes:\n') 283 process.stderr.write(' • Network connectivity issues\n') 284 process.stderr.write(' • npm registry is unreachable\n') 285 process.stderr.write(' • Corporate proxy/firewall blocking npm\n') 286 if (MACRO.PACKAGE_URL && !MACRO.PACKAGE_URL.startsWith('@anthropic')) { 287 process.stderr.write( 288 ' • Internal/development build not published to npm\n', 289 ) 290 } 291 process.stderr.write('\n') 292 process.stderr.write('Try:\n') 293 process.stderr.write(' • Check your internet connection\n') 294 process.stderr.write(' • Run with --debug flag for more details\n') 295 const packageName = 296 MACRO.PACKAGE_URL || 297 (process.env.USER_TYPE === 'ant' 298 ? '@anthropic-ai/claude-cli' 299 : '@anthropic-ai/claude-code') 300 process.stderr.write( 301 ` • Manually check: npm view ${packageName} version\n`, 302 ) 303 304 process.stderr.write(' • Check if you need to login: npm whoami\n') 305 await gracefulShutdown(1) 306 } 307 308 // Check if versions match exactly, including any build metadata (like SHA) 309 if (latestVersion === MACRO.VERSION) { 310 writeToStdout( 311 chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', 312 ) 313 await gracefulShutdown(0) 314 } 315 316 writeToStdout( 317 `New version available: ${latestVersion} (current: ${MACRO.VERSION})\n`, 318 ) 319 writeToStdout('Installing update...\n') 320 321 // Determine update method based on what's actually running 322 let useLocalUpdate = false 323 let updateMethodName = '' 324 325 switch (diagnostic.installationType) { 326 case 'npm-local': 327 useLocalUpdate = true 328 updateMethodName = 'local' 329 break 330 case 'npm-global': 331 useLocalUpdate = false 332 updateMethodName = 'global' 333 break 334 case 'unknown': { 335 // Fallback to detection if we can't determine installation type 336 const isLocal = await localInstallationExists() 337 useLocalUpdate = isLocal 338 updateMethodName = isLocal ? 'local' : 'global' 339 writeToStdout( 340 chalk.yellow('Warning: Could not determine installation type') + '\n', 341 ) 342 writeToStdout( 343 `Attempting ${updateMethodName} update based on file detection...\n`, 344 ) 345 break 346 } 347 default: 348 process.stderr.write( 349 `Error: Cannot update ${diagnostic.installationType} installation\n`, 350 ) 351 await gracefulShutdown(1) 352 } 353 354 writeToStdout(`Using ${updateMethodName} installation update method...\n`) 355 356 logForDebugging(`update: Update method determined: ${updateMethodName}`) 357 logForDebugging(`update: useLocalUpdate: ${useLocalUpdate}`) 358 359 let status: InstallStatus 360 361 if (useLocalUpdate) { 362 logForDebugging( 363 'update: Calling installOrUpdateClaudePackage() for local update', 364 ) 365 status = await installOrUpdateClaudePackage(channel) 366 } else { 367 logForDebugging('update: Calling installGlobalPackage() for global update') 368 status = await installGlobalPackage() 369 } 370 371 logForDebugging(`update: Installation status: ${status}`) 372 373 switch (status) { 374 case 'success': 375 writeToStdout( 376 chalk.green( 377 `Successfully updated from ${MACRO.VERSION} to version ${latestVersion}`, 378 ) + '\n', 379 ) 380 await regenerateCompletionCache() 381 break 382 case 'no_permissions': 383 process.stderr.write( 384 'Error: Insufficient permissions to install update\n', 385 ) 386 if (useLocalUpdate) { 387 process.stderr.write('Try manually updating with:\n') 388 process.stderr.write( 389 ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, 390 ) 391 } else { 392 process.stderr.write('Try running with sudo or fix npm permissions\n') 393 process.stderr.write( 394 'Or consider using native installation with: claude install\n', 395 ) 396 } 397 await gracefulShutdown(1) 398 break 399 case 'install_failed': 400 process.stderr.write('Error: Failed to install update\n') 401 if (useLocalUpdate) { 402 process.stderr.write('Try manually updating with:\n') 403 process.stderr.write( 404 ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, 405 ) 406 } else { 407 process.stderr.write( 408 'Or consider using native installation with: claude install\n', 409 ) 410 } 411 await gracefulShutdown(1) 412 break 413 case 'in_progress': 414 process.stderr.write( 415 'Error: Another instance is currently performing an update\n', 416 ) 417 process.stderr.write('Please wait and try again later\n') 418 await gracefulShutdown(1) 419 break 420 } 421 await gracefulShutdown(0) 422}