Publishing Guide
This guide documents the repository’s current release pipeline for ColorScripts-Enhanced. The PowerShell Gallery is the primary destination; NuGet.org is optional and uses OIDC trusted publishing. The workflow does not publish to GitHub Packages.
Release Contract
- The manifest uses a four-part date-based version:
yyyy.MM.dd.HHmm. - A release tag must be exactly
v<ModuleVersion>and point to the commit being published. - Published package versions are immutable. Increment the manifest version before publishing another build.
- The package is built from
ColorScripts-Enhanced/, normalized to include its README, license, and icon, and attached to the GitHub release.
The current manifest version is 2026.8.17.2152.
Automated Publishing
.github/workflows/publish.yml is the source of truth. It can be invoked by:
- publishing a GitHub release;
- a manual
workflow_dispatch; or - another workflow through
workflow_call.
The workflow:
- installs the pinned PowerShell and Node.js tooling;
- builds the module and runs release-note, verification, conversion, coverage, ScriptAnalyzer, and Pester checks;
- verifies that the requested version and release tag match the built manifest;
- creates and normalizes a
.nupkgpackage; - generates release notes with git-cliff;
- creates the GitHub release as a draft, uploads the normalized package, and only then publishes it as an immutable release;
- publishes to the PowerShell Gallery when
PSGALLERYAPIKEYis available; and - optionally transfers the verified package to a dedicated OIDC job and publishes it to NuGet.org when
publishToNuGetis notfalse.
Manual Inputs
| Input | Default | Effect |
|---|---|---|
publishToNuGet |
true |
Enables the optional NuGet.org publish step |
versionOverride |
empty | Overrides the version passed to build.ps1 |
createRelease |
true |
Creates the immutable GitHub release |
The workflow does not define a publishToGitHub input or push to GitHub Packages.
GitHub release assets fail the workflow if they cannot be uploaded. The release action uses immutableCreate, which creates a draft, attaches the package, and publishes only after the upload succeeds. This follows GitHub’s immutable-release guidance and avoids publishing an assetless release that cannot be repaired in place.
Publishing Credentials
| Secret | Purpose |
|---|---|
PSGALLERYAPIKEY |
Publishes the normalized package to PowerShell Gallery |
PSGALLERYAPIKEY is optional for reusable-workflow calls. A missing key causes the PowerShell Gallery publish step to skip; it does not turn validation into a failure. NuGet.org does not use a repository API-key secret.
NuGet.org Trusted Publishing
The dedicated publish-nuget job has id-token: write, downloads the exact normalized package produced by the validation job, and uses NuGet/login to exchange GitHub’s OIDC token for a one-hour NuGet.org API key immediately before the push. The build and GitHub-release job does not have OIDC permission.
The NuGet.org trusted-publishing policy must match these values:
| Policy field | Value |
|---|---|
| Package owner | typpi |
| Repository owner | Nick2bad4u |
| Repository | PS-Color-Scripts-Enhanced |
| Workflow file | publish.yml |
| Environment | empty |
Leave Environment empty while the workflow does not declare a GitHub Actions environment. If a protected environment is added later, use its exact name in both the workflow job and the NuGet.org policy. A reusable-workflow caller must allow id-token: write; permissions cannot be elevated by the called workflow.
After the first successful trusted publish, revoke any superseded NuGet.org API key and remove the old NUGETAPIKEY repository secret. The workflow no longer reads it.
Manual Dispatch
# Validate, package, create the release, and publish where keys are configured.
gh workflow run publish.yml --ref main
# Skip NuGet.org and use an explicit module version.
gh workflow run publish.yml --ref main `
-f publishToNuGet=false `
-f createRelease=true `
-f versionOverride='2026.7.20.2250'
gh run list --workflow=publish.yml
Do not supply a version override that differs from the intended release tag. The workflow rejects mismatches.
Local Pre-Publish Validation
From the repository root:
Test-ModuleManifest -Path ./ColorScripts-Enhanced/ColorScripts-Enhanced.psd1
npm ci
npm run verify
npm run test
npm run lint
npm run release:verify
npm run build is the aggregate build and release-readiness command. It performs generated-file updates, so review the resulting diff before committing it.
To inspect command exports after validation:
Remove-Module ColorScripts-Enhanced -Force -ErrorAction SilentlyContinue
Import-Module ./ColorScripts-Enhanced/ColorScripts-Enhanced.psd1 -Force
Get-Command -Module ColorScripts-Enhanced | Sort-Object Name
Local Packaging and Publishing
The supported release path is the GitHub workflow. If local publishing is necessary, use a temporary PowerShell repository to create the package, then run the same metadata normalizer used in CI before pushing it.
$stagingPath = Join-Path $env:TEMP 'ColorScripts-Enhanced-packages'
New-Item -ItemType Directory -Path $stagingPath -Force | Out-Null
Register-PSRepository `
-Name LocalModuleStaging `
-SourceLocation $stagingPath `
-PublishLocation $stagingPath `
-InstallationPolicy Trusted
try {
Publish-Module `
-Path ./ColorScripts-Enhanced `
-Repository LocalModuleStaging `
-NuGetApiKey LocalRepositoryKey
}
finally {
Unregister-PSRepository -Name LocalModuleStaging -ErrorAction SilentlyContinue
}
$package = Get-ChildItem -LiteralPath $stagingPath -Filter '*.nupkg' |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
pwsh -NoProfile -File ./scripts/Update-NuGetPackageMetadata.ps1 `
-PackagePath $package.FullName
Push the normalized package only after inspecting it:
dotnet nuget push $package.FullName `
--api-key $env:PSGALLERYAPIKEY `
--source https://www.powershellgallery.com/api/v2/package `
--skip-duplicate
# Optional second destination for a manual emergency publish. The automated
# workflow obtains its NuGet.org credential through trusted publishing instead.
dotnet nuget push $package.FullName `
--api-key $env:NUGET_API_KEY `
--source https://api.nuget.org/v3/index.json `
--skip-duplicate
Avoid converting a secure string back to plaintext in managed memory merely to pass an API key. Prefer a short-lived environment variable supplied by the local secret manager or CI environment.
Release Checklist
- The worktree contains only intentional release changes.
ModuleVersionis the version being released.npm run verify,npm run test,npm run lint, andnpm run release:verifypass.- Generated help, documentation counts, changelog, and release notes are current.
- A
v<ModuleVersion>tag does not already exist for another commit. - The repository secret uses the exact name
PSGALLERYAPIKEY. - The NuGet.org trusted-publishing policy matches the owner, repository, workflow file, and blank Environment value documented above.
- The GitHub release contains the normalized
.nupkgasset. - The new version is visible in each selected public gallery.
Post-Publish Verification
Find-Module -Name ColorScripts-Enhanced -Repository PSGallery |
Select-Object Name, Version, PublishedDate
$testInstallRoot = Join-Path $env:TEMP 'ColorScripts-Enhanced-install-test'
Save-Module -Name ColorScripts-Enhanced -Repository PSGallery -Path $testInstallRoot
Use an isolated PowerShell process or module path for installation checks so an already imported development checkout cannot mask a packaging problem.
Troubleshooting
- Version rejected: confirm the value parses as
[version]. Four-part date versions such as2026.7.20.2250are valid. - Release tag mismatch: the tag without its leading
vmust exactly equal the built manifest version and resolve to the published commit. - Duplicate package: increment
ModuleVersion; gallery versions cannot be replaced. - PowerShell Gallery publish step skipped: verify the exact
PSGALLERYAPIKEYsecret name and that it is visible to the invoking workflow. - NuGet trusted publishing rejected: verify
id-token: write, thetyppiprofile name, and every trusted-publishing policy field. Environment must remain blank unless the job declares the same GitHub environment name. - Package metadata missing: run
scripts/Update-NuGetPackageMetadata.ps1against the staged package before pushing. - Release notes mismatch: fetch tags and run
npm run release:verify; release notes use git-cliff’s--currentrange.