From 12fbf5483c179eb831bb144feb4a6afdd65fdb2f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 22 May 2026 21:18:12 +0200 Subject: [PATCH 01/13] Rename Get-Settings workflow to Plan and add Resolve-PSModuleVersion step --- .github/workflows/Plan.yml | 119 +++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/Plan.yml diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml new file mode 100644 index 00000000..a01754ef --- /dev/null +++ b/.github/workflows/Plan.yml @@ -0,0 +1,119 @@ +name: Plan + +# The Plan job is the single decision point for the workflow. +# It runs two steps: +# 1. Get-PSModuleSettings - loads and resolves configuration +# 2. Resolve-PSModuleVersion - calculates the next version from settings + PR labels +# All downstream jobs consume the Settings JSON and the resolved version outputs from this job. + +on: + workflow_call: + inputs: + SettingsPath: + type: string + description: The path to the settings file. + required: false + Debug: + type: boolean + description: Enable debug output. + required: false + default: false + Verbose: + type: boolean + description: Enable verbose output. + required: false + default: false + Version: + type: string + description: Specifies the version of the GitHub module to be installed. The value must be an exact version. + required: false + default: '' + Prerelease: + type: boolean + description: Whether to use a prerelease version of the 'GitHub' module. + required: false + default: false + WorkingDirectory: + type: string + description: The path to the root of the repo. + required: false + default: '.' + ImportantFilePatterns: + type: string + description: | + Newline-separated list of regex patterns that identify important files. + Changes matching these patterns trigger build, test, and publish stages. + When set, fully replaces the defaults (^src/ and ^README\.md$). + required: false + default: | + ^src/ + ^README\.md$ + + outputs: + Settings: + description: The complete settings object including test suites. + value: ${{ jobs.Plan.outputs.Settings }} + ModuleVersion: + description: The Major.Minor.Patch part of the next version. + value: ${{ jobs.Plan.outputs.ModuleVersion }} + ModulePrerelease: + description: The prerelease tag, empty string when not a prerelease. + value: ${{ jobs.Plan.outputs.ModulePrerelease }} + ModuleFullVersion: + description: The full version string including prefix and prerelease tag (for example v1.4.0). + value: ${{ jobs.Plan.outputs.ModuleFullVersion }} + ReleaseType: + description: The release type - Release, Prerelease, or None. + value: ${{ jobs.Plan.outputs.ReleaseType }} + CreateRelease: + description: 'true when a release/prerelease should actually be created.' + value: ${{ jobs.Plan.outputs.CreateRelease }} + +permissions: + contents: read # to checkout the repo + pull-requests: write # to add labels / comments to PRs + +jobs: + Plan: + name: Plan + runs-on: ubuntu-latest + outputs: + Settings: ${{ steps.Get-Settings.outputs.Settings }} + ModuleVersion: ${{ steps.Resolve-Version.outputs.Version }} + ModulePrerelease: ${{ steps.Resolve-Version.outputs.Prerelease }} + ModuleFullVersion: ${{ steps.Resolve-Version.outputs.FullVersion }} + ReleaseType: ${{ steps.Resolve-Version.outputs.ReleaseType }} + CreateRelease: ${{ steps.Resolve-Version.outputs.CreateRelease }} + steps: + - name: Checkout Code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Get-Settings + uses: PSModule/Get-PSModuleSettings@1e3d156786c56e6fbd839a1ba5ab21ff8858090e # v1.5.0 + id: Get-Settings + with: + SettingsPath: ${{ inputs.SettingsPath }} + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} + ImportantFilePatterns: ${{ inputs.ImportantFilePatterns }} + + - name: Resolve-Version + # Resolve only when the workflow is going to create a release/prerelease. + if: fromJson(steps.Get-Settings.outputs.Settings).Publish.Module.ReleaseType != 'None' + uses: PSModule/Resolve-PSModuleVersion@main + id: Resolve-Version + env: + GH_TOKEN: ${{ github.token }} + with: + Settings: ${{ steps.Get-Settings.outputs.Settings }} + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} From f62e29f9704bb169520cd3288a78fc926bbeb2c1 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 22 May 2026 21:18:19 +0200 Subject: [PATCH 02/13] Rewire Process-PSModule workflow jobs to use the Plan job --- .github/workflows/Get-Settings.yml | 78 -------------------------- .github/workflows/workflow.yml | 90 +++++++++++++++--------------- 2 files changed, 46 insertions(+), 122 deletions(-) delete mode 100644 .github/workflows/Get-Settings.yml diff --git a/.github/workflows/Get-Settings.yml b/.github/workflows/Get-Settings.yml deleted file mode 100644 index 860fb436..00000000 --- a/.github/workflows/Get-Settings.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Get-Settings - -on: - workflow_call: - inputs: - SettingsPath: - type: string - description: The path to the settings file. - required: false - Debug: - type: boolean - description: Enable debug output. - required: false - default: false - Verbose: - type: boolean - description: Enable verbose output. - required: false - default: false - Version: - type: string - description: Specifies the version of the GitHub module to be installed. The value must be an exact version. - required: false - default: '' - Prerelease: - type: boolean - description: Whether to use a prerelease version of the 'GitHub' module. - required: false - default: false - WorkingDirectory: - type: string - description: The path to the root of the repo. - required: false - default: '.' - ImportantFilePatterns: - type: string - description: | - Newline-separated list of regex patterns that identify important files. - Changes matching these patterns trigger build, test, and publish stages. - When set, fully replaces the defaults (^src/ and ^README\.md$). - required: false - default: | - ^src/ - ^README\.md$ - - outputs: - Settings: - description: The complete settings object including test suites - value: ${{ jobs.Get-Settings.outputs.Settings }} - -permissions: - contents: read # to checkout the repo - pull-requests: write # to add labels to PRs - -jobs: - Get-Settings: - name: Get-Settings - runs-on: ubuntu-latest - outputs: - Settings: ${{ steps.Get-Settings.outputs.Settings }} - steps: - - name: Checkout Code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Get-Settings - uses: PSModule/Get-PSModuleSettings@1e3d156786c56e6fbd839a1ba5ab21ff8858090e # v1.5.0 - id: Get-Settings - with: - SettingsPath: ${{ inputs.SettingsPath }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - ImportantFilePatterns: ${{ inputs.ImportantFilePatterns }} diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9a4bc6af..9e65a67f 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -82,8 +82,8 @@ jobs: # - ✅ Merged PR - Always runs to load configuration # - ✅ Abandoned PR - Always runs to load configuration # - ✅ Manual run - Always runs to load configuration - Get-Settings: - uses: ./.github/workflows/Get-Settings.yml + Plan: + uses: ./.github/workflows/Plan.yml with: SettingsPath: ${{ inputs.SettingsPath }} Debug: ${{ inputs.Debug }} @@ -99,12 +99,12 @@ jobs: # - ❌ Abandoned PR - No need to lint abandoned changes # - ❌ Manual run - Only runs for PR events Lint-Repository: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.LintRepository + if: fromJson(needs.Plan.outputs.Settings).Run.LintRepository needs: - - Get-Settings + - Plan uses: ./.github/workflows/Lint-Repository.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Builds module for testing @@ -112,12 +112,14 @@ jobs: # - ❌ Abandoned PR - Skips building abandoned changes # - ✅ Manual run - Builds module when manually triggered Build-Module: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.BuildModule + if: fromJson(needs.Plan.outputs.Settings).Run.BuildModule uses: ./.github/workflows/Build-Module.yml needs: - - Get-Settings + - Plan with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} + ModuleVersion: ${{ needs.Plan.outputs.ModuleVersion }} + ModulePrerelease: ${{ needs.Plan.outputs.ModulePrerelease }} # Runs on: # - ✅ Open/Updated PR - Tests source code changes @@ -125,12 +127,12 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests source code when manually triggered Test-SourceCode: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestSourceCode + if: fromJson(needs.Plan.outputs.Settings).Run.TestSourceCode needs: - - Get-Settings + - Plan uses: ./.github/workflows/Test-SourceCode.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Lints source code changes @@ -138,12 +140,12 @@ jobs: # - ❌ Abandoned PR - Skips linting abandoned changes # - ✅ Manual run - Lints source code when manually triggered Lint-SourceCode: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.LintSourceCode + if: fromJson(needs.Plan.outputs.Settings).Run.LintSourceCode needs: - - Get-Settings + - Plan uses: ./.github/workflows/Lint-SourceCode.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Tests built module @@ -151,13 +153,13 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests built module when manually triggered Test-Module: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestModule && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.TestModule && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - - Get-Settings + - Plan uses: ./.github/workflows/Test-Module.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Runs setup scripts before local module tests @@ -165,7 +167,7 @@ jobs: # - ❌ Abandoned PR - Skips setup for abandoned changes # - ✅ Manual run - Runs setup scripts when manually triggered BeforeAll-ModuleLocal: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.BeforeAllModuleLocal && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.BeforeAllModuleLocal && needs.Build-Module.result == 'success' && !cancelled() uses: ./.github/workflows/BeforeAll-ModuleLocal.yml secrets: TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} @@ -177,9 +179,9 @@ jobs: TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} needs: - Build-Module - - Get-Settings + - Plan with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Tests module in local environment @@ -187,10 +189,10 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests module in local environment when manually triggered Test-ModuleLocal: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestModuleLocal && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.TestModuleLocal && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - - Get-Settings + - Plan - BeforeAll-ModuleLocal uses: ./.github/workflows/Test-ModuleLocal.yml secrets: @@ -202,7 +204,7 @@ jobs: TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Runs teardown scripts after local module tests @@ -210,7 +212,7 @@ jobs: # - ✅ Abandoned PR - Runs teardown if tests were started (cleanup) # - ✅ Manual run - Runs teardown scripts after local module tests AfterAll-ModuleLocal: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.AfterAllModuleLocal && needs.Test-ModuleLocal.result != 'skipped' && always() + if: fromJson(needs.Plan.outputs.Settings).Run.AfterAllModuleLocal && needs.Test-ModuleLocal.result != 'skipped' && always() uses: ./.github/workflows/AfterAll-ModuleLocal.yml secrets: TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} @@ -221,10 +223,10 @@ jobs: TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} needs: - - Get-Settings + - Plan - Test-ModuleLocal with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Collects and reports test results @@ -232,16 +234,16 @@ jobs: # - ❌ Abandoned PR - Skips collecting results for abandoned changes # - ✅ Manual run - Collects and reports test results when manually triggered Get-TestResults: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.GetTestResults && needs.Get-Settings.result == 'success' && always() && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.GetTestResults && needs.Plan.result == 'success' && always() && !cancelled() needs: - - Get-Settings + - Plan - Test-SourceCode - Lint-SourceCode - Test-Module - Test-ModuleLocal uses: ./.github/workflows/Get-TestResults.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Calculates and reports code coverage @@ -249,14 +251,14 @@ jobs: # - ❌ Abandoned PR - Skips coverage for abandoned changes # - ✅ Manual run - Calculates and reports code coverage when manually triggered Get-CodeCoverage: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.GetCodeCoverage && needs.Get-Settings.result == 'success' && always() && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.GetCodeCoverage && needs.Plan.result == 'success' && always() && !cancelled() needs: - - Get-Settings + - Plan - Test-Module - Test-ModuleLocal uses: ./.github/workflows/Get-CodeCoverage.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Only with prerelease label: publishes prerelease version @@ -264,17 +266,17 @@ jobs: # - ✅ Abandoned PR - Cleans up prereleases for the abandoned branch (no version published) # - ❌ Manual run - Only runs for PR events Publish-Module: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.PublishModule && needs.Get-Settings.result == 'success' && !cancelled() && (needs.Get-TestResults.result == 'success' || needs.Get-TestResults.result == 'skipped') && (needs.Get-CodeCoverage.result == 'success' || needs.Get-CodeCoverage.result == 'skipped') && (needs.Build-Site.result == 'success' || needs.Build-Site.result == 'skipped') + if: fromJson(needs.Plan.outputs.Settings).Run.PublishModule && needs.Plan.result == 'success' && !cancelled() && (needs.Get-TestResults.result == 'success' || needs.Get-TestResults.result == 'skipped') && (needs.Get-CodeCoverage.result == 'success' || needs.Get-CodeCoverage.result == 'skipped') && (needs.Build-Site.result == 'success' || needs.Build-Site.result == 'skipped') uses: ./.github/workflows/Publish-Module.yml secrets: APIKey: ${{ secrets.APIKey }} needs: - - Get-Settings + - Plan - Get-TestResults - Get-CodeCoverage - Build-Site with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Builds documentation for review @@ -282,13 +284,13 @@ jobs: # - ❌ Abandoned PR - Skips building docs for abandoned changes # - ✅ Manual run - Builds documentation when manually triggered Build-Docs: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.BuildDocs + if: fromJson(needs.Plan.outputs.Settings).Run.BuildDocs needs: - - Get-Settings + - Plan - Build-Module uses: ./.github/workflows/Build-Docs.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Builds site for preview @@ -296,13 +298,13 @@ jobs: # - ❌ Abandoned PR - Skips building site for abandoned changes # - ✅ Manual run - Builds site when manually triggered Build-Site: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.BuildSite + if: fromJson(needs.Plan.outputs.Settings).Run.BuildSite needs: - - Get-Settings + - Plan - Build-Docs uses: ./.github/workflows/Build-Site.yml with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} # Runs on: # - ❌ Open/Updated PR - Site not published for PRs in progress @@ -310,12 +312,12 @@ jobs: # - ❌ Abandoned PR - Site not published for abandoned changes # - ❌ Manual run - Only publishes on merged PRs to default branch Publish-Site: - if: fromJson(needs.Get-Settings.outputs.Settings).Run.PublishSite && needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Run.PublishSite && needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success' && !cancelled() uses: ./.github/workflows/Publish-Site.yml needs: - - Get-Settings + - Plan - Get-TestResults - Get-CodeCoverage - Build-Site with: - Settings: ${{ needs.Get-Settings.outputs.Settings }} + Settings: ${{ needs.Plan.outputs.Settings }} From 821cd03063b754a1f09814af4e1ed6be8da14830 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 22 May 2026 21:18:24 +0200 Subject: [PATCH 03/13] Forward ModuleVersion and ModulePrerelease into Build-PSModule --- .github/workflows/Build-Module.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index a49da7de..fa59f96a 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -12,6 +12,16 @@ on: description: Name of the artifact to upload. required: false default: module + ModuleVersion: + type: string + description: The Major.Minor.Patch version to stamp into the built manifest. Empty falls back to '999.0.0'. + required: false + default: '' + ModulePrerelease: + type: string + description: Optional prerelease tag to stamp into the built manifest. + required: false + default: '' permissions: contents: read # to checkout the repository @@ -30,8 +40,10 @@ jobs: fetch-depth: 0 - name: Build module - uses: PSModule/Build-PSModule@345728124d201f371a8b0f1aacb98f89000a06dc # v4.0.14 + uses: PSModule/Build-PSModule@main with: Name: ${{ fromJson(inputs.Settings).Name }} + Version: ${{ inputs.ModuleVersion }} + Prerelease: ${{ inputs.ModulePrerelease }} ArtifactName: ${{ inputs.ArtifactName }} WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} From 94616044a7c952340c976cb2e59de3f8c2d64aed Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 22 May 2026 21:18:24 +0200 Subject: [PATCH 04/13] Drop version-calculation inputs from Publish-Module --- .github/workflows/Publish-Module.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/Publish-Module.yml b/.github/workflows/Publish-Module.yml index 6a67831f..2edc731a 100644 --- a/.github/workflows/Publish-Module.yml +++ b/.github/workflows/Publish-Module.yml @@ -13,7 +13,7 @@ on: required: true permissions: - contents: write # to checkout the repo and create releases + contents: write # to checkout the repo, create releases, and upload release artifacts pull-requests: write # to comment on PRs jobs: @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 - name: Publish module - uses: PSModule/Publish-PSModule@8917aed588dae1bd1aa2873b1caec1c50c20d255 # v2.2.4 + uses: PSModule/Publish-PSModule@main env: GH_TOKEN: ${{ github.token }} with: @@ -39,15 +39,6 @@ jobs: APIKey: ${{ secrets.APIKEY }} WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }} AutoCleanup: ${{ fromJson(inputs.Settings).Publish.Module.AutoCleanup }} - AutoPatching: ${{ fromJson(inputs.Settings).Publish.Module.AutoPatching }} - DatePrereleaseFormat: ${{ fromJson(inputs.Settings).Publish.Module.DatePrereleaseFormat }} - IgnoreLabels: ${{ fromJson(inputs.Settings).Publish.Module.IgnoreLabels }} - ReleaseType: ${{ fromJson(inputs.Settings).Publish.Module.ReleaseType }} - IncrementalPrerelease: ${{ fromJson(inputs.Settings).Publish.Module.IncrementalPrerelease }} - MajorLabels: ${{ fromJson(inputs.Settings).Publish.Module.MajorLabels }} - MinorLabels: ${{ fromJson(inputs.Settings).Publish.Module.MinorLabels }} - PatchLabels: ${{ fromJson(inputs.Settings).Publish.Module.PatchLabels }} - VersionPrefix: ${{ fromJson(inputs.Settings).Publish.Module.VersionPrefix }} UsePRTitleAsReleaseName: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsReleaseName }} UsePRBodyAsReleaseNotes: ${{ fromJson(inputs.Settings).Publish.Module.UsePRBodyAsReleaseNotes }} UsePRTitleAsNotesHeading: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsNotesHeading }} From 4072d6ef4949797147feb9f177195c42d7d88bfd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 22 May 2026 21:18:24 +0200 Subject: [PATCH 05/13] Document the Plan job and the release-asset upload guarantee --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c74caadb..d79e4f30 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ Depending on the labels in the pull requests, the [workflow will result in diffe - [How to get started](#how-to-get-started) - [How it works](#how-it-works) - [Workflow overview](#workflow-overview) - - [Get-Settings](#get-settings) + - [Plan](#plan) - [Lint-Repository](#lint-repository) - - [Get settings](#get-settings-1) + - [Plan job](#plan-job) - [Build module](#build-module) - [Test source code](#test-source-code) - [Lint source code](#lint-source-code) @@ -106,20 +106,28 @@ Depending on the labels in the pull requests, the [workflow will result in diffe - [Colocation of concerns](#colocation-of-concerns) - [Compatibility](#compatibility) -### Get-Settings +### Plan -[workflow](./.github/workflows/Get-Settings.yml) +[workflow](./.github/workflows/Plan.yml) + +The Plan job is the single decision point of the workflow. It runs two steps in sequence: + +1. **Get-PSModuleSettings** — loads the settings file (`.github/PSModule.yml`) and emits a fully resolved `Settings` JSON object that every downstream job consumes. +2. **Resolve-PSModuleVersion** — calculates the next module version from the resolved settings and the labels on the current pull request. Emits `ModuleVersion`, `ModulePrerelease`, `ModuleFullVersion`, `ReleaseType`, and `CreateRelease` as job outputs. + +The resolved version is passed into `Build-Module` so the manifest is stamped with the final version **before** the test stages run. The same artifact is then published unchanged by `Publish-Module`, which also uploads the zipped module as a GitHub Release asset. The bytes that are tested are the bytes that ship to the PowerShell Gallery and to GitHub Releases. ### Lint-Repository [workflow](./.github/workflows/Lint-Repository.yml) -### Get settings +### Plan job -[workflow](#get-settings) -- Reads the settings file `github/PSModule.yml` in the module repository to configure the workflow. +[workflow](#plan) +- Reads the settings file `.github/PSModule.yml` in the module repository to configure the workflow. - Gathers context for the process from GitHub and the repo files, configuring what tests to run, if and what kind of release to create, and whether to setup testing infrastructure and what operating systems to run the tests on. +- Calculates the next module version from PR labels and existing releases, then publishes it as job outputs so Build-Module can stamp the manifest before the artifact is tested. ### Build module @@ -317,6 +325,7 @@ The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1 [workflow](./.github/workflows/Publish-Module.yml) - Publishes the module to the PowerShell Gallery. - Creates a release on the GitHub repository. +- Attaches the built module as a `.zip` asset on the GitHub Release so consumers can download the exact bytes that were tested and pushed to the PowerShell Gallery. - **Abandoned PR cleanup**: When a PR is closed without merging (abandoned), the workflow automatically cleans up any prerelease versions and tags that were created for that PR. This ensures that abandoned work doesn't leave orphaned prereleases in the PowerShell Gallery or repository. This behavior is controlled by the `Publish.Module.AutoCleanup` @@ -430,7 +439,7 @@ This table shows when each job runs based on the trigger scenario: | Job | Open/Updated PR | Merged PR | Abandoned PR | Manual Run | | ------------------------- | --------------- | ---------- | ------------ | ---------- | -| **Get-Settings** | ✅ Always | ✅ Always | ✅ Always | ✅ Always | +| **Plan** | ✅ Always | ✅ Always | ✅ Always | ✅ Always | | **Lint-Repository** | ✅ Yes | ❌ No | ❌ No | ❌ No | | **Build-Module** | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes | | **Build-Docs** | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes | @@ -677,7 +686,7 @@ Test: ### Example 2 - Rapid testing -This example ends up running Get-Settings, Build-Module and Test-Module (tests from the module repo) on **ubuntu-latest** only. +This example ends up running Plan, Build-Module and Test-Module (tests from the module repo) on **ubuntu-latest** only. ```yaml Test: From 7d80faad415dc77bd71ee41a9174ad0d5119df16 Mon Sep 17 00:00:00 2001 From: MariusStorhaug Date: Fri, 22 May 2026 21:46:06 +0200 Subject: [PATCH 06/13] Document version resolution algorithm in Plan section --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index d79e4f30..ea7e629a 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,43 @@ The Plan job is the single decision point of the workflow. It runs two steps in The resolved version is passed into `Build-Module` so the manifest is stamped with the final version **before** the test stages run. The same artifact is then published unchanged by `Publish-Module`, which also uploads the zipped module as a GitHub Release asset. The bytes that are tested are the bytes that ship to the PowerShell Gallery and to GitHub Releases. +#### How version resolution works + +The [Resolve-PSModuleVersion](https://github.com/PSModule/Resolve-PSModuleVersion) step reads configuration from `Settings.Publish.Module`: + +| Key | Description | +| --- | ----------- | +| `ReleaseType` | `Release`, `Prerelease`, or `None`. | +| `AutoPatching` | When `true`, a patch bump is applied even without a label. | +| `IncrementalPrerelease` | When `true`, an incrementing counter is appended to prerelease tags. | +| `DatePrereleaseFormat` | Optional .NET DateTime format string for date-based prerelease suffixes. | +| `VersionPrefix` | Tag prefix (typically `v`). | +| `MajorLabels`, `MinorLabels`, `PatchLabels` | Comma-separated PR labels that trigger the corresponding bump. | +| `IgnoreLabels` | Comma-separated PR labels that suppress version creation. | + +Resolution algorithm: + +1. Loads the `pull_request` event payload and collects PR labels. +2. Validates `ReleaseType`; applies `IgnoreLabels` override (suppresses release if matched). +3. Picks the bump type: `MajorLabels` > `MinorLabels` > (`PatchLabels` or `AutoPatching`). +4. Reads the latest version from GitHub Releases (`gh release list`) and the PowerShell Gallery (`Find-PSResource`), + takes the higher of the two as the baseline. +5. Bumps the baseline (major, minor, or patch). +6. For prereleases, appends the sanitized branch name, optionally a `DatePrereleaseFormat` timestamp, and an + incremental counter calculated from existing prereleases on the same baseline + branch. +7. Emits outputs: + +| Output | Description | +| --- | --- | +| `Version` | `Major.Minor.Patch` portion (for example `1.4.0`). | +| `Prerelease` | Prerelease tag, empty when not a prerelease. | +| `FullVersion` | Full string including prefix and prerelease (for example `v1.4.0-mybranch001`). | +| `ReleaseType` | `Release`, `Prerelease`, or `None` when no bump label is found. | +| `CreateRelease` | `true` when a release or prerelease should be created. | + +When `ReleaseType` is `None`, when an `IgnoreLabels` label is present, or when no version bump label is found +(and `AutoPatching` is disabled), `CreateRelease` is `false` and the version outputs are empty strings. + ### Lint-Repository [workflow](./.github/workflows/Lint-Repository.yml) From 5dd0c7ed1df5613c35693eb63a11b0302c136600 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 24 May 2026 11:22:50 +0200 Subject: [PATCH 07/13] Refactor permissions formatting in workflow files for consistency --- .github/workflows/Plan.yml | 4 ++-- .github/workflows/workflow.yml | 8 ++++---- README.md | 13 +++++++++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index a01754ef..4034b394 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -70,8 +70,8 @@ on: value: ${{ jobs.Plan.outputs.CreateRelease }} permissions: - contents: read # to checkout the repo - pull-requests: write # to add labels / comments to PRs + contents: read # to checkout the repo + pull-requests: write # to add labels / comments to PRs jobs: Plan: diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9e65a67f..721c1f1e 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -70,11 +70,11 @@ on: ^README\.md$ permissions: - contents: write # to checkout the repo and create releases on the repo + contents: write # to checkout the repo and create releases on the repo pull-requests: write # to write comments to PRs - statuses: write # to update the status of the workflow from linter - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source + statuses: write # to update the status of the workflow from linter + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source jobs: # Runs on: diff --git a/README.md b/README.md index ea7e629a..7175c8c4 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ When `ReleaseType` is `None`, when an `IgnoreLabels` label is present, or when n ### Plan job [workflow](#plan) + - Reads the settings file `.github/PSModule.yml` in the module repository to configure the workflow. - Gathers context for the process from GitHub and the repo files, configuring what tests to run, if and what kind of release to create, and whether to setup testing infrastructure and what operating systems to run the tests on. @@ -169,11 +170,13 @@ When `ReleaseType` is `None`, when an `IgnoreLabels` label is present, or when n ### Build module [workflow](./.github/workflows/Build-Module.yml) + - Compiles the module source code into a PowerShell module. ### Test source code [workflow](./.github/workflows/Test-SourceCode.yml) + - Tests the source code in parallel (matrix) using: - [PSModule framework settings for style and standards for source code](https://github.com/PSModule/Test-PSModule?tab=readme-ov-file#sourcecode-tests) - This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the tests. @@ -193,10 +196,10 @@ The [PSModule - SourceCode tests](./scripts/tests/SourceCode/PSModule/PSModule.T | ParamBlock | Functions (Generic) | Functions should have a parameter block (`param()`). | | FunctionTest | Functions (Public) | All public functions/filters should have corresponding tests. | - ### Lint source code [workflow](./.github/workflows/Lint-SourceCode.yml) + - Lints the source code in parallel (matrix) using: - [PSScriptAnalyzer rules](https://github.com/PSModule/Invoke-ScriptAnalyzer) - This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the linter. @@ -204,6 +207,7 @@ The [PSModule - SourceCode tests](./scripts/tests/SourceCode/PSModule/PSModule.T ### Framework test [workflow](./.github/workflows/Test-Module.yml) + - Tests and lints the module in parallel (matrix) using: - [PSModule framework settings for style and standards for modules](https://github.com/PSModule/Test-PSModule?tab=readme-ov-file#module-tests) - [PSScriptAnalyzer rules](https://github.com/PSModule/Invoke-ScriptAnalyzer) @@ -220,6 +224,7 @@ The [PSModule - SourceCode tests](./scripts/tests/SourceCode/PSModule/PSModule.T ### Test module [workflow](./.github/workflows/Test-ModuleLocal.yml) + - Imports and tests the module in parallel (matrix) using Pester tests from the module repository. - Supports setup and teardown scripts executed via separate dedicated jobs: - `BeforeAll`: Runs once before all test matrix jobs to set up the test environment (e.g., deploy infrastructure, download test data). @@ -332,7 +337,6 @@ Use a consistent naming scheme so that resources are easy to identify and clean When tests use multiple authentication contexts that share the same runner, include a token or context identifier in the name to avoid collisions (for example, `Test-{OS}-{ContextID}-{RunID}`). - #### Module tests The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1) verifies the following coding practices that the framework enforces: @@ -345,12 +349,14 @@ The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1 ### Get test results [workflow](./.github/workflows/Get-TestResults.yml) + - Gathers the test results from the previous steps and creates a summary of the results. - If any tests have failed, the workflow will fail here. ### Get code coverage [workflow](./.github/workflows/Get-CodeCoverage.yml) + - Gathers the code coverage from the previous steps and creates a summary of the results. - Aggregates coverage from the [Framework test](#framework-test) step (framework-generated boilerplate) and the [Test module](#test-module) step (module author code). A command executed in either step counts as covered, so @@ -360,6 +366,7 @@ The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1 ### Publish module [workflow](./.github/workflows/Publish-Module.yml) + - Publishes the module to the PowerShell Gallery. - Creates a release on the GitHub repository. - Attaches the built module as a `.zip` asset on the GitHub Release so consumers can download the exact bytes that were tested and pushed to the PowerShell Gallery. @@ -371,12 +378,14 @@ The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1 ### Build docs [workflow](./.github/workflows/Build-Docs.yml) + - Generates documentation and lints the documentation using: - [super-linter](https://github.com/super-linter/super-linter). ### Build site [workflow](./.github/workflows/Build-Site.yml) + - Generates a static site using: - [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). From 0b0b7a31be91208d8cded67e4873300d82a0582d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 24 May 2026 12:25:12 +0200 Subject: [PATCH 08/13] Update Resolve-PSModuleVersion to v1.0.1 --- .github/workflows/Plan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index 4034b394..ce63838c 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -106,7 +106,7 @@ jobs: - name: Resolve-Version # Resolve only when the workflow is going to create a release/prerelease. if: fromJson(steps.Get-Settings.outputs.Settings).Publish.Module.ReleaseType != 'None' - uses: PSModule/Resolve-PSModuleVersion@main + uses: PSModule/Resolve-PSModuleVersion@65b7cb026cb3414943778473fd82ee6cf4f0363e # v1.0.1 id: Resolve-Version env: GH_TOKEN: ${{ github.token }} From 7385a29a217098fb1d622fa63176ee920bd19b47 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 25 May 2026 00:56:31 +0200 Subject: [PATCH 09/13] Update Build-PSModule to v4.1 --- .github/workflows/Build-Module.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index fa59f96a..5dca87a8 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -40,7 +40,7 @@ jobs: fetch-depth: 0 - name: Build module - uses: PSModule/Build-PSModule@main + uses: PSModule/Build-PSModule@v4.1 with: Name: ${{ fromJson(inputs.Settings).Name }} Version: ${{ inputs.ModuleVersion }} From df0f1c4f6b6d79089720cf019eed8dee355527c7 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 25 May 2026 01:02:42 +0200 Subject: [PATCH 10/13] Fall back to 0.0.0 when no ModuleVersion is resolved --- .github/workflows/Build-Module.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index 5dca87a8..f2f9dc96 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -43,7 +43,7 @@ jobs: uses: PSModule/Build-PSModule@v4.1 with: Name: ${{ fromJson(inputs.Settings).Name }} - Version: ${{ inputs.ModuleVersion }} + Version: ${{ inputs.ModuleVersion != '' && inputs.ModuleVersion || '0.0.0' }} Prerelease: ${{ inputs.ModulePrerelease }} ArtifactName: ${{ inputs.ArtifactName }} WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} From 87cf094a9ac8022a051305e3312a0d0bace05e61 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 25 May 2026 08:54:43 +0200 Subject: [PATCH 11/13] Merge version outputs into Settings object; update README to be declarative --- .github/workflows/Build-Module.yml | 14 ++------ .github/workflows/Plan.yml | 55 +++++++++++++++------------- .github/workflows/workflow.yml | 2 -- README.md | 57 +++++++----------------------- 4 files changed, 44 insertions(+), 84 deletions(-) diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index f2f9dc96..75857ccc 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -12,16 +12,6 @@ on: description: Name of the artifact to upload. required: false default: module - ModuleVersion: - type: string - description: The Major.Minor.Patch version to stamp into the built manifest. Empty falls back to '999.0.0'. - required: false - default: '' - ModulePrerelease: - type: string - description: Optional prerelease tag to stamp into the built manifest. - required: false - default: '' permissions: contents: read # to checkout the repository @@ -43,7 +33,7 @@ jobs: uses: PSModule/Build-PSModule@v4.1 with: Name: ${{ fromJson(inputs.Settings).Name }} - Version: ${{ inputs.ModuleVersion != '' && inputs.ModuleVersion || '0.0.0' }} - Prerelease: ${{ inputs.ModulePrerelease }} + Version: ${{ fromJson(inputs.Settings).Module.Version != '' && fromJson(inputs.Settings).Module.Version || '0.0.0' }} + Prerelease: ${{ fromJson(inputs.Settings).Module.Prerelease }} ArtifactName: ${{ inputs.ArtifactName }} WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index ce63838c..0c87fd86 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -2,9 +2,10 @@ name: Plan # The Plan job is the single decision point for the workflow. # It runs two steps: -# 1. Get-PSModuleSettings - loads and resolves configuration -# 2. Resolve-PSModuleVersion - calculates the next version from settings + PR labels -# All downstream jobs consume the Settings JSON and the resolved version outputs from this job. +# 1. Get-PSModuleSettings - loads and resolves configuration +# 2. Resolve-PSModuleVersion - calculates the next version from settings + PR labels +# The resolved version is merged into the Settings object (Settings.Module.*) by the Enrich-Settings step. +# All downstream jobs receive one self-contained Settings JSON with no separate version outputs. on: workflow_call: @@ -51,23 +52,8 @@ on: outputs: Settings: - description: The complete settings object including test suites. + description: The complete settings object including test suites and resolved module version. value: ${{ jobs.Plan.outputs.Settings }} - ModuleVersion: - description: The Major.Minor.Patch part of the next version. - value: ${{ jobs.Plan.outputs.ModuleVersion }} - ModulePrerelease: - description: The prerelease tag, empty string when not a prerelease. - value: ${{ jobs.Plan.outputs.ModulePrerelease }} - ModuleFullVersion: - description: The full version string including prefix and prerelease tag (for example v1.4.0). - value: ${{ jobs.Plan.outputs.ModuleFullVersion }} - ReleaseType: - description: The release type - Release, Prerelease, or None. - value: ${{ jobs.Plan.outputs.ReleaseType }} - CreateRelease: - description: 'true when a release/prerelease should actually be created.' - value: ${{ jobs.Plan.outputs.CreateRelease }} permissions: contents: read # to checkout the repo @@ -78,12 +64,7 @@ jobs: name: Plan runs-on: ubuntu-latest outputs: - Settings: ${{ steps.Get-Settings.outputs.Settings }} - ModuleVersion: ${{ steps.Resolve-Version.outputs.Version }} - ModulePrerelease: ${{ steps.Resolve-Version.outputs.Prerelease }} - ModuleFullVersion: ${{ steps.Resolve-Version.outputs.FullVersion }} - ReleaseType: ${{ steps.Resolve-Version.outputs.ReleaseType }} - CreateRelease: ${{ steps.Resolve-Version.outputs.CreateRelease }} + Settings: ${{ steps.Enrich-Settings.outputs.Settings }} steps: - name: Checkout Code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -117,3 +98,27 @@ jobs: Verbose: ${{ inputs.Verbose }} Version: ${{ inputs.Version }} WorkingDirectory: ${{ inputs.WorkingDirectory }} + + - name: Enrich-Settings + # Merge the resolved version into the Settings object so all downstream jobs + # receive a single, self-contained Settings JSON with no separate version outputs. + id: Enrich-Settings + shell: pwsh + env: + SETTINGS: ${{ steps.Get-Settings.outputs.Settings }} + VERSION: ${{ steps.Resolve-Version.outputs.Version }} + PRERELEASE: ${{ steps.Resolve-Version.outputs.Prerelease }} + FULL_VERSION: ${{ steps.Resolve-Version.outputs.FullVersion }} + RELEASE_TYPE: ${{ steps.Resolve-Version.outputs.ReleaseType }} + CREATE_RELEASE: ${{ steps.Resolve-Version.outputs.CreateRelease }} + run: | + $settings = $env:SETTINGS | ConvertFrom-Json + $settings | Add-Member -MemberType NoteProperty -Name Module -Value ([pscustomobject]@{ + Version = $env:VERSION + Prerelease = $env:PRERELEASE + FullVersion = $env:FULL_VERSION + ReleaseType = if ([string]::IsNullOrEmpty($env:RELEASE_TYPE)) { 'None' } else { $env:RELEASE_TYPE } + CreateRelease = $env:CREATE_RELEASE -eq 'true' + }) + $enriched = $settings | ConvertTo-Json -Depth 10 -Compress + "Settings=$enriched" >> $env:GITHUB_OUTPUT diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 721c1f1e..7296e007 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -118,8 +118,6 @@ jobs: - Plan with: Settings: ${{ needs.Plan.outputs.Settings }} - ModuleVersion: ${{ needs.Plan.outputs.ModuleVersion }} - ModulePrerelease: ${{ needs.Plan.outputs.ModulePrerelease }} # Runs on: # - ✅ Open/Updated PR - Tests source code changes diff --git a/README.md b/README.md index 7175c8c4..20d93aef 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ Depending on the labels in the pull requests, the [workflow will result in diffe - [Workflow overview](#workflow-overview) - [Plan](#plan) - [Lint-Repository](#lint-repository) - - [Plan job](#plan-job) - [Build module](#build-module) - [Test source code](#test-source-code) - [Lint source code](#lint-source-code) @@ -113,65 +112,33 @@ Depending on the labels in the pull requests, the [workflow will result in diffe The Plan job is the single decision point of the workflow. It runs two steps in sequence: 1. **Get-PSModuleSettings** — loads the settings file (`.github/PSModule.yml`) and emits a fully resolved `Settings` JSON object that every downstream job consumes. -2. **Resolve-PSModuleVersion** — calculates the next module version from the resolved settings and the labels on the current pull request. Emits `ModuleVersion`, `ModulePrerelease`, `ModuleFullVersion`, `ReleaseType`, and `CreateRelease` as job outputs. +2. **Resolve-PSModuleVersion** — calculates the next module version from the resolved settings and the labels on the current pull request. -The resolved version is passed into `Build-Module` so the manifest is stamped with the final version **before** the test stages run. The same artifact is then published unchanged by `Publish-Module`, which also uploads the zipped module as a GitHub Release asset. The bytes that are tested are the bytes that ship to the PowerShell Gallery and to GitHub Releases. +After both steps complete, the resolved version is merged into `Settings` under a `Module` key: -#### How version resolution works - -The [Resolve-PSModuleVersion](https://github.com/PSModule/Resolve-PSModuleVersion) step reads configuration from `Settings.Publish.Module`: - -| Key | Description | +| `Settings.Module` field | Description | | --- | ----------- | -| `ReleaseType` | `Release`, `Prerelease`, or `None`. | -| `AutoPatching` | When `true`, a patch bump is applied even without a label. | -| `IncrementalPrerelease` | When `true`, an incrementing counter is appended to prerelease tags. | -| `DatePrereleaseFormat` | Optional .NET DateTime format string for date-based prerelease suffixes. | -| `VersionPrefix` | Tag prefix (typically `v`). | -| `MajorLabels`, `MinorLabels`, `PatchLabels` | Comma-separated PR labels that trigger the corresponding bump. | -| `IgnoreLabels` | Comma-separated PR labels that suppress version creation. | - -Resolution algorithm: - -1. Loads the `pull_request` event payload and collects PR labels. -2. Validates `ReleaseType`; applies `IgnoreLabels` override (suppresses release if matched). -3. Picks the bump type: `MajorLabels` > `MinorLabels` > (`PatchLabels` or `AutoPatching`). -4. Reads the latest version from GitHub Releases (`gh release list`) and the PowerShell Gallery (`Find-PSResource`), - takes the higher of the two as the baseline. -5. Bumps the baseline (major, minor, or patch). -6. For prereleases, appends the sanitized branch name, optionally a `DatePrereleaseFormat` timestamp, and an - incremental counter calculated from existing prereleases on the same baseline + branch. -7. Emits outputs: - -| Output | Description | -| --- | --- | | `Version` | `Major.Minor.Patch` portion (for example `1.4.0`). | | `Prerelease` | Prerelease tag, empty when not a prerelease. | | `FullVersion` | Full string including prefix and prerelease (for example `v1.4.0-mybranch001`). | -| `ReleaseType` | `Release`, `Prerelease`, or `None` when no bump label is found. | -| `CreateRelease` | `true` when a release or prerelease should be created. | +| `ReleaseType` | `Release`, `Prerelease`, or `None`. | +| `CreateRelease` | `true` when a release or prerelease will be created. | -When `ReleaseType` is `None`, when an `IgnoreLabels` label is present, or when no version bump label is found -(and `AutoPatching` is disabled), `CreateRelease` is `false` and the version outputs are empty strings. +All downstream jobs receive this single enriched `Settings` object — there are no separate version outputs. +The version decided here is the version that ships. `Build-Module` stamps it into the manifest before any test runs, +and `Publish-Module` publishes the artifact unchanged. ### Lint-Repository [workflow](./.github/workflows/Lint-Repository.yml) -### Plan job - -[workflow](#plan) - -- Reads the settings file `.github/PSModule.yml` in the module repository to configure the workflow. -- Gathers context for the process from GitHub and the repo files, configuring what tests to run, if and what kind of release to create, and whether - to setup testing infrastructure and what operating systems to run the tests on. -- Calculates the next module version from PR labels and existing releases, then publishes it as job outputs so Build-Module can stamp the manifest before the artifact is tested. - ### Build module [workflow](./.github/workflows/Build-Module.yml) +- Receives the resolved version from `Settings.Module` and stamps it into the module manifest before the artifact is uploaded. - Compiles the module source code into a PowerShell module. +- Produces the artifact that flows unchanged through test and publish stages. ### Test source code @@ -367,8 +334,8 @@ The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1 [workflow](./.github/workflows/Publish-Module.yml) -- Publishes the module to the PowerShell Gallery. -- Creates a release on the GitHub repository. +- Publishes the artifact to the PowerShell Gallery exactly as built — no version mutation. +- Creates a GitHub Release using the version already stamped in the manifest. - Attaches the built module as a `.zip` asset on the GitHub Release so consumers can download the exact bytes that were tested and pushed to the PowerShell Gallery. - **Abandoned PR cleanup**: When a PR is closed without merging (abandoned), the workflow automatically cleans up any prerelease versions and tags that were created for that PR. This ensures that abandoned work doesn't leave orphaned From 19b2789f58f6cac031d602d04d6d3a5c359deec5 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 25 May 2026 08:55:28 +0200 Subject: [PATCH 12/13] Tighten Plan section: describe only what Plan does, not downstream effects --- README.md | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 20d93aef..ec3d3e26 100644 --- a/README.md +++ b/README.md @@ -109,24 +109,10 @@ Depending on the labels in the pull requests, the [workflow will result in diffe [workflow](./.github/workflows/Plan.yml) -The Plan job is the single decision point of the workflow. It runs two steps in sequence: - -1. **Get-PSModuleSettings** — loads the settings file (`.github/PSModule.yml`) and emits a fully resolved `Settings` JSON object that every downstream job consumes. -2. **Resolve-PSModuleVersion** — calculates the next module version from the resolved settings and the labels on the current pull request. - -After both steps complete, the resolved version is merged into `Settings` under a `Module` key: - -| `Settings.Module` field | Description | -| --- | ----------- | -| `Version` | `Major.Minor.Patch` portion (for example `1.4.0`). | -| `Prerelease` | Prerelease tag, empty when not a prerelease. | -| `FullVersion` | Full string including prefix and prerelease (for example `v1.4.0-mybranch001`). | -| `ReleaseType` | `Release`, `Prerelease`, or `None`. | -| `CreateRelease` | `true` when a release or prerelease will be created. | - -All downstream jobs receive this single enriched `Settings` object — there are no separate version outputs. -The version decided here is the version that ships. `Build-Module` stamps it into the manifest before any test runs, -and `Publish-Module` publishes the artifact unchanged. +The Plan job is the single decision point of the workflow. It reads the settings file (`.github/PSModule.yml`), +collects event context from GitHub, and decides what should happen in the rest of the process. Using that +situational awareness it calculates the next module version. All decisions are captured in a single `Settings` +object — including version data under `Settings.Module` — that every downstream job receives. ### Lint-Repository From 3d174654e9a37ff07f987cdcc33f5ae5633ccefc Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 25 May 2026 09:01:37 +0200 Subject: [PATCH 13/13] Fix Build module description: correct sequence, remove downstream commentary --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ec3d3e26..65571c96 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,8 @@ object — including version data under `Settings.Module` — that every downstr [workflow](./.github/workflows/Build-Module.yml) -- Receives the resolved version from `Settings.Module` and stamps it into the module manifest before the artifact is uploaded. -- Compiles the module source code into a PowerShell module. -- Produces the artifact that flows unchanged through test and publish stages. +- Compiles the module source code into a PowerShell module, stamping the version from `Settings.Module` into the manifest. +- Uploads the built artifact. ### Test source code @@ -406,16 +405,16 @@ jobs: The following secrets are used by the workflow. They can be automatically provided (if available) by setting `secrets: inherit` in the workflow file. -| Name | Location | Description | Default | -| ---- | -------------- | ------------------------------------------------------------------------- | ------- | -| `APIKEY` | GitHub secrets | The API key for the PowerShell Gallery. | N/A | -| `TEST_APP_ENT_CLIENT_ID` | GitHub secrets | The client ID of an Enterprise GitHub App for running tests. | N/A | -| `TEST_APP_ENT_PRIVATE_KEY` | GitHub secrets | The private key of an Enterprise GitHub App for running tests. | N/A | -| `TEST_APP_ORG_CLIENT_ID` | GitHub secrets | The client ID of an Organization GitHub App for running tests. | N/A | -| `TEST_APP_ORG_PRIVATE_KEY` | GitHub secrets | The private key of an Organization GitHub App for running tests. | N/A | -| `TEST_USER_ORG_FG_PAT` | GitHub secrets | The fine-grained PAT with organization access for running tests. | N/A | -| `TEST_USER_USER_FG_PAT` | GitHub secrets | The fine-grained PAT with user account access for running tests. | N/A | -| `TEST_USER_PAT` | GitHub secrets | The classic personal access token for running tests. | N/A | +| Name | Location | Description | +| -------------------------- | -------------- | ---------------------------------------------------------------- | +| `APIKEY` | GitHub secrets | The API key for the PowerShell Gallery. | +| `TEST_APP_ENT_CLIENT_ID` | GitHub secrets | The client ID of an Enterprise GitHub App for running tests. | +| `TEST_APP_ENT_PRIVATE_KEY` | GitHub secrets | The private key of an Enterprise GitHub App for running tests. | +| `TEST_APP_ORG_CLIENT_ID` | GitHub secrets | The client ID of an Organization GitHub App for running tests. | +| `TEST_APP_ORG_PRIVATE_KEY` | GitHub secrets | The private key of an Organization GitHub App for running tests. | +| `TEST_USER_ORG_FG_PAT` | GitHub secrets | The fine-grained PAT with organization access for running tests. | +| `TEST_USER_USER_FG_PAT` | GitHub secrets | The fine-grained PAT with user account access for running tests. | +| `TEST_USER_PAT` | GitHub secrets | The classic personal access token for running tests. | ### Permissions @@ -547,7 +546,7 @@ The following settings are available in the settings file: | Name | Type | Description | Default | | ----------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | `Name` | `String` | Name of the module to publish. Defaults to the repository name. | `null` | -| `ImportantFilePatterns` | `Array` | Regular expression patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. When set, fully replaces the defaults. | `['^src/', '^README\.md$']` | +| `ImportantFilePatterns` | `Array` | Regular expression patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. When set, fully replaces the defaults. | `['^src/', '^README\.md$']` | | `Test.Skip` | `Boolean` | Skip all tests | `false` | | `Test.Linux.Skip` | `Boolean` | Skip tests on Linux | `false` | | `Test.MacOS.Skip` | `Boolean` | Skip tests on macOS | `false` |