diff --git a/Install-Claude.ps1 b/Install-Claude.ps1 index 8c332257..67444271 100644 --- a/Install-Claude.ps1 +++ b/Install-Claude.ps1 @@ -818,6 +818,56 @@ function Add-ManifestEntry { } } +function Remove-OldManifestsForPath { + <# + .SYNOPSIS + Remove old manifest files for the same installation path + #> + param( + [Parameter(Mandatory=$true)] + [string]$InstallationPath + ) + + if (-not (Test-Path $script:ManifestDir)) { + return + } + + try { + $manifestFiles = Get-ChildItem -Path $script:ManifestDir -Filter "install-*.json" -File + $removedCount = 0 + + foreach ($file in $manifestFiles) { + try { + $manifestJson = Get-Content -Path $file.FullName -Raw -Encoding utf8 + $manifest = $manifestJson | ConvertFrom-Json + + # Normalize paths for comparison (case-insensitive, handle trailing slashes) + $manifestPath = if ($manifest.installation_path) { + $manifest.installation_path.TrimEnd('\', '/').ToLower() + } else { + "" + } + $targetPath = $InstallationPath.TrimEnd('\', '/').ToLower() + + # If paths match, remove this old manifest + if ($manifestPath -eq $targetPath) { + Remove-Item -Path $file.FullName -Force + Write-ColorOutput "Removed old manifest: $($file.Name)" $ColorInfo + $removedCount++ + } + } catch { + Write-ColorOutput "WARNING: Failed to process manifest $($file.Name): $($_.Exception.Message)" $ColorWarning + } + } + + if ($removedCount -gt 0) { + Write-ColorOutput "Removed $removedCount old manifest(s) for installation path: $InstallationPath" $ColorSuccess + } + } catch { + Write-ColorOutput "WARNING: Failed to clean old manifests: $($_.Exception.Message)" $ColorWarning + } +} + function Save-InstallManifest { <# .SYNOPSIS @@ -829,6 +879,11 @@ function Save-InstallManifest { ) try { + # Remove old manifests for the same installation path + if ($Manifest.installation_path) { + Remove-OldManifestsForPath -InstallationPath $Manifest.installation_path + } + # Use manifest ID to create unique file name $manifestFileName = "$($Manifest.manifest_id).json" $manifestPath = Join-Path $script:ManifestDir $manifestFileName @@ -901,7 +956,7 @@ function Migrate-LegacyManifest { function Get-AllInstallManifests { <# .SYNOPSIS - Get all installation manifests + Get all installation manifests (only latest per installation path) #> # Migrate legacy manifest if exists @@ -913,7 +968,7 @@ function Get-AllInstallManifests { try { $manifestFiles = Get-ChildItem -Path $script:ManifestDir -Filter "install-*.json" -File | Sort-Object LastWriteTime -Descending - $manifests = [System.Collections.ArrayList]::new() + $allManifests = [System.Collections.ArrayList]::new() foreach ($file in $manifestFiles) { try { @@ -957,13 +1012,52 @@ function Get-AllInstallManifests { directories_count = $dirsCount } - $null = $manifests.Add($manifestHash) + $null = $allManifests.Add($manifestHash) } catch { Write-ColorOutput "WARNING: Failed to load manifest $($file.Name): $($_.Exception.Message)" $ColorWarning } } - return ,$manifests.ToArray() + # Group by installation_path (normalized) and keep only the latest per path + $pathGroups = @{} + foreach ($manifest in $allManifests) { + $normalizedPath = $manifest.installation_path.TrimEnd('\', '/').ToLower() + + if (-not $pathGroups.ContainsKey($normalizedPath)) { + $pathGroups[$normalizedPath] = @() + } + $pathGroups[$normalizedPath] += $manifest + } + + # Select the latest manifest for each path (based on installation_date) + $latestManifests = [System.Collections.ArrayList]::new() + foreach ($pathKey in $pathGroups.Keys) { + $groupManifests = $pathGroups[$pathKey] + + # Sort by installation_date descending and take the first (latest) + $latest = $groupManifests | Sort-Object { + try { + [DateTime]::Parse($_.installation_date) + } catch { + [DateTime]::MinValue + } + } -Descending | Select-Object -First 1 + + if ($latest) { + $null = $latestManifests.Add($latest) + } + } + + # Sort final results by installation_date descending + $sortedManifests = $latestManifests | Sort-Object { + try { + [DateTime]::Parse($_.installation_date) + } catch { + [DateTime]::MinValue + } + } -Descending + + return ,$sortedManifests } catch { Write-ColorOutput "ERROR: Failed to list installation manifests: $($_.Exception.Message)" $ColorError return @() diff --git a/Install-Claude.sh b/Install-Claude.sh index 5201cdc0..c069b65d 100644 --- a/Install-Claude.sh +++ b/Install-Claude.sh @@ -627,7 +627,7 @@ function install_global() { create_version_json "$global_claude_dir" "Global" # Save installation manifest - save_install_manifest "$manifest_file" + save_install_manifest "$manifest_file" "$user_home" return 0 } @@ -822,7 +822,7 @@ function install_path() { create_version_json "$global_claude_dir" "Global" # Save installation manifest - save_install_manifest "$manifest_file" + save_install_manifest "$manifest_file" "$target_dir" return 0 } @@ -974,8 +974,49 @@ EOF mv "$temp_file" "$manifest_file" } +function remove_old_manifests_for_path() { + local installation_path="$1" + + if [ ! -d "$MANIFEST_DIR" ]; then + return 0 + fi + + # Normalize paths for comparison (lowercase, remove trailing slashes) + local target_path=$(echo "$installation_path" | sed 's:/*$::' | tr '[:upper:]' '[:lower:]') + local removed_count=0 + + # Find and remove old manifests for the same installation path + while IFS= read -r -d '' file; do + local manifest_path=$(jq -r '.installation_path // ""' "$file" 2>/dev/null) + + if [ -n "$manifest_path" ]; then + # Normalize manifest path + local normalized_manifest_path=$(echo "$manifest_path" | sed 's:/*$::' | tr '[:upper:]' '[:lower:]') + + # If paths match, remove this old manifest + if [ "$normalized_manifest_path" = "$target_path" ]; then + rm -f "$file" + write_color "Removed old manifest: $(basename "$file")" "$COLOR_INFO" + ((removed_count++)) + fi + fi + done < <(find "$MANIFEST_DIR" -name "install-*.json" -type f -print0 2>/dev/null) + + if [ "$removed_count" -gt 0 ]; then + write_color "Removed $removed_count old manifest(s) for installation path: $installation_path" "$COLOR_SUCCESS" + fi + + return 0 +} + function save_install_manifest() { local manifest_file="$1" + local installation_path="$2" + + # Remove old manifests for the same installation path + if [ -n "$installation_path" ]; then + remove_old_manifests_for_path "$installation_path" + fi if [ -f "$manifest_file" ]; then write_color "Installation manifest saved: $manifest_file" "$COLOR_SUCCESS" @@ -1034,14 +1075,14 @@ function get_all_install_manifests() { fi # Collect all manifests into JSON array - local manifests="[" + local all_manifests="[" local first=true while IFS= read -r -d '' file; do if [ "$first" = true ]; then first=false else - manifests+="," + all_manifests+="," fi # Add manifest_file field @@ -1054,12 +1095,31 @@ function get_all_install_manifests() { # Add counts to manifest manifest_content=$(echo "$manifest_content" | jq --argjson fc "$files_count" --argjson dc "$dirs_count" '. + {files_count: $fc, directories_count: $dc}') - manifests+="$manifest_content" + all_manifests+="$manifest_content" done < <(find "$MANIFEST_DIR" -name "install-*.json" -type f -print0 | sort -z) - manifests+="]" + all_manifests+="]" - echo "$manifests" + # Group by installation_path (normalized) and keep only the latest per path + # Use jq to process the array and keep only the latest manifest for each unique path + local latest_manifests=$(echo "$all_manifests" | jq ' + # Normalize paths and add normalized_path field + map(. + { + normalized_path: (.installation_path // "" | ascii_downcase | sub("/+$"; "")) + }) + # Group by normalized_path + | group_by(.normalized_path) + # For each group, sort by installation_date descending and take the first (latest) + | map( + sort_by(.installation_date) | reverse | .[0] + ) + # Remove the temporary normalized_path field + | map(del(.normalized_path)) + # Sort final results by installation_date descending + | sort_by(.installation_date) | reverse + ') + + echo "$latest_manifests" } # ============================================================================