From f82c2196ef9ee4e29ea70da403e949d768cf570c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:19:56 +0200 Subject: [PATCH 1/3] Internalize phase runtime settings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/Settings.schema.json | 107 +----------------- .../actions/Get-PSModuleSettings/src/main.ps1 | 107 ++++++++++++------ .github/workflows/Build-Module.yml | 4 +- .github/workflows/Get-TestResults.yml | 6 +- .github/workflows/Lint-SourceCode.yml | 2 +- .github/workflows/Plan.yml | 6 +- .github/workflows/Test-Module.yml | 4 +- .github/workflows/Test-ModuleLocal.yml | 4 +- .github/workflows/Test-SourceCode.yml | 2 +- .github/workflows/workflow.yml | 28 ++--- tests/SettingsRuntimeContract.Tests.ps1 | 45 ++++++++ 11 files changed, 147 insertions(+), 168 deletions(-) create mode 100644 tests/SettingsRuntimeContract.Tests.ps1 diff --git a/.github/actions/Get-PSModuleSettings/src/Settings.schema.json b/.github/actions/Get-PSModuleSettings/src/Settings.schema.json index 1e0f4bcd..fdbc9013 100644 --- a/.github/actions/Get-PSModuleSettings/src/Settings.schema.json +++ b/.github/actions/Get-PSModuleSettings/src/Settings.schema.json @@ -220,51 +220,6 @@ } } }, - "TestSuites": { - "type": "object", - "description": "Test suite configurations", - "properties": { - "SourceCode": { - "oneOf": [ - { - "type": "null" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/testSuite" - } - } - ] - }, - "PSModule": { - "oneOf": [ - { - "type": "null" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/testSuite" - } - } - ] - }, - "Module": { - "oneOf": [ - { - "type": "null" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/moduleTestSuite" - } - } - ] - } - } - }, "SettingsPath": { "type": "string", "description": "Path to the settings file" @@ -306,66 +261,8 @@ "description": "Indicates if important files have changed in the PR (src/**, examples/**, README.md, .github/workflows/Process-PSModule.yml)" }, "Run": { - "type": "object", - "description": "Runtime execution flags", - "properties": { - "LintRepository": { - "type": "boolean", - "description": "Run repository linting" - }, - "BuildModule": { - "type": "boolean", - "description": "Build the module" - }, - "TestSourceCode": { - "type": "boolean", - "description": "Test source code" - }, - "LintSourceCode": { - "type": "boolean", - "description": "Lint source code" - }, - "TestModule": { - "type": "boolean", - "description": "Test the module" - }, - "BeforeAllModuleLocal": { - "type": "boolean", - "description": "Run before all module local tests" - }, - "TestModuleLocal": { - "type": "boolean", - "description": "Test module locally" - }, - "AfterAllModuleLocal": { - "type": "boolean", - "description": "Run after all module local tests" - }, - "GetTestResults": { - "type": "boolean", - "description": "Collect test results" - }, - "GetCodeCoverage": { - "type": "boolean", - "description": "Collect code coverage" - }, - "PublishModule": { - "type": "boolean", - "description": "Publish the module" - }, - "BuildDocs": { - "type": "boolean", - "description": "Build documentation" - }, - "BuildSite": { - "type": "boolean", - "description": "Build the site" - }, - "PublishSite": { - "type": "boolean", - "description": "Publish the site" - } - } + "type": "null", + "description": "Deprecated. Runtime execution flags are now stored on each phase object." } }, "definitions": { diff --git a/.github/actions/Get-PSModuleSettings/src/main.ps1 b/.github/actions/Get-PSModuleSettings/src/main.ps1 index 83ad38ae..6eb92a01 100644 --- a/.github/actions/Get-PSModuleSettings/src/main.ps1 +++ b/.github/actions/Get-PSModuleSettings/src/main.ps1 @@ -375,13 +375,6 @@ if ($settings.Test.Skip) { $sourceCodeTestSuites = $null $psModuleTestSuites = $null $moduleTestSuites = $null - - # Add TestSuites to settings - $settings | Add-Member -MemberType NoteProperty -Name TestSuites -Value ([pscustomobject]@{ - SourceCode = $null - PSModule = $null - Module = $null - }) } else { # Define test configurations as an array of hashtables. @@ -529,14 +522,13 @@ if ($settings.Test.Skip) { $moduleTestSuites | Format-Table -AutoSize | Out-String } - # Add TestSuites to settings - $settings | Add-Member -MemberType NoteProperty -Name TestSuites -Value ([pscustomobject]@{ - SourceCode = $sourceCodeTestSuites - PSModule = $psModuleTestSuites - Module = $moduleTestSuites - }) } +# Keep test suites with each test phase that owns them. +$settings.Test.SourceCode | Add-Member -MemberType NoteProperty -Name Suites -Value $sourceCodeTestSuites -Force +$settings.Test.PSModule | Add-Member -MemberType NoteProperty -Name Suites -Value $psModuleTestSuites -Force +$settings.Test.Module | Add-Member -MemberType NoteProperty -Name Suites -Value $moduleTestSuites -Force + # Calculate job-specific conditions and add to settings LogGroup 'Calculate Job Run Conditions:' { # Calculate if prereleases should be cleaned up: @@ -565,32 +557,77 @@ LogGroup 'Calculate Job Run Conditions:' { Write-Host " tests/BeforeAll.ps1 exists: $hasBeforeAllScript" Write-Host " tests/AfterAll.ps1 exists: $hasAfterAllScript" - # Create Run object with all job-specific conditions - $run = [pscustomobject]@{ - LintRepository = $isOpenOrUpdatedPR -and (-not $settings.Linter.Skip) - BuildModule = $shouldRunBuildTest -and (-not $settings.Build.Module.Skip) - TestSourceCode = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.SourceCode) - LintSourceCode = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.SourceCode) - TestModule = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.PSModule) - BeforeAllModuleLocal = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.Module) -and $hasBeforeAllScript - TestModuleLocal = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.Module) - AfterAllModuleLocal = $shouldRunBuildTest -and ($null -ne $settings.TestSuites.Module) -and $hasAfterAllScript - GetTestResults = $shouldRunBuildTest -and (-not $settings.Test.TestResults.Skip) -and ( - ($null -ne $settings.TestSuites.SourceCode) -or ($null -ne $settings.TestSuites.PSModule) -or ($null -ne $settings.TestSuites.Module) + $sourceCodeEnabled = $shouldRunBuildTest -and ($null -ne $settings.Test.SourceCode.Suites) + $psModuleEnabled = $shouldRunBuildTest -and ($null -ne $settings.Test.PSModule.Suites) + $moduleLocalEnabled = $shouldRunBuildTest -and ($null -ne $settings.Test.Module.Suites) + $beforeAllEnabled = $moduleLocalEnabled -and $hasBeforeAllScript + $afterAllEnabled = $moduleLocalEnabled -and $hasAfterAllScript + + # Keep desired/computed execution state with each phase. + $settings.Linter | Add-Member -MemberType NoteProperty -Name Repository -Value ([pscustomobject]@{ + Desired = -not $settings.Linter.Skip + Enabled = $isOpenOrUpdatedPR -and (-not $settings.Linter.Skip) + }) -Force + $settings.Linter | Add-Member -MemberType NoteProperty -Name SourceCode -Value ([pscustomobject]@{ + Desired = -not $settings.Test.SourceCode.Skip + Enabled = $sourceCodeEnabled + }) -Force + + $settings.Build.Module | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Module.Skip) -Force + $settings.Build.Module | Add-Member -MemberType NoteProperty -Name Enabled -Value ($shouldRunBuildTest -and (-not $settings.Build.Module.Skip)) -Force + $settings.Build.Docs | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Docs.Skip) -Force + $settings.Build.Docs | Add-Member -MemberType NoteProperty -Name Enabled -Value ($shouldRunBuildTest -and (-not $settings.Build.Docs.Skip)) -Force + $settings.Build.Site | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Site.Skip) -Force + $settings.Build.Site | Add-Member -MemberType NoteProperty -Name Enabled -Value ($shouldRunBuildTest -and (-not $settings.Build.Site.Skip)) -Force + + $settings.Test.SourceCode | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Test.SourceCode.Skip) -Force + $settings.Test.SourceCode | Add-Member -MemberType NoteProperty -Name Enabled -Value $sourceCodeEnabled -Force + $settings.Test.PSModule | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Test.PSModule.Skip) -Force + $settings.Test.PSModule | Add-Member -MemberType NoteProperty -Name Enabled -Value $psModuleEnabled -Force + $settings.Test.Module | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Test.Module.Skip) -Force + $settings.Test.Module | Add-Member -MemberType NoteProperty -Name BeforeAllEnabled -Value $beforeAllEnabled -Force + $settings.Test.Module | Add-Member -MemberType NoteProperty -Name MainEnabled -Value $moduleLocalEnabled -Force + $settings.Test.Module | Add-Member -MemberType NoteProperty -Name AfterAllEnabled -Value $afterAllEnabled -Force + + $settings.Test.TestResults | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Test.TestResults.Skip) -Force + $settings.Test.TestResults | Add-Member -MemberType NoteProperty -Name Enabled -Value ( + $shouldRunBuildTest -and (-not $settings.Test.TestResults.Skip) -and ( + ($null -ne $settings.Test.SourceCode.Suites) -or ($null -ne $settings.Test.PSModule.Suites) -or ($null -ne $settings.Test.Module.Suites) ) - GetCodeCoverage = $shouldRunBuildTest -and (-not $settings.Test.CodeCoverage.Skip) -and ( - ($null -ne $settings.TestSuites.PSModule) -or ($null -ne $settings.TestSuites.Module) + ) -Force + $settings.Test.CodeCoverage | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Test.CodeCoverage.Skip) -Force + $settings.Test.CodeCoverage | Add-Member -MemberType NoteProperty -Name Enabled -Value ( + $shouldRunBuildTest -and (-not $settings.Test.CodeCoverage.Skip) -and ( + ($null -ne $settings.Test.PSModule.Suites) -or ($null -ne $settings.Test.Module.Suites) ) - PublishModule = ($releaseType -ne 'None') -or $shouldAutoCleanup - BuildDocs = $shouldRunBuildTest -and (-not $settings.Build.Docs.Skip) - BuildSite = $shouldRunBuildTest -and (-not $settings.Build.Site.Skip) - PublishSite = $isMergedPR -and $isTargetDefaultBranch -and $hasImportantChanges - } - $settings | Add-Member -MemberType NoteProperty -Name Run -Value $run + ) -Force + + $settings.Publish.Module | Add-Member -MemberType NoteProperty -Name Desired -Value (($releaseType -ne 'None') -or $shouldAutoCleanup) -Force + $settings.Publish.Module | Add-Member -MemberType NoteProperty -Name Enabled -Value (($releaseType -ne 'None') -or $shouldAutoCleanup) -Force + $settings.Publish | Add-Member -MemberType NoteProperty -Name Site -Value ([pscustomobject]@{ + Desired = $isMergedPR -and $isTargetDefaultBranch -and $hasImportantChanges + Enabled = $isMergedPR -and $isTargetDefaultBranch -and $hasImportantChanges + }) -Force + $settings | Add-Member -MemberType NoteProperty -Name HasImportantChanges -Value $hasImportantChanges - Write-Host 'Job Run Conditions:' - $run | Format-List | Out-String + Write-Host 'Phase execution state:' + [pscustomobject]@{ + LintRepository = $settings.Linter.Repository.Enabled + BuildModule = $settings.Build.Module.Enabled + TestSourceCode = $settings.Test.SourceCode.Enabled + LintSourceCode = $settings.Linter.SourceCode.Enabled + TestModule = $settings.Test.PSModule.Enabled + BeforeAllModuleLocal = $settings.Test.Module.BeforeAllEnabled + TestModuleLocal = $settings.Test.Module.MainEnabled + AfterAllModuleLocal = $settings.Test.Module.AfterAllEnabled + GetTestResults = $settings.Test.TestResults.Enabled + GetCodeCoverage = $settings.Test.CodeCoverage.Enabled + PublishModule = $settings.Publish.Module.Enabled + BuildDocs = $settings.Build.Docs.Enabled + BuildSite = $settings.Build.Site.Enabled + PublishSite = $settings.Publish.Site.Enabled + } | Format-List | Out-String } LogGroup 'Final settings' { diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index e1e402e0..411d7ed6 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -41,7 +41,7 @@ jobs: uses: ./_wf/.github/actions/Build-PSModule with: Name: ${{ fromJson(inputs.Settings).Name }} - Version: ${{ fromJson(inputs.Settings).Module.Version != '' && fromJson(inputs.Settings).Module.Version || '999.0.0' }} - Prerelease: ${{ fromJson(inputs.Settings).Module.Prerelease }} + Version: ${{ fromJson(inputs.Settings).Publish.Module.Resolution.Version != '' && fromJson(inputs.Settings).Publish.Module.Resolution.Version || '999.0.0' }} + Prerelease: ${{ fromJson(inputs.Settings).Publish.Module.Resolution.Prerelease }} ArtifactName: ${{ inputs.ArtifactName }} WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} diff --git a/.github/workflows/Get-TestResults.yml b/.github/workflows/Get-TestResults.yml index a3716484..b3e0e5ba 100644 --- a/.github/workflows/Get-TestResults.yml +++ b/.github/workflows/Get-TestResults.yml @@ -33,9 +33,9 @@ jobs: uses: ./_wf/.github/actions/Get-PesterTestResults id: Get-TestResults with: - SourceCodeTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.SourceCode) }} - PSModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.PSModule) }} - ModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.Module) }} + SourceCodeTestSuites: ${{ toJSON(fromJson(inputs.Settings).Test.SourceCode.Suites) }} + PSModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).Test.PSModule.Suites) }} + ModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).Test.Module.Suites) }} BeforeAllModuleLocalResult: ${{ inputs.BeforeAllModuleLocalResult }} Debug: ${{ fromJson(inputs.Settings).Debug }} Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} diff --git a/.github/workflows/Lint-SourceCode.yml b/.github/workflows/Lint-SourceCode.yml index 519b3421..c3d8a1e3 100644 --- a/.github/workflows/Lint-SourceCode.yml +++ b/.github/workflows/Lint-SourceCode.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(inputs.Settings).TestSuites.SourceCode }} + include: ${{ fromJson(inputs.Settings).Test.SourceCode.Suites }} steps: - name: Checkout Code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index 442e14fe..3fad50eb 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -4,7 +4,7 @@ name: Plan # It runs two steps: # 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. +# The resolved version is merged into the publish phase object (Settings.Publish.Module.Resolution.*) by the Enrich-Settings step. # All downstream jobs receive one self-contained Settings JSON with no separate version outputs. on: @@ -118,12 +118,12 @@ jobs: CREATE_RELEASE: ${{ steps.Resolve-Version.outputs.CreateRelease }} run: | $settings = $env:SETTINGS | ConvertFrom-Json - $settings | Add-Member -MemberType NoteProperty -Name Module -Value ([pscustomobject]@{ + $settings.Publish.Module | Add-Member -MemberType NoteProperty -Name Resolution -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' - }) + }) -Force $enriched = $settings | ConvertTo-Json -Depth 10 -Compress "Settings=$enriched" >> $env:GITHUB_OUTPUT diff --git a/.github/workflows/Test-Module.yml b/.github/workflows/Test-Module.yml index a648679b..d36e07fb 100644 --- a/.github/workflows/Test-Module.yml +++ b/.github/workflows/Test-Module.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(inputs.Settings).TestSuites.PSModule }} + include: ${{ fromJson(inputs.Settings).Test.PSModule.Suites }} steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(inputs.Settings).TestSuites.PSModule }} + include: ${{ fromJson(inputs.Settings).Test.PSModule.Suites }} steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/Test-ModuleLocal.yml b/.github/workflows/Test-ModuleLocal.yml index 8c71165c..3f413968 100644 --- a/.github/workflows/Test-ModuleLocal.yml +++ b/.github/workflows/Test-ModuleLocal.yml @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(inputs.Settings).TestSuites.Module }} + include: ${{ fromJson(inputs.Settings).Test.Module.Suites }} steps: - name: Checkout Code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -88,4 +88,4 @@ jobs: WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} Prescript: | # This is to speed up module loading in Pester. Install-PSResource -Repository PSGallery -TrustRepository -Name PSCustomObject - Import-Module -Name '${{ steps.import-module.outputs.name }}' -RequiredVersion '${{ fromJson(inputs.Settings).Module.Version }}' + Import-Module -Name '${{ steps.import-module.outputs.name }}' -RequiredVersion '${{ fromJson(inputs.Settings).Publish.Module.Resolution.Version }}' diff --git a/.github/workflows/Test-SourceCode.yml b/.github/workflows/Test-SourceCode.yml index a4192f6f..06ea571d 100644 --- a/.github/workflows/Test-SourceCode.yml +++ b/.github/workflows/Test-SourceCode.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(inputs.Settings).TestSuites.SourceCode }} + include: ${{ fromJson(inputs.Settings).Test.SourceCode.Suites }} steps: - name: Checkout Code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1b9839a2..099afc34 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -92,7 +92,7 @@ jobs: # - ❌ Abandoned PR - No need to lint abandoned changes # - ❌ Manual run - Only runs for PR events Lint-Repository: - if: fromJson(needs.Plan.outputs.Settings).Run.LintRepository + if: fromJson(needs.Plan.outputs.Settings).Linter.Repository.Enabled needs: - Plan uses: ./.github/workflows/Lint-Repository.yml @@ -105,7 +105,7 @@ jobs: # - ❌ Abandoned PR - Skips building abandoned changes # - ✅ Manual run - Builds module when manually triggered Build-Module: - if: fromJson(needs.Plan.outputs.Settings).Run.BuildModule + if: fromJson(needs.Plan.outputs.Settings).Build.Module.Enabled uses: ./.github/workflows/Build-Module.yml needs: - Plan @@ -118,7 +118,7 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests source code when manually triggered Test-SourceCode: - if: fromJson(needs.Plan.outputs.Settings).Run.TestSourceCode + if: fromJson(needs.Plan.outputs.Settings).Test.SourceCode.Enabled needs: - Plan uses: ./.github/workflows/Test-SourceCode.yml @@ -131,7 +131,7 @@ jobs: # - ❌ Abandoned PR - Skips linting abandoned changes # - ✅ Manual run - Lints source code when manually triggered Lint-SourceCode: - if: fromJson(needs.Plan.outputs.Settings).Run.LintSourceCode + if: fromJson(needs.Plan.outputs.Settings).Linter.SourceCode.Enabled needs: - Plan uses: ./.github/workflows/Lint-SourceCode.yml @@ -144,7 +144,7 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests built module when manually triggered Test-Module: - if: fromJson(needs.Plan.outputs.Settings).Run.TestModule && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Test.PSModule.Enabled && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - Plan @@ -158,7 +158,7 @@ jobs: # - ❌ Abandoned PR - Skips setup for abandoned changes # - ✅ Manual run - Runs setup scripts when manually triggered BeforeAll-ModuleLocal: - if: fromJson(needs.Plan.outputs.Settings).Run.BeforeAllModuleLocal && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Test.Module.BeforeAllEnabled && needs.Build-Module.result == 'success' && !cancelled() uses: ./.github/workflows/BeforeAll-ModuleLocal.yml secrets: TestData: ${{ secrets.TestData }} @@ -174,7 +174,7 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests module in local environment when manually triggered Test-ModuleLocal: - if: fromJson(needs.Plan.outputs.Settings).Run.TestModuleLocal && needs.Build-Module.result == 'success' && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Test.Module.MainEnabled && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - Plan @@ -191,7 +191,7 @@ jobs: # - ✅ Abandoned PR - Runs teardown if local module setup/tests were started (cleanup) # - ✅ Manual run - Runs teardown scripts after local module setup/tests AfterAll-ModuleLocal: - if: fromJson(needs.Plan.outputs.Settings).Run.AfterAllModuleLocal && needs.BeforeAll-ModuleLocal.result != 'skipped' && always() + if: fromJson(needs.Plan.outputs.Settings).Test.Module.AfterAllEnabled && needs.BeforeAll-ModuleLocal.result != 'skipped' && always() uses: ./.github/workflows/AfterAll-ModuleLocal.yml secrets: TestData: ${{ secrets.TestData }} @@ -208,7 +208,7 @@ jobs: # - ❌ Abandoned PR - Skips collecting results for abandoned changes # - ✅ Manual run - Collects and reports test results when manually triggered Get-TestResults: - if: fromJson(needs.Plan.outputs.Settings).Run.GetTestResults && needs.Plan.result == 'success' && always() && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Test.TestResults.Enabled && needs.Plan.result == 'success' && always() && !cancelled() needs: - Plan - Test-SourceCode @@ -227,7 +227,7 @@ jobs: # - ❌ Abandoned PR - Skips coverage for abandoned changes # - ✅ Manual run - Calculates and reports code coverage when manually triggered Get-CodeCoverage: - if: fromJson(needs.Plan.outputs.Settings).Run.GetCodeCoverage && needs.Plan.result == 'success' && always() && !cancelled() + if: fromJson(needs.Plan.outputs.Settings).Test.CodeCoverage.Enabled && needs.Plan.result == 'success' && always() && !cancelled() needs: - Plan - Test-Module @@ -242,7 +242,7 @@ 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.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') + if: fromJson(needs.Plan.outputs.Settings).Publish.Module.Enabled && 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 }} @@ -260,7 +260,7 @@ jobs: # - ❌ Abandoned PR - Skips building docs for abandoned changes # - ✅ Manual run - Builds documentation when manually triggered Build-Docs: - if: fromJson(needs.Plan.outputs.Settings).Run.BuildDocs + if: fromJson(needs.Plan.outputs.Settings).Build.Docs.Enabled needs: - Plan - Build-Module @@ -274,7 +274,7 @@ jobs: # - ❌ Abandoned PR - Skips building site for abandoned changes # - ✅ Manual run - Builds site when manually triggered Build-Site: - if: fromJson(needs.Plan.outputs.Settings).Run.BuildSite + if: fromJson(needs.Plan.outputs.Settings).Build.Site.Enabled needs: - Plan - Build-Docs @@ -288,7 +288,7 @@ jobs: # - ❌ Abandoned PR - Site not published for abandoned changes # - ❌ Manual run - Only publishes on merged PRs to default branch Publish-Site: - if: fromJson(needs.Plan.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).Publish.Site.Enabled && needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success' && !cancelled() uses: ./.github/workflows/Publish-Site.yml needs: - Plan diff --git a/tests/SettingsRuntimeContract.Tests.ps1 b/tests/SettingsRuntimeContract.Tests.ps1 new file mode 100644 index 00000000..904b059a --- /dev/null +++ b/tests/SettingsRuntimeContract.Tests.ps1 @@ -0,0 +1,45 @@ +[CmdletBinding()] +param() + +Describe 'Runtime settings contract' { + BeforeAll { + $repoRoot = (Get-Location).Path + $workflowFile = Join-Path $repoRoot '.github/workflows/workflow.yml' + $planFile = Join-Path $repoRoot '.github/workflows/Plan.yml' + $settingsActionFile = Join-Path $repoRoot '.github/actions/Get-PSModuleSettings/src/main.ps1' + } + + It 'does not use legacy root Run flags in workflow dispatch conditions' { + $workflowContent = Get-Content -Path $workflowFile -Raw + $workflowContent | Should -Not -Match '\.Run\.' + } + + It 'uses phase-owned test suites for workflow matrices' { + $workflowFiles = @( + '.github/workflows/Test-SourceCode.yml', + '.github/workflows/Lint-SourceCode.yml', + '.github/workflows/Test-Module.yml', + '.github/workflows/Test-ModuleLocal.yml', + '.github/workflows/Get-TestResults.yml' + ) | ForEach-Object { Join-Path $repoRoot $_ } + + foreach ($file in $workflowFiles) { + $content = Get-Content -Path $file -Raw + $content | Should -Not -Match '\.TestSuites\.' + } + } + + It 'stores resolved version metadata in Publish.Module.Resolution' { + $planContent = Get-Content -Path $planFile -Raw + $planContent | Should -Match 'Publish\.Module \| Add-Member -MemberType NoteProperty -Name Resolution' + } + + It 'keeps phase execution state in owned objects, not root Run' { + $settingsContent = Get-Content -Path $settingsActionFile -Raw + $settingsContent | Should -Not -Match 'Add-Member -MemberType NoteProperty -Name Run' + $settingsContent | Should -Match 'Linter\.Repository' + $settingsContent | Should -Match 'Build\.Module' + $settingsContent | Should -Match 'Test\.SourceCode' + $settingsContent | Should -Match 'Publish\.Module' + } +} From 85f6ee071a36e4291ac9f49f7c086e3494ef6e36 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:36:15 +0200 Subject: [PATCH 2/3] Remove unused runtime contract test file Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/SettingsRuntimeContract.Tests.ps1 | 45 ------------------------- 1 file changed, 45 deletions(-) delete mode 100644 tests/SettingsRuntimeContract.Tests.ps1 diff --git a/tests/SettingsRuntimeContract.Tests.ps1 b/tests/SettingsRuntimeContract.Tests.ps1 deleted file mode 100644 index 904b059a..00000000 --- a/tests/SettingsRuntimeContract.Tests.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -[CmdletBinding()] -param() - -Describe 'Runtime settings contract' { - BeforeAll { - $repoRoot = (Get-Location).Path - $workflowFile = Join-Path $repoRoot '.github/workflows/workflow.yml' - $planFile = Join-Path $repoRoot '.github/workflows/Plan.yml' - $settingsActionFile = Join-Path $repoRoot '.github/actions/Get-PSModuleSettings/src/main.ps1' - } - - It 'does not use legacy root Run flags in workflow dispatch conditions' { - $workflowContent = Get-Content -Path $workflowFile -Raw - $workflowContent | Should -Not -Match '\.Run\.' - } - - It 'uses phase-owned test suites for workflow matrices' { - $workflowFiles = @( - '.github/workflows/Test-SourceCode.yml', - '.github/workflows/Lint-SourceCode.yml', - '.github/workflows/Test-Module.yml', - '.github/workflows/Test-ModuleLocal.yml', - '.github/workflows/Get-TestResults.yml' - ) | ForEach-Object { Join-Path $repoRoot $_ } - - foreach ($file in $workflowFiles) { - $content = Get-Content -Path $file -Raw - $content | Should -Not -Match '\.TestSuites\.' - } - } - - It 'stores resolved version metadata in Publish.Module.Resolution' { - $planContent = Get-Content -Path $planFile -Raw - $planContent | Should -Match 'Publish\.Module \| Add-Member -MemberType NoteProperty -Name Resolution' - } - - It 'keeps phase execution state in owned objects, not root Run' { - $settingsContent = Get-Content -Path $settingsActionFile -Raw - $settingsContent | Should -Not -Match 'Add-Member -MemberType NoteProperty -Name Run' - $settingsContent | Should -Match 'Linter\.Repository' - $settingsContent | Should -Match 'Build\.Module' - $settingsContent | Should -Match 'Test\.SourceCode' - $settingsContent | Should -Match 'Publish\.Module' - } -} From 38621988b87db0b29dd3fe4ee5bd3b8e3d3a0bb0 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:40:33 +0200 Subject: [PATCH 3/3] Fix PowerShell lint long line Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/Get-PSModuleSettings/src/main.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/Get-PSModuleSettings/src/main.ps1 b/.github/actions/Get-PSModuleSettings/src/main.ps1 index 6eb92a01..36aeba1a 100644 --- a/.github/actions/Get-PSModuleSettings/src/main.ps1 +++ b/.github/actions/Get-PSModuleSettings/src/main.ps1 @@ -574,7 +574,9 @@ LogGroup 'Calculate Job Run Conditions:' { }) -Force $settings.Build.Module | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Module.Skip) -Force - $settings.Build.Module | Add-Member -MemberType NoteProperty -Name Enabled -Value ($shouldRunBuildTest -and (-not $settings.Build.Module.Skip)) -Force + $settings.Build.Module | Add-Member -MemberType NoteProperty -Name Enabled -Value ( + $shouldRunBuildTest -and (-not $settings.Build.Module.Skip) + ) -Force $settings.Build.Docs | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Docs.Skip) -Force $settings.Build.Docs | Add-Member -MemberType NoteProperty -Name Enabled -Value ($shouldRunBuildTest -and (-not $settings.Build.Docs.Skip)) -Force $settings.Build.Site | Add-Member -MemberType NoteProperty -Name Desired -Value (-not $settings.Build.Site.Skip) -Force