Skip to content

Extract shared versioning and release logic from Publish-PSModule and Release-GHRepository #326

@MariusStorhaug

Description

Publish-PSModule and Release-GHRepository independently implement the same core logic for semantic versioning, release type determination, GitHub release creation, prerelease cleanup, and PR commenting. A comparison of the two actions reveals 347+ lines of near-identical code.

Request

Current experience

Both actions independently implement:

Responsibility Release-GHRepository/src/main.ps1 Publish-PSModule/src/init.ps1 + publish.ps1 + cleanup.ps1
Version calculation Lines 178–217 init.ps1 lines 209–354
Release type determination Lines 128–150 init.ps1 lines 124–186
Label analysis Lines 139–141 init.ps1 lines 145–172
GitHub release creation Lines 219–285 publish.ps1 lines 140–216
Prerelease cleanup Lines 367–388 cleanup.ps1 lines 17–49
PR commenting Lines 278–284 publish.ps1 lines 131–137, 209–214

The version calculation logic is nearly character-for-character identical:

# Release-GHRepository (lines 178-190)
LogGroup 'Calculate new version' {
    $latestVersion = New-PSSemVer -Version $latestVersion
    $newVersion = New-PSSemVer -Version $latestVersion
    $newVersion.Prefix = $versionPrefix
    if ($majorRelease) {
        Write-Output 'Incrementing major version.'
        $newVersion.BumpMajor()
    } elseif ($minorRelease) { ... }

# Publish-PSModule/init.ps1 (lines 269-285)
LogGroup 'Calculate new version' {
    $newVersion = New-PSSemVer -Version $latestVersion
    $newVersion.Prefix = $versionPrefix
    if ($majorRelease) {
        Write-Host 'Incrementing major version.'
        $newVersion.BumpMajor()
    } elseif ($minorRelease) { ... }

A bug fix or feature added to one action (e.g., date-based prerelease formatting, incremental prerelease numbering) must be manually replicated in the other. Divergence has already occurred — Publish-PSModule uses Write-Host where Release-GHRepository uses Write-Output.

Desired experience

Shared versioning, release, and cleanup logic lives in a single location. Both actions consume the shared logic rather than reimplementing it.

Acceptance criteria

  • Version calculation logic exists in exactly one place
  • Release type determination (from PR labels) exists in exactly one place
  • GitHub release creation logic exists in exactly one place
  • Prerelease cleanup logic exists in exactly one place
  • Both Publish-PSModule and Release-GHRepository produce identical results for the same inputs
  • A bug fix to shared logic automatically applies to both actions

Note

This violates the DRY principle and Single Responsibility Principle. It is closely related to #210 (separate release from publish) and #176 (move version generation to build).


Technical decisions

Approach: Extract the shared logic into a dedicated PowerShell module (e.g., PSModuleRelease or extend the existing Helpers module) that both actions import. The module provides functions such as:

  • Get-ReleaseType — determines release/prerelease/none from PR labels and merge status
  • Get-NextVersion — calculates the next semantic version given the current version and release type
  • New-GitHubRelease — creates a GitHub release with configurable notes
  • Remove-PrereleaseVersions — cleans up old prerelease tags

Alternative considered: Making Publish-PSModule depend on Release-GHRepository as a sub-action. Rejected because Publish-PSModule has the additional responsibility of PowerShell Gallery publishing, and tight coupling between two composite actions creates a fragile dependency chain.

Alternative considered: Moving all shared logic into the Helpers module installed by Install-PSModuleHelpers. This is feasible but would increase the Helpers module scope significantly. A dedicated module with a clear boundary is preferred.

Relationship to #210: This issue provides the architectural foundation that makes #210 (separate release creation from publishing) straightforward to implement. Once shared logic is extracted, Publish-PSModule becomes: "publish to Gallery" + "call shared release logic."


Implementation plan

Shared module

  • Create a shared PowerShell module (or script library) containing extracted functions
  • Implement Get-ReleaseType from the label-analysis logic in both actions
  • Implement Get-NextVersion from the version-calculation logic in both actions
  • Implement New-GitHubRelease from the release-creation logic in both actions
  • Implement Remove-PrereleaseVersions from the cleanup logic in both actions
  • Add unit tests for each extracted function

Release-GHRepository changes

  • Replace inline versioning, release, and cleanup logic with calls to the shared module
  • Verify all existing inputs and outputs are preserved

Publish-PSModule changes

  • Replace inline versioning, release, and cleanup logic with calls to the shared module
  • Retain PowerShell Gallery-specific publishing logic (this is Publish-PSModule's unique responsibility)
  • Verify all existing inputs and outputs are preserved

Testing

  • Verify Release-GHRepository produces identical releases as before
  • Verify Publish-PSModule produces identical releases and publishes as before
  • Verify prerelease cleanup works identically in both actions

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions