Private/Publish-ChocolateyPackage.ps1
function Publish-ChocolateyPackage { <# .SYNOPSIS Publishes a Chocolatey package file to a source repository, with optional force-push for GitHub NuGet feeds. .DESCRIPTION Pushes a .nupkg package to the specified Chocolatey or NuGet repository using choco push. Supports GitHub NuGet feeds and can force-push by deleting an existing version if a conflict occurs (409 Conflict) and -Force is specified. Returns the path to the published package file. .PARAMETER Path Path to the .nupkg package file to publish. .PARAMETER SourceUrl The repository URL to push the package to. .PARAMETER ApiKey Optional. API key for authenticating with the source repository. .PARAMETER Force If specified and a 409 Conflict occurs on GitHub, deletes the existing version and retries the push. Only supported for GitHub NuGet feeds. .EXAMPLE Publish-ChocolateyPackage -Path 'out/firebird.4.0.0.nupkg' -SourceUrl 'https://nuget.pkg.github.com/owner/index.json' -ApiKey $env:GITHUB_TOKEN -Force Publishes the specified package to a GitHub NuGet feed, force-pushing if a version conflict occurs. .OUTPUTS System.String The path to the published .nupkg package file. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline)] [string]$Path, [Parameter(Mandatory)] [string]$SourceUrl, [string]$ApiKey, [switch]$Force ) process { $forcedSuffix = if ($Force) { ' (forced)' } else { '' } if (-not $PSCmdlet.ShouldProcess($Path, "Publish Chocolatey package to '$SourceUrl'$forcedSuffix")) { # Return simulated package path. No need to output verbose messages in WhatIf mode. return $Path } if (-not (Test-Path -LiteralPath $Path)) { throw "Package file not found: $Path" } $chocoArguments = @( 'push', $Path, '--source', $SourceUrl, '--limit-output' ) if ($ApiKey) { $chocoArguments += @('--api-key', $ApiKey) } Write-VerboseMark -Message "Pushing package '$Path' to '$SourceUrl'$forcedSuffix..." $result = Invoke-Chocolatey -Arguments $chocoArguments if ($result.ExitCode -eq 0) { Write-VerboseMark -Message "Successfully published $Path to $SourceUrl" return $Path } if ($result.StdOut -match '409 \(Conflict\)' -and $Force) { # GitHub only: force push logic if (-not $SourceUrl.StartsWith('https://nuget.pkg.github.com')) { throw 'Force push not supported for this source.' } $packageName, $version = ([System.IO.Path]::GetFileNameWithoutExtension($Path)).Split('.', 2) $owner = ($SourceUrl -replace '^https://nuget.pkg.github.com/', '') -replace '/.*', '' $headers = @{ 'Authorization' = "Bearer $ApiKey" } # Get id of this version $versionsUrl = "https://api.github.com/users/$owner/packages/nuget/$packageName/versions" $response = Invoke-RestMethod -Uri $versionsUrl -Headers $headers -Verbose:$false $versionId = $response | Where-Object { $_.name -eq $version } | Select-Object -ExpandProperty 'id' if (-not $versionId) { throw "Unexpected error: Version '$version' not found for package '$packageName'." } # Delete the version (discard response to not output it to the pipeline) $null = Invoke-RestMethod -Uri "$versionsUrl/$versionId" -Headers $headers -Method Delete -Verbose:$false # Retry the push Write-VerboseMark -Message 'Retrying push after forced removal...' $result = Invoke-Chocolatey -Arguments $chocoArguments if ($result.ExitCode -ne 0) { throw "choco push failed for $packageName after force: $($result.StdOut)" } Write-VerboseMark -Message "Successfully published $packageName to $SourceUrl after force." return $Path } throw "choco push failed for $($packageName): $($result.StdOut)" } } |