scripts/New-QuietCliPackage.ps1
|
#requires -Version 7.6 #requires -PSEdition Core [CmdletBinding()] param( [Parameter(Mandatory)][string]$ArchivePath, [Parameter(Mandatory)][string]$ManifestPath, [Parameter(Mandatory)][string]$OutputDirectory, [Parameter(Mandatory)][string]$Tag ) $ErrorActionPreference = 'Stop' if ($env:GITHUB_ACTIONS -ne 'true') { throw 'Release package creation is reserved for the GitHub Actions workflow.' } Import-Module Microsoft.PowerShell.PSResourceGet -RequiredVersion 1.2.0 -ErrorAction Stop $archivePath = [IO.Path]::GetFullPath($ArchivePath) $manifestPath = [IO.Path]::GetFullPath($ManifestPath) $output = [IO.Path]::GetFullPath($OutputDirectory) $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json -AsHashtable if ((Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash -ine $manifest.ArchiveSHA256) { throw 'Archive SHA256 mismatch.' } if ($manifest.SourceRef -cne $Tag) { throw 'Archive source tag does not match the release tag.' } [void][IO.Directory]::CreateDirectory($output) $archiveReport = Join-Path $output 'archive-verification.json' $null = & (Join-Path $PSScriptRoot 'Verify-QuietCliArchive.ps1') -ArchivePath $archivePath -OutputPath $archiveReport $temporary = Join-Path ([IO.Path]::GetTempPath()) ('quietcli-package-' + [guid]::NewGuid().ToString('N')) [void][IO.Directory]::CreateDirectory($temporary) try { [IO.Compression.ZipFile]::ExtractToDirectory($archivePath, $temporary) $extracted = Join-Path $temporary 'QuietCli' $embedded = Get-Content -LiteralPath (Join-Path $extracted 'package-manifest.json') -Raw | ConvertFrom-Json -AsHashtable foreach ($field in @('SchemaVersion', 'ModuleVersion', 'Repository', 'ModulePath', 'SourceRef', 'SourceCommitBase', 'SourceKind')) { if ([string]$manifest[$field] -cne [string]$embedded[$field]) { throw "Archive provenance mismatch: $field" } } $expected = @($manifest.Files | ForEach-Object { $_.Path + ':' + $_.SHA256 } | Sort-Object) $actual = @($embedded.Files | ForEach-Object { $_.Path + ':' + $_.SHA256 } | Sort-Object) if (Compare-Object $expected $actual) { throw 'Archive file inventories do not match.' } $zip = [IO.Compression.ZipFile]::OpenRead($archivePath) try { $expectedEntries = @('QuietCli/package-manifest.json') + @($embedded.Files | ForEach-Object { 'QuietCli/' + $_.Path }) $actualEntries = @($zip.Entries | ForEach-Object FullName) if ($expectedEntries.Count -ne $actualEntries.Count -or (Compare-Object $expectedEntries $actualEntries)) { throw 'Archive contains files outside its verified inventory.' } } finally { $zip.Dispose() } $release = & (Join-Path $PSScriptRoot 'Test-QuietCliRelease.ps1') -ModuleRoot $extracted -Tag $Tag $staging = Join-Path $temporary $release.Name Move-Item -LiteralPath $extracted -Destination $staging $packagePath = Join-Path $output ($release.Name + '.' + $release.Version + '.nupkg') if (Test-Path -LiteralPath $packagePath) { throw "Package output already exists: $packagePath" } Compress-PSResource -Path $staging -DestinationPath $output -ErrorAction Stop if (!(Test-Path -LiteralPath $packagePath -PathType Leaf)) { throw 'PSResourceGet did not create the expected package.' } # PSResourceGet omits nested .nuspec files. Restore every declared payload file, # including third-party metadata, before verifying and hashing the final package. $package = [IO.Compression.ZipFile]::Open($packagePath, [IO.Compression.ZipArchiveMode]::Update) try { foreach ($relative in (@($embedded.Files.Path) + @('package-manifest.json'))) { if ($null -eq $package.GetEntry($relative)) { $entry = [IO.Compression.ZipFileExtensions]::CreateEntryFromFile($package, (Join-Path $staging $relative), $relative, [IO.Compression.CompressionLevel]::Optimal) $entry.LastWriteTime = [DateTimeOffset]::new(2000, 1, 1, 0, 0, 0, [TimeSpan]::Zero) } } } finally { $package.Dispose() } $packageReport = Join-Path $output 'package-verification.json' $null = & (Join-Path $PSScriptRoot 'Verify-QuietCliArchive.ps1') -ArchivePath $packagePath -ModuleDirectory '.' -OutputPath $packageReport $manifest['PackageFile'] = Split-Path -Leaf $packagePath $manifest['PackageSHA256'] = (Get-FileHash -LiteralPath $packagePath -Algorithm SHA256).Hash.ToLowerInvariant() $manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $manifestPath -Encoding utf8 [pscustomobject]@{ Outcome = 'Success' Data = @{ Package = $packagePath; SHA256 = $manifest.PackageSHA256; ModuleVersion = $release.Version; Files = $embedded.Files.Count } } | ConvertTo-Json -Depth 4 -Compress } finally { if (Test-Path -LiteralPath $temporary) { Remove-Item -LiteralPath $temporary -Recurse -Force } } |