* Performs the core update operation: download (if needed), install, and update symlink. * Returns whether a new install was performed (vs just updating symlink).
( version: string, forceReinstall: boolean, )
| 439 | * Returns whether a new install was performed (vs just updating symlink). |
| 440 | */ |
| 441 | async function performVersionUpdate( |
| 442 | version: string, |
| 443 | forceReinstall: boolean, |
| 444 | ): Promise<boolean> { |
| 445 | const { stagingPath: baseStagingPath, installPath } = |
| 446 | await getVersionPaths(version) |
| 447 | const { executable: executablePath } = getBaseDirectories() |
| 448 | |
| 449 | // For lockless updates, use a unique staging path to avoid conflicts between concurrent downloads |
| 450 | const stagingPath = isEnvTruthy(process.env.ENABLE_LOCKLESS_UPDATES) |
| 451 | ? `${baseStagingPath}.${process.pid}.${Date.now()}` |
| 452 | : baseStagingPath |
| 453 | |
| 454 | // Only download if not already installed (or if force reinstall) |
| 455 | const needsInstall = !(await versionIsAvailable(version)) || forceReinstall |
| 456 | if (needsInstall) { |
| 457 | logForDebugging( |
| 458 | forceReinstall |
| 459 | ? `Force reinstalling native installer version ${version}` |
| 460 | : `Downloading native installer version ${version}`, |
| 461 | ) |
| 462 | const downloadType = await downloadVersion(version, stagingPath) |
| 463 | await installVersion(stagingPath, installPath, downloadType) |
| 464 | } else { |
| 465 | logForDebugging(`Version ${version} already installed, updating symlink`) |
| 466 | } |
| 467 | |
| 468 | // Create direct symlink from ~/.local/bin/claude to the version binary |
| 469 | await removeDirectoryIfEmpty(executablePath) |
| 470 | await updateSymlink(executablePath, installPath) |
| 471 | |
| 472 | // Verify the executable was actually created/updated |
| 473 | if (!(await isPossibleClaudeBinary(executablePath))) { |
| 474 | let installPathExists = false |
| 475 | try { |
| 476 | await stat(installPath) |
| 477 | installPathExists = true |
| 478 | } catch { |
| 479 | // installPath doesn't exist |
| 480 | } |
| 481 | throw new Error( |
| 482 | `Failed to create executable at ${executablePath}. ` + |
| 483 | `Source file exists: ${installPathExists}. ` + |
| 484 | `Check write permissions to ${executablePath}.`, |
| 485 | ) |
| 486 | } |
| 487 | return needsInstall |
| 488 | } |
| 489 | |
| 490 | async function versionIsAvailable(version: string): Promise<boolean> { |
| 491 | const { installPath } = await getVersionPaths(version) |
no test coverage detected