diff --git a/install.ps1 b/install.ps1 index e598f23..5327613 100644 --- a/install.ps1 +++ b/install.ps1 @@ -39,11 +39,11 @@ if ($env:CAPA_PRINT_QUIET -eq "1") { } # Colors -$ColorReset = "`e[0m" -$ColorRed = "`e[31m" -$ColorGreen = "`e[32m" -$ColorYellow = "`e[33m" -$ColorBlue = "`e[34m" +$ColorReset = "$([char]27)[0m" +$ColorRed = "$([char]27)[31m" +$ColorGreen = "$([char]27)[32m" +$ColorYellow = "$([char]27)[33m" +$ColorBlue = "$([char]27)[34m" function Show-Usage { Write-Host @" @@ -108,7 +108,7 @@ function Write-Info { function Write-Success { param([string]$Message) if (-not $Quiet) { - Write-Host "${ColorGreen}✓${ColorReset} $Message" + Write-Host "${ColorGreen}OK${ColorReset} $Message" } } @@ -250,9 +250,9 @@ function Install-Capa { $bannerPad = [Math]::Max(1, $bannerInner - $bannerTitle.Length) $bannerSpaces = " " * $bannerPad Write-Host "" - Write-Host "${ColorGreen}╔═══════════════════════════════════════╗${ColorReset}" - Write-Host "${ColorGreen}║${bannerTitle}${bannerSpaces}║${ColorReset}" - Write-Host "${ColorGreen}╚═══════════════════════════════════════╝${ColorReset}" + Write-Host "${ColorGreen}+---------------------------------------+${ColorReset}" + Write-Host "${ColorGreen}|${bannerTitle}${bannerSpaces}|${ColorReset}" + Write-Host "${ColorGreen}+---------------------------------------+${ColorReset}" Write-Host "" # Detect architecture @@ -321,13 +321,13 @@ function Install-Capa { # Replace existing binary (fixes #19). On Windows a running .exe is locked # and cannot be overwritten, but it *can* be renamed within the same # directory while it runs (the OS keeps the file handle regardless of name). - # So move the old binary aside first — that frees $destPath — then drop the + # So move the old binary aside first (that frees $destPath), then drop the # new binary in. This makes the upgrade succeed even if the previous capa # process hasn't fully exited yet, instead of relying on a timing race. $backupPath = "$destPath.old" if (Test-Path $destPath) { # Remove any leftover backup from a prior upgrade (best-effort; harmless - # if it's still locked — it'll be cleaned on the next run). + # if it's still locked; it'll be cleaned on the next run). if (Test-Path $backupPath) { Remove-Item -LiteralPath $backupPath -Force -ErrorAction SilentlyContinue } @@ -375,7 +375,7 @@ function Install-Capa { } # New binary is in place; clean up the old one. This will fail silently if - # the previous capa process is still running (the file stays locked) — the + # the previous capa process is still running (the file stays locked); the # leftover .old is then removed by the next upgrade. if (Test-Path $backupPath) { Remove-Item -LiteralPath $backupPath -Force -ErrorAction SilentlyContinue @@ -388,9 +388,9 @@ function Install-Capa { # Print success message Write-Host "" - Write-Host "${ColorGreen}╔═══════════════════════════════════════╗${ColorReset}" - Write-Host "${ColorGreen}║ CAPA installed successfully! 🎉 ║${ColorReset}" - Write-Host "${ColorGreen}╚═══════════════════════════════════════╝${ColorReset}" + Write-Host "${ColorGreen}+---------------------------------------+${ColorReset}" + Write-Host "${ColorGreen}| CAPA installed successfully! |${ColorReset}" + Write-Host "${ColorGreen}+---------------------------------------+${ColorReset}" Write-Host "" Write-Host "To get started, run:" Write-Host " ${ColorBlue}capa init${ColorReset} # Initialize a new project" diff --git a/src/cli/commands/__tests__/upgrade-plan.test.ts b/src/cli/commands/__tests__/upgrade-plan.test.ts index ea81333..c2b69c4 100644 --- a/src/cli/commands/__tests__/upgrade-plan.test.ts +++ b/src/cli/commands/__tests__/upgrade-plan.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect } from 'bun:test'; import { createHash } from 'crypto'; +import { readFileSync } from 'fs'; +import { join } from 'path'; import { assertSha256Match, formatUpgradePreview, @@ -131,4 +133,11 @@ describe('capa upgrade planning', () => { const map = parseSha256Sums(`${digest} install.sh\n`); expect(map.get('install.sh')).toBe(digest); }); + + // `capa upgrade` runs install.ps1 via Windows PowerShell 5.1, which reads a + // BOM-less file as Windows-1252; any non-ASCII byte can break parsing. + it('keeps install.ps1 ASCII-only so Windows PowerShell 5.1 can parse it', () => { + const bytes = readFileSync(join(import.meta.dir, '../../../../install.ps1')); + expect(bytes.findIndex((b) => b > 0x7f)).toBe(-1); + }); }); diff --git a/src/cli/commands/upgrade.ts b/src/cli/commands/upgrade.ts index 23a719a..280056d 100644 --- a/src/cli/commands/upgrade.ts +++ b/src/cli/commands/upgrade.ts @@ -5,7 +5,6 @@ import { getServerStatus, stopServer } from '../utils/server-manager'; import { header, footer, - success, info, error, isYes, @@ -89,35 +88,14 @@ export async function upgradeCommand(): Promise { CAPA_VERSION: plan.version, }; - if (process.platform === 'win32') { - const proc = Bun.spawn( - [ - 'powershell.exe', - '-NoProfile', - '-ExecutionPolicy', - 'Bypass', - '-File', - installerPath, - ], - { - stdout: 'ignore', - stderr: 'ignore', - stdin: 'ignore', - detached: true, - env, - }, - ); - proc.unref(); - success( - 'Verified installer started. This window will close; capa will update in the background.', - ); - info( - 'Restart your terminal when the installer finishes, then run `capa --version` to confirm.', - ); - process.exit(0); - } - - const proc = Bun.spawn(['bash', installerPath], { + // Run the installer in the foreground on every platform so failures are + // visible. On Windows the installer renames this running capa.exe aside + // before dropping the new one in, so it doesn't need us to exit first. + const cmd = + process.platform === 'win32' + ? ['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', installerPath] + : ['bash', installerPath]; + const proc = Bun.spawn(cmd, { stdout: 'inherit', stderr: 'inherit', stdin: 'inherit',