Publish-Packages.psm1
|
<#
.Synopsis Returns all published versions of a package on a feed #> function Get-FeedPackageVersions { param ([string] $Id, [string] $Feed) $json = dotnet package search $Id --exact-match --prerelease --source $Feed --format json | Out-String $exitCode = $LASTEXITCODE $result = try { $json | ConvertFrom-Json } catch { $null } if ($null -eq $result) { throw "dotnet package search failed for $Id on $Feed`n$json" } #errors (unknown feed, auth, network) are reported per source with exit code 0 - treat any problem as failure, #otherwise an unreachable feed would look like "version not on feed" and the package would be pushed $problems = @($result.problems) + @($result.searchResult | ForEach-Object { $_.problems }) | Where-Object { $null -ne $_ } if ($problems.Count -gt 0) { throw "dotnet package search failed for $Id on ${Feed}: $(($problems | ForEach-Object { $_.text }) -join '; ')" } if ($exitCode -ne 0) { throw "dotnet package search failed for $Id on $Feed`n$json" } if (@($result.searchResult).Count -eq 0) { throw "dotnet package search returned no result for $Id on $Feed" } @($result.searchResult.packages | Where-Object { $_.id -eq $Id } | ForEach-Object { $_.version }) } <# .Synopsis Automatic Publish Tool (APT) .DESCRIPTION Publish nugets to a feed. Only packages whose id+version is not on the feed yet are pushed; an existing version is never sent. The current directory is never changed: packages are only read from -Path, while dotnet keeps running in the directory the command was called from, so the nuget.config of that directory (and of its parents) is used. -Feed is a source name from 'dotnet nuget list source'. Credentials come from nuget.config. Lomtec.Packages (Azure Artifacts) gets the api key 'az', other feeds (Nuaktiv.GitLab) no key. -WhatIf runs every step (file names, version lookup on the feed) except the push itself. .EXAMPLE Publish-Packages -Path D:\Projects\Framework\ActiveOffice.NET\developer\publish\ -Feed Lomtec.Packages .EXAMPLE Publish-Packages -Path D:\Projects\Framework\ActiveOffice.NET\developer\publish\ -Feed Nuaktiv.GitLab .EXAMPLE Publish-Packages -Path D:\Projects\Framework\ActiveOffice.NET\developer\publish\ -Feed Nuaktiv.GitLab -WhatIf .NOTES Author: Imrich Szolik #> function Publish-Packages { [CmdletBinding(SupportsShouldProcess = $true)] param ( #Packages path [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$false, HelpMessage="Packages path")] [string] $Path, #Package feed name (dotnet nuget list source), e.g. Lomtec.Packages or Nuaktiv.GitLab [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$false, HelpMessage="Packages feed name (dotnet nuget list source).")] [string] $Feed = "Lomtec.Packages" ) begin { if ($null -eq (Get-Command dotnet -ErrorAction SilentlyContinue)) { throw "Please install .NET SDK 8.0.200 or newer (dotnet package search)." } } process { #no Push-Location: the packages directory is only read from, every dotnet command keeps the caller's #current directory, so the nuget.config (sources + credentials) of that directory stays in effect if (-not (Test-Path -LiteralPath $Path -PathType Container)) { throw "Packages path not found: $Path" } #validate feed: must be an enabled source in 'dotnet nuget list source' $sourceList = dotnet nuget list source if ($LASTEXITCODE -ne 0) { throw "dotnet nuget list source failed:`n$($sourceList -join "`n")" } $sources = @($sourceList | ForEach-Object { if ($_ -match '^\s*\d+\.\s+(?<name>.+?)\s+\[(?<state>[^\]]+)\]\s*$') { [pscustomobject]@{ Name = $Matches['name']; Enabled = ($Matches['state'] -eq 'Enabled') } } }) $source = $sources | Where-Object { $_.Name -eq $Feed } | Select-Object -First 1 if ($null -eq $source) { throw "Feed '$Feed' not found in NuGet sources (nuget.config searched from the current directory $((Get-Location).Path)). Available: $(@($sources | Where-Object { $_.Enabled }).Name -join ', ')" } if (-not $source.Enabled) { throw "Feed '$Feed' is disabled in NuGet sources." } $Feed = $source.Name # #legacy *.symbols.nupkg have the same id+version as the package, never push them as packages $files = Get-ChildItem -LiteralPath $Path -Filter *.nupkg -File | Where-Object { $_.Name -notlike '*.symbols.nupkg' } if(($files | Measure-Object).Count -eq 0 ){ Write-Host "#### APT NUGET deploy into $Feed aborted. Not found any packages in $Path ####" return } # Write-Host "#### APT NUGET deploy of $Path into $Feed ####" # $pushed = 0 $skipped = 0 $notPushed = 0 # foreach($file in $files){ #id and version from the file name: <id>.<major>.<minor>.<patch>[-prerelease].nupkg $package = [regex]::Match($file.Name, '^(?<id>.+?)\.(?<version>\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\.nupkg$') if (-not $package.Success) { throw "Cannot read id/version from file name: $($file.Name)" } $id = $package.Groups['id'].Value $version = $package.Groups['version'].Value $name = "$id.$version" # #versions already on the feed are never pushed $feedVersions = Get-FeedPackageVersions -Id $id -Feed $Feed if ($feedVersions -contains $version) { Write-Verbose "Found $name, not deployed." $skipped++ continue } # if ($PSCmdlet.ShouldProcess($name, "Push to $Feed")) { Write-Host "#### APT NUGET deploy: $name ####" $pushArgs = @('nuget', 'push', $file.FullName, '--source', $Feed, '--skip-duplicate') #Azure Artifacts requires an api key (any value), GitLab authenticates with nuget.config credentials only if ($Feed -eq 'Lomtec.Packages') { $pushArgs += @('--api-key', 'az') } dotnet @pushArgs if ($LASTEXITCODE -ne 0) { throw "Push of $name to $Feed failed." } $pushed++ } else { #-WhatIf (or -Confirm answered No): everything checked, only the push is not sent $notPushed++ } } # $summary = "Pushed: $pushed, already on feed: $skipped" if ($notPushed -gt 0) { $summary += ", would push (not sent): $notPushed" } Write-Host "#### APT NUGET deploy into $Feed done. $summary ####" } end { } } Export-ModuleMember -Function Publish-Packages # SIG # Begin signature block # MIIIEwYJKoZIhvcNAQcCoIIIBDCCCAACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBmGvThfwhCt9Vi # f64LHV4GVj9Qzgms8XgAoZ/hlxrp7qCCBQ8wggULMIIC86ADAgECAgIApTANBgkq # hkiG9w0BAQsFADCBoDELMAkGA1UEBhMCU0sxETAPBgNVBAgTCFNsb3Zha2lhMRMw # EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpMb210ZWMuY29tMSswKQYDVQQD # EyJMb210ZWMuY29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MScwJQYJKoZIhvcN # AQkBFhhJbXJpY2guU3pvbGlrQGxvbXRlYy5jb20wHhcNMTkwMzI2MTUwNDQzWhcN # MzAwMTAxMDAwMDAwWjCBkzELMAkGA1UEBhMCU0sxETAPBgNVBAgTCFNsb3Zha2lh # MRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpMb210ZWMuY29tMR4wHAYD # VQQDExVMb210ZWMuY29tIFBvd2Vyc2hlbGwxJzAlBgkqhkiG9w0BCQEWGEltcmlj # aC5Tem9saWtAbG9tdGVjLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAL4n4nCHbkdrf09IHNFQ2P/z6I43GKScsFJOQHUMkRD3ALoUFL/URC9sW2fY # rqG5VkFAKhnM0VxeiICR53cAshNShjFf58PhaS973jtCoJcaugIVBVoFIuQ+gnNY # Jp2VdBbIPKMW4JjjZOxBEkHpMWmfitXKWGKeU68Qcn3oI6PO+YSztfXLe1NU+1GI # O3fA7E0vHVPUf/qWZXMYU5ElLQVm9AXbfX79mTgl76A57+OC6j+Aehkd2OPfUl4w # snox3fOyUAUA8iojeWh97PpXd/s+RkuxWdgsC4YSWDUjhZSzBkml9uerYqo9a+XA # b39dvkpK9TPl3q5HNBQMkCfp8bkCAwEAAaNaMFgwCQYDVR0TBAIwADALBgNVHQ8E # BAMCB4AwKwYDVR0lBCQwIgYIKwYBBQUHAwMGCisGAQQBgjcCARUGCisGAQQBgjcC # ARYwEQYJYIZIAYb4QgEBBAQDAgSwMA0GCSqGSIb3DQEBCwUAA4ICAQAJJapv9skY # jh5HTsJnqDdtqh7YOOXuA8g+DKBj+5gDEZE5V9VhAFVp8UJ9RoITGGIRTVId0lqc # LJiVSTHx305VW9aID8vo77kfrTyXvPXNIsTtHnPkkwH47+CoiY3IpPQLjUA/Q6Qd # qwINvvwom7/Wc+OoIqlPdJH5DbBrIy85dr6M/Lm3Rw2BolcTRwXTB3xAhweth78B # P6pbcAd32FdymkRopLIihuNs7g7ZR/Q/5803G+OiQIMRGyTvaQ+aQjJgFpkzp7NI # whzougfCOV47Sc89jEpUqw16i2UFfz2ywOlWUyYtue1S1PjM1ljgJfRo+e0wUnFp # gFQzXGF1bTYVaQ4e3nJleADfvqeXoH2AYeBTbz9BcogkkfURAC3iiob1bNs5jE1C # brEDCw6m/03k0oOmm3xQksXyAhBYuUkRwu9jd4y3FwZ/syDGLz3b6cY8o1YyINOO # A3B2r92shNt0rWhJu3v+qcIVmFQ0aKlhNNRoiVlQgJ7NgO0UV+vU2lgiscpUSxEt # xKN+450r49su06NA6zsyn3CELUmVkPyjx5fyizwt9KxVuYOUSEb32Y7QCffHJ1qt # F6SuQrKbgb/24y7cCDW6PDVRvPOySUlKu9sPykICDjvzXBvjYEILM7AmtYCIBGQg # imBsQvTEoXJXiwAOi1XNz3LxcjQHQ/ZM4zGCAlowggJWAgEBMIGnMIGgMQswCQYD # VQQGEwJTSzERMA8GA1UECBMIU2xvdmFraWExEzARBgNVBAcTCkJyYXRpc2xhdmEx # EzARBgNVBAoTCkxvbXRlYy5jb20xKzApBgNVBAMTIkxvbXRlYy5jb20gQ2VydGlm # aWNhdGlvbiBBdXRob3JpdHkxJzAlBgkqhkiG9w0BCQEWGEltcmljaC5Tem9saWtA # bG9tdGVjLmNvbQICAKUwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEK # MAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3 # AgELMQ4wDAYKKwYBBAGCNwIBFjAvBgkqhkiG9w0BCQQxIgQgv+tQE3Sk3d0JkYL+ # nuxZIHK7BZfs3xmOVft7K3t6sIMwDQYJKoZIhvcNAQEBBQAEggEArDM8bFhoncgf # 0b27FKFAK5vUh54ORI2sp4T7WAYSlKNcqTv1YDNmLHhyUUKSpz/LUVIyBFlHC7Jc # Gep+v2dHPDApWJHAAPO43cpR67kKCI0jdPpl5h8nEsXaSmK5UaLaicoa/yijtUHo # zzFt7Eg6UY7UUBgeZeFhMFbEsu5JQ+J/DwVGApNyC77qd/sELtn7YZkkvLlizq7j # WjXPpEXykLetwquW9Bf8LlEDCiHOmGJ4JdMdb5ePDuP1Jq1ZpnAv9em670USWvGo # wRwdXOuJIiF/1GQInY8lGRFXWI5PbBM2jcHfSeq+C7Uv+08+sWkk5+L4eV/YtFeq # Gdy0Md1KnA== # SIG # End signature block |