Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fedb9fd
BREAKING: remove version calculation; publish pre-stamped artifact an…
MariusStorhaug May 17, 2026
3ca4e18
Address Copilot review: validate version format, gate cleanup on stab…
MariusStorhaug May 25, 2026
239a98d
Fix CI failures: remove Test-ModuleManifest, fix lint warnings and ta…
MariusStorhaug May 26, 2026
80d44ea
Use utf8NoBOM encoding when writing to GITHUB_ENV
MariusStorhaug May 26, 2026
23ec483
fix: address review feedback on publish.ps1 and action.yml
MariusStorhaug May 26, 2026
f7417b6
chore: remove breaking change notes regarding version calculation fro…
MariusStorhaug May 26, 2026
a3a69a1
docs: update README.md to remove deprecated features and clarify usage
MariusStorhaug May 26, 2026
a7332bd
fix: add Test-ModuleManifest validation and prerelease regex guard
MariusStorhaug May 27, 2026
06c7bfb
fix: filter cleanup on isPrerelease to avoid deleting stable releases
MariusStorhaug May 27, 2026
db7d200
fix: make Test-ModuleManifest non-terminating for missing RequiredMod…
MariusStorhaug May 27, 2026
6f5b20f
fix: use Get-Content -Raw for JSON event parsing and fix long line
MariusStorhaug May 27, 2026
43a6f99
fix: add UTF-8 BOM to publish.ps1 for PSUseBOMForUnicodeEncodedFile
MariusStorhaug May 27, 2026
09152da
test: add version validation step to prove artifact is not stamped
MariusStorhaug May 27, 2026
3d50e52
test: use pre-stamped version 1.2.3 in test artifact
MariusStorhaug May 27, 2026
40ed488
test: add prerelease and unstamped version test cases
MariusStorhaug May 27, 2026
ff56835
fix: address unresolved PR review comments
MariusStorhaug May 27, 2026
82d6969
fix: correct ModulePath in prerelease/unstamped tests, add placeholde…
MariusStorhaug May 27, 2026
6b6ab0c
fix: unstamped fixture should have no ModuleVersion key, update valid…
MariusStorhaug May 27, 2026
932e828
fix: simplify prerelease fixture manifest to remove non-existent file…
MariusStorhaug May 27, 2026
042f31e
fix: address copilot review - path normalization, fixture manifests, …
MariusStorhaug May 27, 2026
464fbbd
fix: use named parameters for Join-Path (PSAvoidUsingPositionalParame…
MariusStorhaug May 27, 2026
012b38c
fix: use unique artifact names in test workflow and address review co…
MariusStorhaug May 27, 2026
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
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
# Publish-PSModule

This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule).

It publishes a **pre-versioned** PowerShell module artifact to the PowerShell Gallery and creates a matching GitHub
Release. The compressed module is also uploaded as a release asset so the GitHub Release page exposes the exact bytes
that were tested and pushed to the Gallery.

## Breaking change — v3.0.0

`Publish-PSModule` no longer calculates the next version or mutates the module manifest. The artifact passed in must
already contain the final `ModuleVersion` (and `Prerelease` tag, if any).

The following inputs were **removed**:

- `AutoPatching`
- `IncrementalPrerelease`
- `DatePrereleaseFormat`
- `VersionPrefix`
- `MajorLabels`, `MinorLabels`, `PatchLabels`, `IgnoreLabels`
- `ReleaseType`

