Modules/businessdev.ALbuild.Feeds/Public/Publish-BcPackage.ps1
|
function Publish-BcPackage { <# .SYNOPSIS Pushes a NuGet package (.nupkg) to a NuGet v3 feed. .DESCRIPTION Resolves the feed's PackagePublish service from its service index and uploads the package using a multipart PUT (the NuGet v3 push protocol). An existing package version is reported as a conflict rather than failing the build (unless -FailOnConflict is set). .PARAMETER PackagePath Path to the .nupkg to push. .PARAMETER Url The feed's NuGet v3 service index URL (.../index.json). .PARAMETER ApiKey The API key (nuget.org) or PAT (Azure DevOps/GitHub) for publishing. .PARAMETER FailOnConflict Throw if the package version already exists. Default: warn and continue. .PARAMETER View Optional Azure DevOps Artifacts view (e.g. 'Prerelease' or 'Release') to promote the package to after a successful push. When omitted, only the push is performed. Promotion still runs when the version already existed (a non-failing conflict), since promoting an existing version is valid. Ignored for non-Azure-DevOps feeds (Invoke-BcPackagePromotion throws if misused there). .EXAMPLE Publish-BcPackage -PackagePath .\pkg.1.0.0.nupkg -Url $feedUrl -ApiKey $env:NUGET_KEY .EXAMPLE Publish-BcPackage -PackagePath .\pkg.1.0.0.nupkg -Url $feedUrl -ApiKey $env:FEED_PAT -View Prerelease .OUTPUTS System.Boolean ($true on success or conflict-when-not-failing). #> [CmdletBinding(SupportsShouldProcess)] [OutputType([bool])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $PackagePath, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Url, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ApiKey, [switch] $FailOnConflict, [string] $View ) if (-not (Test-Path -LiteralPath $PackagePath)) { throw "Package not found: '$PackagePath'." } $headers = @{} if ($Url -notlike 'https://api.nuget.org/*') { $headers['Authorization'] = "Basic $([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("user:$ApiKey")))" } $index = Invoke-RestMethod -Uri $Url -Headers $headers -Method Get -UseBasicParsing -ErrorAction Stop $publishUrl = $index.resources | Where-Object { $_.'@type' -eq 'PackagePublish/2.0.0' } | Select-Object -ExpandProperty '@id' -First 1 if (-not $publishUrl) { throw "Feed '$Url' does not expose PackagePublish/2.0.0." } if (-not $PSCmdlet.ShouldProcess($publishUrl, "Push $(Split-Path $PackagePath -Leaf)")) { return $true } # Build a multipart/form-data body containing the package bytes. $boundary = [Guid]::NewGuid().ToString() $lf = "`r`n" $fileName = [System.IO.Path]::GetFileName($PackagePath) $tempBody = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString() + '.body') $fs = [System.IO.File]::OpenWrite($tempBody) try { $enc = [System.Text.Encoding]::UTF8 $header = "--$boundary$lf" + "Content-Type: application/octet-stream$lf" + "Content-Disposition: form-data; name=package; filename=`"$fileName`"$lf$lf" $headerBytes = $enc.GetBytes($header) $fs.Write($headerBytes, 0, $headerBytes.Length) $packageBytes = [System.IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $PackagePath).ProviderPath) $fs.Write($packageBytes, 0, $packageBytes.Length) $footerBytes = $enc.GetBytes("$lf--$boundary--$lf") $fs.Write($footerBytes, 0, $footerBytes.Length) } finally { $fs.Dispose() } $pushHeaders = $headers.Clone() $pushHeaders['X-NuGet-ApiKey'] = $ApiKey try { Invoke-RestMethod -Uri $publishUrl -Method Put -Headers $pushHeaders ` -ContentType "multipart/form-data; boundary=$boundary" -InFile $tempBody -ErrorAction Stop | Out-Null Write-ALbuildLog -Level Success "Pushed '$fileName' to '$Url'." } catch { $status = $null if ($_.Exception.Response) { try { $status = [int]$_.Exception.Response.StatusCode } catch { $status = $null } } if ($status -eq 409) { if ($FailOnConflict) { throw "Package '$fileName' already exists on '$Url'." } Write-ALbuildLog -Level Warning "Package '$fileName' already exists on '$Url'; skipping." } else { throw "Failed to push '$fileName' to '$Url': $($_.Exception.Message)" } } finally { if (Test-Path -LiteralPath $tempBody) { Remove-Item -LiteralPath $tempBody -Force -ErrorAction SilentlyContinue } } # The version now exists on the feed (freshly pushed or already present) - promote it if requested. if ($View) { Invoke-BcPackagePromotion -Url $Url -PackagePath $PackagePath -View $View -ApiKey $ApiKey | Out-Null } return $true } |