Public/Compare.ps1
|
# PSSnips — Snippet diff / preview before import. function Compare-SnipCollection { <# .SYNOPSIS Previews what would change if you ran Import-SnipCollection against a ZIP archive. .DESCRIPTION Extracts the specified PSSnips ZIP archive to a temporary directory and compares its index.json against the current local index.json. Each snippet is classified as one of: Added — present in the archive but not in the local collection. Modified — present in both but with a different contentHash or modified timestamp. Unchanged — present in both with an identical contentHash. LocalOnly — present locally but not in the archive (import would not affect these). Without -PassThru the function prints a formatted summary table and per-category counts to the host. With -PassThru it returns an array of PSCustomObject instead, which is useful for scripting or piping to further filtering. The temporary extraction directory is always removed on exit, even if an error occurs. .PARAMETER Path Full or relative path to the PSSnips ZIP archive produced by Export-SnipCollection. .PARAMETER PassThru When specified, returns an array of PSCustomObject instead of printing to host. Each object has the properties: Name, Status, LocalModified, ArchiveModified, Language, Tags. .EXAMPLE Compare-SnipCollection -Path .\backup.zip Prints a formatted preview table and summary counts to the host. .EXAMPLE $diff = Compare-SnipCollection -Path .\backup.zip -PassThru $diff | Where-Object Status -eq 'Modified' Returns result objects and filters to only the modified snippets. .INPUTS None. This function does not accept pipeline input. .OUTPUTS PSCustomObject[] Only emitted when -PassThru is specified. Each object contains: Name, Status, LocalModified, ArchiveModified, Language, Tags. .NOTES Status values: Added, Modified, Unchanged, LocalOnly. LocalOnly snippets are informational — Import-SnipCollection does not remove them. Requires PowerShell 7.0 or later (module-wide requirement). #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, Position = 0, HelpMessage = 'Path to the PSSnips ZIP archive to compare')] [ValidateNotNullOrEmpty()] [string]$Path, [switch]$PassThru ) script:InitEnv if (-not (Test-Path $Path)) { Write-Error "Archive not found: $Path" return } $tempDir = Join-Path $env:TEMP "pssnips_compare_$(New-Guid)" try { Expand-Archive -Path $Path -DestinationPath $tempDir -Force $archiveIdxPath = Join-Path $tempDir 'index.json' if (-not (Test-Path $archiveIdxPath)) { Write-Error 'Archive does not contain index.json' return } $archiveRaw = Get-Content $archiveIdxPath -Raw | ConvertFrom-Json -AsHashtable $archiveSnips = if ($archiveRaw.ContainsKey('snippets')) { $archiveRaw['snippets'] } else { @{} } $localIdx = script:LoadIdx $localSnips = $localIdx.snippets $results = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($name in $archiveSnips.Keys) { $arch = $archiveSnips[$name] $archHash = if ($arch -is [hashtable] -and $arch.ContainsKey('contentHash')) { $arch['contentHash'] } else { '' } $archMod = if ($arch -is [hashtable] -and $arch.ContainsKey('modified')) { try { [datetime]$arch['modified'] } catch { $null } } else { $null } $archLang = if ($arch -is [hashtable] -and $arch.ContainsKey('language')) { $arch['language'] } else { '' } $archTags = if ($arch -is [hashtable] -and $arch.ContainsKey('tags')) { @($arch['tags']) -join ',' } else { '' } if (-not $localSnips.ContainsKey($name)) { $status = 'Added' $localMod = $null } else { $local = $localSnips[$name] $localHash = $local.ContentHash $localMod = $local.Modified $status = if ($archHash -and $localHash -and $archHash -ne $localHash) { 'Modified' } else { 'Unchanged' } } $results.Add([PSCustomObject]@{ Name = $name Status = $status LocalModified = $localMod ArchiveModified = $archMod Language = $archLang Tags = $archTags }) } foreach ($name in $localSnips.Keys) { if (-not $archiveSnips.ContainsKey($name)) { $local = $localSnips[$name] $results.Add([PSCustomObject]@{ Name = $name Status = 'LocalOnly' LocalModified = $local.Modified ArchiveModified = $null Language = $local.Language Tags = ($local.Tags -join ',') }) } } if ($PassThru) { return $results.ToArray() } $added = @($results | Where-Object Status -eq 'Added').Count $modified = @($results | Where-Object Status -eq 'Modified').Count $unchanged = @($results | Where-Object Status -eq 'Unchanged').Count $localOnly = @($results | Where-Object Status -eq 'LocalOnly').Count script:Out-Info "Collection comparison: $Path" Write-Host '' $results | Sort-Object Status, Name | Format-Table Name, Status, Language, ArchiveModified -AutoSize Write-Host '' script:Out-OK "Summary — Added: $added Modified: $modified Unchanged: $unchanged LocalOnly: $localOnly" } finally { if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue } } } |