Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Build/Publish-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one limit of the three the Gallery has not stated to this module. 4,000 is the length of the NuGet gallery's description column, and the figure the Gallery quoted for tags in PowerShell/PowerShellGetv2#344.

$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.
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions Tests/Unit/Module.Contract.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Field>' -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' {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated rather than dot-sourced from Build/Publish-Module.ps1: that script requires PowerShell 7.4 and PSResourceGet and runs its gates on load, and this test runs under Windows PowerShell 5.1 too.

$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 @(
Expand Down
Loading