diff --git a/Tests/PSGetPublishModule.Tests.ps1 b/Tests/PSGetPublishModule.Tests.ps1 index c62a12cb..3eb3ad21 100644 --- a/Tests/PSGetPublishModule.Tests.ps1 +++ b/Tests/PSGetPublishModule.Tests.ps1 @@ -763,6 +763,83 @@ Describe PowerShell.PSGet.PublishModuleTests -Tags 'BVT','InnerLoop' { } ` -Skip:$($PSVersionTable.PSVersion -lt '5.0.0') + # Purpose: Test Publish-Module cmdlet warns when tag length is greater than 4000 + # + # Action: Create a module manifest with PrivateData\PSData hashtable, try to publish + # + # Expected Result: Publish operation should succeed and should have warned about tag length. + # + It PublishModuleFunctionsAsTagsWarnWithoutSkipAutomaticTags { + $version = "1.0.0" + $Description = "$script:PublishModuleName module" + $Tags = "PSGet","DSC" + $Author = "AuthorName" + $CompanyName = "CompanyName" + $CopyRight = "CopyRight" + + $functionNames = 1..250 | Foreach-Object { "Get-TestFunction$($_)" } + $tempFunctions = 1..250 | Foreach-Object { "function Get-TestFunction$($_) { Get-Date }" + [Environment]::NewLine } + Set-Content (Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psm1") -Value $tempFunctions + + New-ModuleManifest -Path (Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psd1") ` + -ModuleVersion $version ` + -Description "$script:PublishModuleName module" ` + -NestedModules "$script:PublishModuleName.psm1" ` + -Author $Author ` + -CompanyName $CompanyName ` + -Copyright $CopyRight ` + -Tags $Tags ` + -FunctionsToExport $functionNames + + Publish-Module -Path $script:PublishModuleBase ` + -NuGetApiKey $script:ApiKey ` + -Repository "PSGallery" ` + -WarningAction SilentlyContinue ` + -WarningVariable wa + + Assert ("$wa".Contains('4000 characters')) "Warning messages should include 'Tag list exceeded 4000 characters and may not be accepted by some Nuget feeds.'" + } ` + -Skip:$($PSVersionTable.PSVersion -lt '5.0.0') + + # Purpose: Test Publish-Module cmdlet excludes functions from tags when using SkipAutomaticTags parameter + # + # Action: Create a module manifest with PrivateData\PSData hashtable, try to publish with SkipAutomaticTags parameter + # + # Expected Result: Publish operation should succeed and should not have warned about tag length + # + It PublishModuleFunctionsAsTagsWithSkipAutomaticTags { + $version = "1.0.0" + $Description = "$script:PublishModuleName module" + $Tags = "PSGet","DSC" + $Author = "AuthorName" + $CompanyName = "CompanyName" + $CopyRight = "CopyRight" + + $functionNames = 1..250 | Foreach-Object { "Get-TestFunction$($_)" } + $tempFunctions = 1..250 | Foreach-Object { "function Get-TestFunction$($_) { Get-Date }" + [Environment]::NewLine } + Set-Content (Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psm1") -Value $tempFunctions + + New-ModuleManifest -Path (Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psd1") ` + -ModuleVersion $version ` + -Description "$script:PublishModuleName module" ` + -NestedModules "$script:PublishModuleName.psm1" ` + -Author $Author ` + -CompanyName $CompanyName ` + -Copyright $CopyRight ` + -Tags $Tags ` + -FunctionsToExport $functionNames + + Publish-Module -Path $script:PublishModuleBase ` + -NuGetApiKey $script:ApiKey ` + -Repository "PSGallery" ` + -SkipAutomaticTags ` + -WarningAction SilentlyContinue ` + -WarningVariable wa + + Assert (-not "$wa".Contains('4000 characters')) "Warning messages should not include 'Tag list exceeded 4000 characters and may not be accepted by some Nuget feeds.'" + } ` + -Skip:$($PSVersionTable.PSVersion -lt '5.0.0') + # Purpose: Test Publish-Module cmdlet gets the PSData properties from the module manifest file and also with Uri objects specified to the cmdlet # # Action: Create a module manifest with PrivateData\PSData hashtable, try to publish it with Uri objects to ProjectUri, IconUri and LicenseUri parameters diff --git a/src/PowerShellGet/private/functions/New-NuspecFile.ps1 b/src/PowerShellGet/private/functions/New-NuspecFile.ps1 index 2e7fa6ef..6ba53dbf 100644 --- a/src/PowerShellGet/private/functions/New-NuspecFile.ps1 +++ b/src/PowerShellGet/private/functions/New-NuspecFile.ps1 @@ -59,10 +59,9 @@ function New-NuspecFile { $packageElement = $xml.CreateElement("package", $nameSpaceUri) $metaDataElement = $xml.CreateElement("metadata", $nameSpaceUri) - #truncate tags if they exceed nuspec specifications for size. - $Tags = $Tags -Join " " - - if ($Tags.Length -gt 4000) { + # warn we're over 4000 characters for standard nuget servers + $tagsString = $Tags -Join " " + if ($tagsString.Length -gt 4000) { Write-Warning -Message "Tag list exceeded 4000 characters and may not be accepted by some Nuget feeds." } @@ -75,7 +74,7 @@ function New-NuspecFile { releaseNotes = $ReleaseNotes requireLicenseAcceptance = $RequireLicenseAcceptance.ToString().ToLower() copyright = $Copyright - tags = $Tags + tags = $tagsString } if ($LicenseUrl) { $metaDataElementsHash.Add("licenseUrl", $LicenseUrl) } diff --git a/src/PowerShellGet/private/functions/Publish-PSArtifactUtility.ps1 b/src/PowerShellGet/private/functions/Publish-PSArtifactUtility.ps1 index 4d93943f..31e001da 100644 --- a/src/PowerShellGet/private/functions/Publish-PSArtifactUtility.ps1 +++ b/src/PowerShellGet/private/functions/Publish-PSArtifactUtility.ps1 @@ -53,6 +53,10 @@ function Publish-PSArtifactUtility { [string[]] $Tags, + [Parameter(ParameterSetName = 'PublishModule')] + [switch] + $SkipAutomaticTags, + [Parameter(ParameterSetName = 'PublishModule')] [Uri] $LicenseUri, @@ -218,7 +222,7 @@ function Publish-PSArtifactUtility { if ($PSScriptInfo) { $Tags += "PSScript" - if ($PSScriptInfo.DefinedCommands) { + if ($PSScriptInfo.DefinedCommands -and -not $SkipAutomaticTags) { if ($PSScriptInfo.DefinedFunctions) { $Tags += "$($script:Includes)_Function" $Tags += $PSScriptInfo.DefinedFunctions | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } @@ -251,52 +255,54 @@ function Publish-PSArtifactUtility { $ModuleManifestHashTable = Get-ManifestHashTable -Path $ManifestPath - if ($PSModuleInfo.ExportedCommands.Count) { - if ($PSModuleInfo.ExportedCmdlets.Count) { - $Tags += "$($script:Includes)_Cmdlet" - $Tags += $PSModuleInfo.ExportedCmdlets.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Cmdlet)_$_" } + if (-not $SkipAutomaticTags) { + if ($PSModuleInfo.ExportedCommands.Count) { + if ($PSModuleInfo.ExportedCmdlets.Count) { + $Tags += "$($script:Includes)_Cmdlet" + $Tags += $PSModuleInfo.ExportedCmdlets.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Cmdlet)_$_" } - #if CmdletsToExport field in manifest file is "*", we suggest the user to include all those cmdlets for best practice - if ($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('CmdletsToExport') -and ($ModuleManifestHashTable.CmdletsToExport -eq "*")) { - $WarningMessage = $LocalizedData.ShouldIncludeCmdletsToExport -f ($ManifestPath) - Write-Warning -Message $WarningMessage + #if CmdletsToExport field in manifest file is "*", we suggest the user to include all those cmdlets for best practice + if ($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('CmdletsToExport') -and ($ModuleManifestHashTable.CmdletsToExport -eq "*")) { + $WarningMessage = $LocalizedData.ShouldIncludeCmdletsToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } } - } - if ($PSModuleInfo.ExportedFunctions.Count) { - $Tags += "$($script:Includes)_Function" - $Tags += $PSModuleInfo.ExportedFunctions.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } + if ($PSModuleInfo.ExportedFunctions.Count) { + $Tags += "$($script:Includes)_Function" + $Tags += $PSModuleInfo.ExportedFunctions.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } - if ($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('FunctionsToExport') -and ($ModuleManifestHashTable.FunctionsToExport -eq "*")) { - $WarningMessage = $LocalizedData.ShouldIncludeFunctionsToExport -f ($ManifestPath) - Write-Warning -Message $WarningMessage + if ($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('FunctionsToExport') -and ($ModuleManifestHashTable.FunctionsToExport -eq "*")) { + $WarningMessage = $LocalizedData.ShouldIncludeFunctionsToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } } - } - $Tags += $PSModuleInfo.ExportedCommands.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } - } + $Tags += $PSModuleInfo.ExportedCommands.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } + } - $dscResourceNames = Get-ExportedDscResources -PSModuleInfo $PSModuleInfo - if ($dscResourceNames) { - $Tags += "$($script:Includes)_DscResource" + $dscResourceNames = Get-ExportedDscResources -PSModuleInfo $PSModuleInfo + if ($dscResourceNames) { + $Tags += "$($script:Includes)_DscResource" - $Tags += $dscResourceNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:DscResource)_$_" } + $Tags += $dscResourceNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:DscResource)_$_" } - #If DscResourcesToExport is commented out or "*" is used, we will write-warning - if ($ModuleManifestHashTable -and - ($ModuleManifestHashTable.ContainsKey("DscResourcesToExport") -and - $ModuleManifestHashTable.DscResourcesToExport -eq "*") -or - -not $ModuleManifestHashTable.ContainsKey("DscResourcesToExport")) { - $WarningMessage = $LocalizedData.ShouldIncludeDscResourcesToExport -f ($ManifestPath) - Write-Warning -Message $WarningMessage + #If DscResourcesToExport is commented out or "*" is used, we will write-warning + if ($ModuleManifestHashTable -and + ($ModuleManifestHashTable.ContainsKey("DscResourcesToExport") -and + $ModuleManifestHashTable.DscResourcesToExport -eq "*") -or + -not $ModuleManifestHashTable.ContainsKey("DscResourcesToExport")) { + $WarningMessage = $LocalizedData.ShouldIncludeDscResourcesToExport -f ($ManifestPath) + Write-Warning -Message $WarningMessage + } } - } - $RoleCapabilityNames = Get-AvailableRoleCapabilityName -PSModuleInfo $PSModuleInfo - if ($RoleCapabilityNames) { - $Tags += "$($script:Includes)_RoleCapability" + $RoleCapabilityNames = Get-AvailableRoleCapabilityName -PSModuleInfo $PSModuleInfo + if ($RoleCapabilityNames) { + $Tags += "$($script:Includes)_RoleCapability" - $Tags += $RoleCapabilityNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:RoleCapability)_$_" } + $Tags += $RoleCapabilityNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:RoleCapability)_$_" } + } } # Populate the module dependencies elements from RequiredModules and diff --git a/src/PowerShellGet/public/psgetfunctions/Publish-Module.ps1 b/src/PowerShellGet/public/psgetfunctions/Publish-Module.ps1 index 4a3d1f4b..682c2b81 100644 --- a/src/PowerShellGet/public/psgetfunctions/Publish-Module.ps1 +++ b/src/PowerShellGet/public/psgetfunctions/Publish-Module.ps1 @@ -76,7 +76,11 @@ function Publish-Module { [Parameter(ParameterSetName = "ModuleNameParameterSet")] [switch] - $AllowPrerelease + $AllowPrerelease, + + [Parameter()] + [switch] + $SkipAutomaticTags ) Begin { @@ -137,8 +141,7 @@ function Publish-Module { if (-not $DestinationLocation -or (-not (Microsoft.PowerShell.Management\Test-Path $DestinationLocation) -and - -not (Test-WebUri -uri $DestinationLocation))) - { + -not (Test-WebUri -uri $DestinationLocation))) { $message = $LocalizedData.PSGalleryPublishLocationIsMissing -f ($Repository, $Repository) ThrowError -ExceptionName "System.ArgumentException" ` -ExceptionMessage $message ` @@ -370,12 +373,12 @@ function Publish-Module { # This finds all the items without force (leaving out hidden files and dirs) then copies them Microsoft.PowerShell.Management\Get-ChildItem $Path -recurse | Microsoft.PowerShell.Management\Copy-Item -Force -Confirm:$false -WhatIf:$false -Destination { - if ($_.PSIsContainer) { - Join-Path $tempModulePathForFormatVersion $_.Parent.FullName.substring($path.length) - } - else { - join-path $tempModulePathForFormatVersion $_.FullName.Substring($path.Length) - } + if ($_.PSIsContainer) { + Join-Path $tempModulePathForFormatVersion $_.Parent.FullName.substring($path.length) + } + else { + join-path $tempModulePathForFormatVersion $_.FullName.Substring($path.Length) + } } try { @@ -541,22 +544,23 @@ function Publish-Module { $shouldProcessMessage = $LocalizedData.PublishModulewhatIfMessage -f ($moduleInfo.Version, $moduleInfo.Name) if ($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Module")) { $PublishPSArtifactUtility_Params = @{ - PSModuleInfo = $moduleInfo - ManifestPath = $manifestPath - NugetApiKey = $NuGetApiKey - Destination = $DestinationLocation - Repository = $Repository - NugetPackageRoot = $tempModulePath - FormatVersion = $FormatVersion - ReleaseNotes = $($ReleaseNotes -join "`r`n") - Tags = $Tags - LicenseUri = $LicenseUri - IconUri = $IconUri - ProjectUri = $ProjectUri - Verbose = $VerbosePreference - WarningAction = $WarningPreference - ErrorAction = $ErrorActionPreference - Debug = $DebugPreference + PSModuleInfo = $moduleInfo + ManifestPath = $manifestPath + NugetApiKey = $NuGetApiKey + Destination = $DestinationLocation + Repository = $Repository + NugetPackageRoot = $tempModulePath + FormatVersion = $FormatVersion + ReleaseNotes = $($ReleaseNotes -join "`r`n") + Tags = $Tags + SkipAutomaticTags = $SkipAutomaticTags + LicenseUri = $LicenseUri + IconUri = $IconUri + ProjectUri = $ProjectUri + Verbose = $VerbosePreference + WarningAction = $WarningPreference + ErrorAction = $ErrorActionPreference + Debug = $DebugPreference } if ($PSBoundParameters.Containskey('Credential')) { $PublishPSArtifactUtility_Params.Add('Credential', $Credential)