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