diff --git a/Build/Publish-Module.ps1 b/Build/Publish-Module.ps1 index 00618f0..d018f2a 100644 --- a/Build/Publish-Module.ps1 +++ b/Build/Publish-Module.ps1 @@ -185,6 +185,22 @@ if (-not $WhatIfPreference -and -not $ApiKey) { } } +# The tags the Gallery lists for a published module: the manifest's, then the ones the publish +# adds for the package kind, the editions and every exported function. +function Get-PublishedTag { + param([Parameter(Mandatory)]$Manifest) + $tags = [System.Collections.Generic.List[string]]::new() + foreach ($tag in @($Manifest.Tags)) { $tags.Add($tag) } + $tags.Add('PSModule') + foreach ($edition in @($Manifest.CompatiblePSEditions)) { $tags.Add("PSEdition_$edition") } + foreach ($name in @($Manifest.ExportedFunctions.Keys | Sort-Object)) { + $tags.Add("PSFunction_$name") + $tags.Add("PSCommand_$name") + } + if ($Manifest.ExportedFunctions.Count) { $tags.Add('PSIncludes_Function') } + $tags +} + # --- 1. Help gates ---------------------------------------------------------------- # Before staging, because a stale-MAML failure should stop the release rather than leave a # staged tree that nobody notices is wrong. @@ -236,6 +252,32 @@ if ($releaseNotesLength -gt $releaseNotesLimit) { "$releaseNotesLimit. Move the older versions out, CHANGELOG.md has them.") } +# NuGet's limit on a package description, which the Gallery applies. +$descriptionLimit = 4000 +$descriptionLength = "$($manifest.Description)".Length +if ($descriptionLength -gt $descriptionLimit) { + throw ("The manifest's Description is $descriptionLength characters; the Gallery accepts at most " + + "$descriptionLimit.") +} + +# The Gallery refuses a package whose tags pass 4,000 characters ("A nuget package's Tags property +# may not be more than 4000 characters long"), and the tags it measures are not the manifest's +# alone: the publish adds PSModule, one PSEdition_ tag per compatible edition, PSFunction_ and +# PSCommand_ for every exported function, and PSIncludes_Function. That is the list the Gallery +# shows for 0.27.0 (61 tags, 1,935 characters for 27 commands), so every new command costs about +# twice its name. Measured here the way the Gallery sees it. +$tagsLimit = 4000 +$publishedTags = Get-PublishedTag -Manifest $manifest +$tagsLength = ($publishedTags -join ' ').Length +if ($tagsLength -gt $tagsLimit) { + throw ("The package's tags come to $tagsLength characters once the publish adds its own for " + + "$($manifest.ExportedFunctions.Count) commands; the Gallery accepts at most $tagsLimit. Drop " + + 'manifest tags, or fewer commands.') +} +$metadataSummary = ("Gallery metadata: Description $descriptionLength/$descriptionLimit, Tags " + + "$tagsLength/$tagsLimit, ReleaseNotes $releaseNotesLength/$releaseNotesLimit characters.") +Write-Information $metadataSummary -InformationAction Continue + # --- 3. Already published? -------------------------------------------------------- # A version number is consumed forever on first publish. Learning that from a rejected # upload is worse than a check costing one request. diff --git a/CHANGELOG.md b/CHANGELOG.md index a837347..9c39608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,11 @@ release notes. `Import-Clixml`, a cast or a `[pscredential]` parameter, the finding is a note that says the call can go. A variable with any other source, a member or a call stays a warning. +- The release rehearsal (`Build/Publish-Module.ps1 -WhatIf`) measures the manifest's `Description` and the + package's tags against the Gallery's 4,000-character limits, next to the `ReleaseNotes` check from 0.27.0. + The tags counted are the ones the Gallery lists, the manifest's plus `PSModule`, the editions and two per + exported command, so a new command costs about twice its name. The module contract test holds the same + three limits, so a pull request fails before a release does. ## [0.27.0] - 2026-10-05 Fixes to the runtime harness and to four rules, each rule change backed by a tenth validation diff --git a/Tests/Unit/Module.Contract.Tests.ps1 b/Tests/Unit/Module.Contract.Tests.ps1 index b5db9b1..17c345d 100644 --- a/Tests/Unit/Module.Contract.Tests.ps1 +++ b/Tests/Unit/Module.Contract.Tests.ps1 @@ -26,6 +26,33 @@ Describe 'IntuneScriptLab module contract' -Tag 'Unit', 'Contract' { @((Test-ModuleManifest -Path $script:ManifestPath).RequiredModules).Count | Should-Be 0 } + It 'keeps the Gallery metadata within the limits the Gallery enforces: ' -ForEach @( + @{ Field = 'ReleaseNotes'; Limit = 10600 } + @{ Field = 'Description'; Limit = 4000 } + @{ Field = 'Tags'; Limit = 4000 } + ) { + # The Gallery refused 0.27.0's first upload: "A package's ReleaseNotes property extracted from + # the PowerShell manifest may not be more than 10600 characters long". Description and Tags + # carry NuGet's 4,000. The tags it measures include the ones the publish adds, PSModule, a + # PSEdition_ tag per edition, PSFunction_ and PSCommand_ per exported function and + # PSIncludes_Function: the list the Gallery shows for 0.27.0. Build/Publish-Module.ps1 checks + # the same three; this fails the pull request instead of the tag-driven release. + $manifest = Test-ModuleManifest -Path $script:ManifestPath + $text = switch ($Field) { + 'ReleaseNotes' { "$($manifest.ReleaseNotes)" } + 'Description' { "$($manifest.Description)" } + 'Tags' { + $tags = @($manifest.Tags) + 'PSModule' + $tags += @($manifest.CompatiblePSEditions | ForEach-Object { "PSEdition_$_" }) + foreach ($name in @($manifest.ExportedFunctions.Keys | Sort-Object)) { + $tags += "PSFunction_$name", "PSCommand_$name" + } + ($tags + 'PSIncludes_Function') -join ' ' + } + } + $text.Length | Should-BeLessThanOrEqual $Limit + } + It 'exports exactly the public functions' { $exported = @((Get-Command -Module IntuneScriptLab -CommandType Function).Name | Sort-Object) $exported | Should-BeCollection @(