Files
MercuryToolbox/scripts/uninstall-package-toolbox.ps1
T

222 lines
6.4 KiB
PowerShell

[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$InstallRoot = (Join-Path $env:LOCALAPPDATA 'MercuryToolbox'),
[string]$CodexHome
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
. (Join-Path $PSScriptRoot 'toolbox-commands.ps1')
function Normalize-PathValue {
param(
[AllowNull()]
[string]$PathValue
)
if ([string]::IsNullOrWhiteSpace($PathValue)) {
return ''
}
return $PathValue.Replace('/', '\').Trim().TrimEnd('\').ToLowerInvariant()
}
function Split-PathEntries {
param(
[AllowNull()]
[string]$PathValue
)
if ([string]::IsNullOrWhiteSpace($PathValue)) {
return @()
}
return $PathValue.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
}
function Remove-PathEntry {
param(
[AllowNull()]
[string]$ExistingPath,
[Parameter(Mandatory = $true)]
[string]$Entry
)
$normalizedEntry = Normalize-PathValue -PathValue $Entry
$remaining = [System.Collections.Generic.List[string]]::new()
$changed = $false
foreach ($existing in (Split-PathEntries -PathValue $ExistingPath)) {
if ((Normalize-PathValue -PathValue $existing) -eq $normalizedEntry) {
$changed = $true
continue
}
$remaining.Add($existing)
}
return @{
Changed = $changed
Value = ($remaining -join ';')
}
}
function Resolve-CodexHomePath {
param(
[AllowNull()]
[string]$ExplicitCodexHome
)
if (-not [string]::IsNullOrWhiteSpace($ExplicitCodexHome)) {
return $ExplicitCodexHome
}
if (-not [string]::IsNullOrWhiteSpace($env:CODEX_HOME)) {
return $env:CODEX_HOME
}
return (Join-Path $env:USERPROFILE '.codex')
}
function Test-CodexSkillOwnedByMercury {
param(
[Parameter(Mandatory = $true)]
[string]$SkillRoot
)
$markerPath = Join-Path $SkillRoot '.mercury-toolbox-owner.json'
if (-not (Test-Path -LiteralPath $markerPath)) {
return $false
}
try {
$marker = Get-Content -LiteralPath $markerPath -Raw | ConvertFrom-Json
return [string]$marker.owner -eq 'mercury-toolbox'
}
catch {
return $false
}
}
function Assert-ExistingPathNotReparsePoint {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Description
)
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
if (-not (Test-Path -LiteralPath $resolvedPath)) {
return
}
$item = Get-Item -LiteralPath $resolvedPath -Force
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "$Description must not be a reparse point: $resolvedPath"
}
}
function Assert-ExistingTreeHasNoReparsePoints {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Description
)
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
if (-not (Test-Path -LiteralPath $resolvedPath)) {
return
}
$reparsePoints = @(Get-ChildItem -LiteralPath $resolvedPath -Force -Recurse -Attributes ReparsePoint)
if ($reparsePoints.Count -gt 0) {
throw "$Description must not contain reparse points: $($reparsePoints[0].FullName)"
}
}
$legacyBinDir = Join-Path $InstallRoot 'bin'
$currentRoot = Join-Path $InstallRoot 'current'
$activeBinDir = Join-Path $currentRoot 'bin'
$versionsRoot = Join-Path $InstallRoot 'versions'
$shareRoot = Join-Path $InstallRoot 'share\mercury-toolbox'
$codexSkillRoot = Join-Path (Resolve-CodexHomePath -ExplicitCodexHome $CodexHome) 'skills\mercury-toolbox'
$legacyCommandNames = @('context', 'waitfor')
Assert-ExistingPathNotReparsePoint -Path $InstallRoot -Description 'InstallRoot'
Assert-ExistingPathNotReparsePoint -Path $versionsRoot -Description 'versions root'
Assert-ExistingPathNotReparsePoint -Path $shareRoot -Description 'share root'
Assert-ExistingPathNotReparsePoint -Path $codexSkillRoot -Description 'Codex skill root'
Assert-ExistingTreeHasNoReparsePoints -Path $shareRoot -Description 'share root'
Assert-ExistingTreeHasNoReparsePoints -Path $codexSkillRoot -Description 'Codex skill root'
if (Test-Path -LiteralPath $legacyBinDir) {
foreach ($commandName in Get-ToolboxCommandNames) {
$commandPath = Join-Path $legacyBinDir "$commandName.exe"
if (Test-Path -LiteralPath $commandPath) {
Remove-Item -LiteralPath $commandPath -Force
}
}
foreach ($legacyName in $legacyCommandNames) {
$legacyPath = Join-Path $legacyBinDir "$legacyName.exe"
if (Test-Path -LiteralPath $legacyPath) {
Remove-Item -LiteralPath $legacyPath -Force
}
}
$remainingBinItems = @(Get-ChildItem -LiteralPath $legacyBinDir -Force)
if ($remainingBinItems.Count -eq 0) {
Remove-Item -LiteralPath $legacyBinDir -Force
}
}
if (Test-Path -LiteralPath $currentRoot) {
Remove-Item -LiteralPath $currentRoot -Force
}
if (Test-Path -LiteralPath $versionsRoot) {
Remove-Item -LiteralPath $versionsRoot -Recurse -Force
}
if (Test-Path -LiteralPath $shareRoot) {
Remove-Item -LiteralPath $shareRoot -Recurse -Force
}
if (Test-Path -LiteralPath $codexSkillRoot) {
if (Test-CodexSkillOwnedByMercury -SkillRoot $codexSkillRoot) {
Remove-Item -LiteralPath $codexSkillRoot -Recurse -Force
}
else {
Write-Warning "Skipping Codex skill removal because Mercury Toolbox does not own this skill copy: $codexSkillRoot"
}
}
if (Test-Path -LiteralPath $InstallRoot) {
$remainingItems = @(Get-ChildItem -LiteralPath $InstallRoot -Force)
if ($remainingItems.Count -eq 0) {
Remove-Item -LiteralPath $InstallRoot -Force
}
}
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
foreach ($entry in @($legacyBinDir, $activeBinDir)) {
$pathUpdate = Remove-PathEntry -ExistingPath $userPath -Entry $entry
if ($pathUpdate.Changed) {
$userPath = $pathUpdate.Value
}
}
[Environment]::SetEnvironmentVariable('Path', $userPath, 'User')
foreach ($entry in @($legacyBinDir, $activeBinDir)) {
$sessionUpdate = Remove-PathEntry -ExistingPath $env:Path -Entry $entry
if ($sessionUpdate.Changed) {
$env:Path = $sessionUpdate.Value
}
}
Write-Host "Mercury Toolbox package removed from $InstallRoot"
Write-Host "Removed Codex skill root: $codexSkillRoot"