To migrate, run [`PSModule/Resolve-PSModuleVersion`](https://github.com/PSModule/Resolve-PSModuleVersion) to compute
the version and pass it to [`PSModule/Build-PSModule`](https://github.com/PSModule/Build-PSModule) so the artifact is
stamped before it is tested. The publish action then ships that artifact without any post-build manipulation.

This makes the tested artifact identical to the published artifact (see
[PSModule/Process-PSModule#326](https://github.com/PSModule/Process-PSModule/issues/326)).

## What it does

1. Downloads the `module` artifact uploaded by `Build-PSModule`.
2. Reads `ModuleVersion` and `PrivateData.PSData.Prerelease` from the downloaded manifest.
3. Installs `RequiredModules` declared by the manifest.
4. Publishes the module to the PowerShell Gallery (`Publish-PSResource`).
5. Creates a GitHub Release with the same tag.
6. Zips the module folder and uploads it as a release asset (`<Name>-<Version>.zip`).
7. Optionally cleans up prerelease tags whose name matches the current PR branch.

## Inputs

| Name | Description | Required | Default |
| -------------------------- | ------------------------------------------------------------------------------------------ | -------- | ---------------- |
| `Name` | Name of the module. Defaults to the repository name. | No | Repository name |
| `ModulePath` | Path to the downloaded module folder. | No | `outputs/module` |
| `APIKey` | PowerShell Gallery API key. | Yes | |
| `AutoCleanup` | Delete prerelease tags matching the PR branch after a stable release. | No | `true` |
| `WhatIf` | Log the changes that would be made without publishing, creating, or deleting anything. | No | `false` |
| `WorkingDirectory` | The working directory where the script will run from. | No | `.` |
| `UsePRTitleAsReleaseName` | Use the PR title as the release name (otherwise the version string is used). | No | `false` |
| `UsePRBodyAsReleaseNotes` | Use the PR body as the release notes (otherwise `--generate-notes` is used). | No | `true` |
| `UsePRTitleAsNotesHeading` | Prefix the release notes with the PR title as an H1 heading linking to the PR. | No | `true` |
Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated
68 changes: 9 additions & 59 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
name: Publish-PSModule
description: Publish a PowerShell module to the PowerShell Gallery.
description: Publish a pre-versioned PowerShell module artifact to the PowerShell Gallery and GitHub Releases.
author: PSModule

# BREAKING CHANGE (v3.0.0):
# This action no longer calculates the next version or mutates the module manifest. The artifact passed in
# must already contain the final ModuleVersion (and Prerelease tag, if any). Use PSModule/Resolve-PSModuleVersion
# to compute the version and PSModule/Build-PSModule (v5+) to stamp it before testing. The version-calculation
# inputs (AutoPatching, IncrementalPrerelease, DatePrereleaseFormat, VersionPrefix, *Labels, ReleaseType, IgnoreLabels)
# have been removed.
Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated

inputs:
Name:
description: Name of the module to publish.
Expand All @@ -17,42 +24,6 @@ inputs:
description: Control whether to automatically delete the prerelease tags after the stable release is created.
required: false
default: 'true'
AutoPatching:
description: Control whether to automatically handle patches. If disabled, the action will only create a patch release if the pull request has a 'patch' label.
required: false
default: 'true'
IncrementalPrerelease:
description: Control whether to automatically increment the prerelease number. If disabled, the action will ensure only one prerelease exists for a given branch.
required: false
default: 'true'
DatePrereleaseFormat:
description: If specified, uses a date based prerelease scheme. The format should be a valid .NET format string like 'yyyyMMddHHmm'.
required: false
default: ''
VersionPrefix:
description: The prefix to use for the version number.
required: false
default: v
MajorLabels:
description: A comma separated list of labels that trigger a major release.
required: false
default: major, breaking
MinorLabels:
description: A comma separated list of labels that trigger a minor release.
required: false
default: minor, feature
PatchLabels:
description: A comma separated list of labels that trigger a patch release.
required: false
default: patch, fix
IgnoreLabels:
description: A comma separated list of labels that do not trigger a release.
required: false
default: NoRelease
ReleaseType:
description: The type of release to create. Values are 'Release' (stable), 'Prerelease', or 'None'.
required: false
default: Release
WhatIf:
description: If specified, the action will only log the changes it would make, but will not actually create or delete any releases or tags.
required: false
Expand Down Expand Up @@ -85,34 +56,13 @@ runs:
run: |
Install-PSResource -Name Microsoft.PowerShell.PSResourceGet -Repository PSGallery -TrustRepository

- name: Initialize Publish Context
id: init
shell: pwsh
working-directory: ${{ inputs.WorkingDirectory }}
env:
PSMODULE_PUBLISH_PSMODULE_INPUT_Name: ${{ inputs.Name }}
PSMODULE_PUBLISH_PSMODULE_INPUT_AutoCleanup: ${{ inputs.AutoCleanup }}
PSMODULE_PUBLISH_PSMODULE_INPUT_AutoPatching: ${{ inputs.AutoPatching }}
PSMODULE_PUBLISH_PSMODULE_INPUT_DatePrereleaseFormat: ${{ inputs.DatePrereleaseFormat }}
PSMODULE_PUBLISH_PSMODULE_INPUT_IgnoreLabels: ${{ inputs.IgnoreLabels }}
PSMODULE_PUBLISH_PSMODULE_INPUT_ReleaseType: ${{ inputs.ReleaseType }}
PSMODULE_PUBLISH_PSMODULE_INPUT_IncrementalPrerelease: ${{ inputs.IncrementalPrerelease }}
PSMODULE_PUBLISH_PSMODULE_INPUT_MajorLabels: ${{ inputs.MajorLabels }}
PSMODULE_PUBLISH_PSMODULE_INPUT_MinorLabels: ${{ inputs.MinorLabels }}
PSMODULE_PUBLISH_PSMODULE_INPUT_PatchLabels: ${{ inputs.PatchLabels }}
PSMODULE_PUBLISH_PSMODULE_INPUT_VersionPrefix: ${{ inputs.VersionPrefix }}
PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf: ${{ inputs.WhatIf }}
run: ${{ github.action_path }}/src/init.ps1

- name: Download module artifact
if: env.PUBLISH_CONTEXT_ShouldPublish == 'true' || inputs.WhatIf == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: module
path: ${{ inputs.ModulePath }}

- name: Publish Module
if: env.PUBLISH_CONTEXT_ShouldPublish == 'true' || inputs.WhatIf == 'true'
shell: pwsh
working-directory: ${{ inputs.WorkingDirectory }}
env:
Expand All @@ -126,7 +76,7 @@ runs:
run: ${{ github.action_path }}/src/publish.ps1
Comment thread
MariusStorhaug marked this conversation as resolved.

- name: Cleanup Prereleases
if: env.PUBLISH_CONTEXT_ShouldCleanup == 'true' || inputs.WhatIf == 'true'
if: inputs.AutoCleanup == 'true' || inputs.WhatIf == 'true'
Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated
shell: pwsh
working-directory: ${{ inputs.WorkingDirectory }}
env:
Expand Down
46 changes: 34 additions & 12 deletions src/cleanup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,40 @@ $PSStyle.OutputRendering = 'Ansi'

Import-Module -Name 'Helpers' -Force

$prereleaseName = $env:PUBLISH_CONTEXT_PrereleaseName
$prereleaseTagsToCleanup = $env:PUBLISH_CONTEXT_PrereleaseTagsToCleanup
$whatIf = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf -eq 'true'
#region Load inputs
LogGroup 'Load inputs' {
$whatIf = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf -eq 'true'

if ([string]::IsNullOrWhiteSpace($prereleaseName)) {
Write-Error 'PUBLISH_CONTEXT_PrereleaseName is not set. Run init.ps1 first.'
exit 1
}
$githubEventJson = Get-Content $env:GITHUB_EVENT_PATH
Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated
$githubEvent = $githubEventJson | ConvertFrom-Json
$pull_request = $githubEvent.pull_request
if (-not $pull_request) {
throw 'GitHub event does not contain pull_request data. This script must be run from a pull_request event.'
}
$prHeadRef = $pull_request.head.ref
$prereleaseName = $prHeadRef -replace '[^a-zA-Z0-9]'

LogGroup "Cleanup prereleases for [$prereleaseName]" {
if ([string]::IsNullOrWhiteSpace($prereleaseTagsToCleanup)) {
Write-Host "No prereleases found to cleanup for [$prereleaseName]."
if ([string]::IsNullOrWhiteSpace($prereleaseName)) {
Write-Host "No prerelease tag derivable from PR head ref [$prHeadRef]. Nothing to cleanup."
return
}
Comment thread
MariusStorhaug marked this conversation as resolved.

$tagsToDelete = $prereleaseTagsToCleanup -split ',' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
Write-Host "PR head ref: [$prHeadRef]"
Write-Host "Prerelease name: [$prereleaseName]"
Write-Host "WhatIf: [$whatIf]"
}
#endregion Load inputs

#region Find prereleases to cleanup
LogGroup "Find prereleases to cleanup for [$prereleaseName]" {
$releases = gh release list --json 'createdAt,isDraft,isLatest,isPrerelease,name,publishedAt,tagName' | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) {
Write-Error 'Failed to list releases for the repository.'
exit $LASTEXITCODE
}

Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated
$prereleasesToCleanup = $releases | Where-Object { $_.tagName -like "*$prereleaseName*" }
$tagsToDelete = @($prereleasesToCleanup | ForEach-Object { $_.tagName } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
Comment thread
MariusStorhaug marked this conversation as resolved.
Outdated

if ($tagsToDelete.Count -eq 0) {
Write-Host "No prereleases found to cleanup for [$prereleaseName]."
Expand All @@ -29,8 +47,11 @@ LogGroup "Cleanup prereleases for [$prereleaseName]" {

Write-Host "Found $($tagsToDelete.Count) prereleases to cleanup:"
$tagsToDelete | ForEach-Object { Write-Host " - $_" }
Write-Host ''
}
#endregion Find prereleases to cleanup

#region Delete prereleases
LogGroup "Delete prereleases for [$prereleaseName]" {
foreach ($tag in $tagsToDelete) {
Write-Host "Deleting prerelease: [$tag]"
if ($whatIf) {
Expand All @@ -47,3 +68,4 @@ LogGroup "Cleanup prereleases for [$prereleaseName]" {

Write-Host "::notice::Cleaned up $($tagsToDelete.Count) prerelease(s) for [$prereleaseName]."
}
#endregion Delete prereleases
Loading
Loading