diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b9a9fc6d..588a1c48 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,17 +2,18 @@ ## Terminal Commands -When executing terminal commands (using `run_in_terminal` or similar tools): - - Prefer MCP server calls over command-line tools when possible. -- **ALWAYS** send commands into `pwsh -Command` to ensure proper execution. - - These commands must be enclosed in single quotes. - - Escape any single quotes within the command by doubling them (e.g., `It's` becomes `It''s`). - - Use double quotes for string with variables or expressions inside the single-quoted command. +- When running scripts within the [scripts](../.specify/scripts/) folder, just run them directly (shell is default PowerShell). +- For other commands, send them into `pwsh -Command` to ensure proper execution. + +### Quoting in PowerShell + +Proper quoting is essential in PowerShell to prevent parsing errors and ensure correct command execution. + +- **Direct script execution**: Scripts run directly in PowerShell use standard PowerShell quoting rules. Double quotes expand variables and expressions, while single quotes are literal. No additional shell escaping is needed. + +- **Via `pwsh -Command`**: Commands are passed as strings to PowerShell. Enclose the entire command in single quotes to treat it as a literal string. Escape single quotes within the command by doubling them (e.g., `It's` becomes `It''s`). Use double quotes within the command for variable expansion, but ensure the outer single quotes protect the string from shell interpretation. -## Other instructions +For arguments containing single quotes, prefer double-quoting the argument inside the command string. -| Tech | Instruction file | -|------------|-------------------------------------------------------------| -| PowerShell | [pwsh.instructions.md](./instructions/pwsh.instructions.md) | -| Markdown | [md.instructions.md](./instructions/md.instructions.md) | +Example: `pwsh -Command 'Write-Host "I''m Groot"'` diff --git a/.github/prompts/analyze.prompt.md b/.github/prompts/PSModule.analyze.prompt.md similarity index 100% rename from .github/prompts/analyze.prompt.md rename to .github/prompts/PSModule.analyze.prompt.md diff --git a/.github/prompts/clarify.prompt.md b/.github/prompts/PSModule.clarify.prompt.md similarity index 100% rename from .github/prompts/clarify.prompt.md rename to .github/prompts/PSModule.clarify.prompt.md diff --git a/.github/prompts/constitution.prompt.md b/.github/prompts/PSModule.constitution.prompt.md similarity index 100% rename from .github/prompts/constitution.prompt.md rename to .github/prompts/PSModule.constitution.prompt.md diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/PSModule.implement.prompt.md similarity index 100% rename from .github/prompts/implement.prompt.md rename to .github/prompts/PSModule.implement.prompt.md diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/PSModule.plan.prompt.md similarity index 100% rename from .github/prompts/plan.prompt.md rename to .github/prompts/PSModule.plan.prompt.md diff --git a/.github/prompts/pr.prompt.md b/.github/prompts/PSModule.pr.prompt.md similarity index 100% rename from .github/prompts/pr.prompt.md rename to .github/prompts/PSModule.pr.prompt.md diff --git a/.github/prompts/specify.prompt.md b/.github/prompts/PSModule.specify.prompt.md similarity index 100% rename from .github/prompts/specify.prompt.md rename to .github/prompts/PSModule.specify.prompt.md diff --git a/.github/prompts/tasks.prompt.md b/.github/prompts/PSModule.tasks.prompt.md similarity index 100% rename from .github/prompts/tasks.prompt.md rename to .github/prompts/PSModule.tasks.prompt.md diff --git a/.github/workflows/AfterAll-ModuleLocal.yml b/.github/workflows/AfterAll-ModuleLocal.yml new file mode 100644 index 00000000..669e136e --- /dev/null +++ b/.github/workflows/AfterAll-ModuleLocal.yml @@ -0,0 +1,56 @@ +name: AfterAll-ModuleLocal + +on: + workflow_call: + inputs: + Settings: + type: string + description: The complete settings object including test suites. + required: true + +permissions: + contents: read # to checkout the repo + +jobs: + AfterAll-ModuleLocal: + name: AfterAll-ModuleLocal + runs-on: ubuntu-latest + env: + SETTINGS: ${{ inputs.Settings }} + steps: + - name: Checkout Code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Run AfterAll Teardown Scripts + if: always() + uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 + with: + Name: AfterAll-ModuleLocal + ShowInfo: false + ShowOutput: true + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} + Script: | + LogGroup "Running AfterAll Teardown Scripts" { + $afterAllScript = 'tests/AfterAll.ps1' + + if (-not (Test-Path $afterAllScript)) { + Write-Host "No AfterAll.ps1 script found at [$afterAllScript] - exiting successfully" + exit 0 + } + + Write-Host "Running AfterAll teardown script: $afterAllScript" + try { + & $afterAllScript + Write-Host "AfterAll script completed successfully: $afterAllScript" + } catch { + Write-Warning "AfterAll script failed: $afterAllScript - $_" + # Don't throw for teardown scripts to ensure other cleanup scripts can run + } + } diff --git a/.github/workflows/BeforeAll-ModuleLocal.yml b/.github/workflows/BeforeAll-ModuleLocal.yml new file mode 100644 index 00000000..706738c4 --- /dev/null +++ b/.github/workflows/BeforeAll-ModuleLocal.yml @@ -0,0 +1,55 @@ +name: BeforeAll-ModuleLocal + +on: + workflow_call: + inputs: + Settings: + type: string + description: The complete settings object including test suites. + required: true + +permissions: + contents: read # to checkout the repo + +jobs: + BeforeAll-ModuleLocal: + name: BeforeAll-ModuleLocal + runs-on: ubuntu-latest + env: + Settings: ${{ inputs.Settings }} + steps: + - name: Checkout Code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Run BeforeAll Setup Scripts + uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 + with: + Name: BeforeAll-ModuleLocal + ShowInfo: false + ShowOutput: true + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} + Script: | + LogGroup "Running BeforeAll Setup Scripts" { + $beforeAllScript = 'tests/BeforeAll.ps1' + + if (-not (Test-Path $beforeAllScript)) { + Write-Host "No BeforeAll.ps1 script found at [$beforeAllScript] - exiting successfully" + exit 0 + } + + Write-Host "Running BeforeAll setup script: $beforeAllScript" + try { + & $beforeAllScript + Write-Host "BeforeAll script completed successfully: $beforeAllScript" + } catch { + Write-Error "BeforeAll script failed: $beforeAllScript - $_" + throw + } + } diff --git a/.github/workflows/Build-Docs.yml b/.github/workflows/Build-Docs.yml index 50cb096c..dd8094f1 100644 --- a/.github/workflows/Build-Docs.yml +++ b/.github/workflows/Build-Docs.yml @@ -3,40 +3,10 @@ name: Build-Docs on: workflow_call: inputs: - Name: + Settings: type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - 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 working directory where the script will run from. - required: false - default: '.' - ShowSummaryOnSuccess: - type: boolean - description: Whether to show the super-linter summary on success. - required: false - default: false + description: The complete settings object. + required: true permissions: contents: read # to checkout the repo @@ -57,30 +27,30 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: module - path: ${{ inputs.WorkingDirectory }}/outputs/module + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Document module uses: PSModule/Document-PSModule@15dc407c99e408fc0a4023d4f16aee2a5507ba74 # v1.0.12 with: - Name: ${{ inputs.Name }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Name: ${{ fromJson(inputs.Settings).Name }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} - name: Upload docs artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: docs - path: ${{ inputs.WorkingDirectory }}/outputs/docs + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs if-no-files-found: error retention-days: 1 - name: Commit all changes uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} Script: | # Rename the gitignore file to .gitignore.bak if (Test-Path -Path .gitignore) { @@ -106,8 +76,8 @@ jobs: env: RUN_LOCAL: true DEFAULT_BRANCH: main - DEFAULT_WORKSPACE: ${{ inputs.WorkingDirectory }} - FILTER_REGEX_INCLUDE: ${{ inputs.WorkingDirectory }}/outputs/docs + DEFAULT_WORKSPACE: ${{ fromJson(inputs.Settings).WorkingDirectory }} + FILTER_REGEX_INCLUDE: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs ENABLE_GITHUB_ACTIONS_GROUP_TITLE: true GITHUB_TOKEN: ${{ github.token }} VALIDATE_ALL_CODEBASE: true @@ -123,7 +93,7 @@ jobs: SAVE_SUPER_LINTER_SUMMARY: true - name: Post super-linter summary - if: failure() || inputs.ShowSummaryOnSuccess == true + if: failure() || fromJson(inputs.Settings).Build.Docs.ShowSummaryOnSuccess == true shell: pwsh run: | $summaryPath = Join-Path $env:GITHUB_WORKSPACE 'super-linter-output' 'super-linter-summary.md' diff --git a/.github/workflows/Build-Module.yml b/.github/workflows/Build-Module.yml index 1caec07f..11248d2c 100644 --- a/.github/workflows/Build-Module.yml +++ b/.github/workflows/Build-Module.yml @@ -3,20 +3,15 @@ name: Build-Module on: workflow_call: inputs: - Name: + Settings: type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - required: false + description: The settings object as a JSON string. + required: true ArtifactName: type: string description: Name of the artifact to upload. required: false default: module - WorkingDirectory: - type: string - description: The working directory where the script will run from. - required: false - default: '.' permissions: contents: read # to checkout the repository @@ -37,6 +32,6 @@ jobs: - name: Build module uses: PSModule/Build-PSModule@fe8cc14a7192066cc46cb9514659772ebde05849 # v4.0.9 with: - Name: ${{ inputs.Name }} + Name: ${{ fromJson(inputs.Settings).Name }} ArtifactName: ${{ inputs.ArtifactName }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index ed1d2afd..94be506a 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -3,35 +3,10 @@ name: Build-Site on: workflow_call: inputs: - Name: + Settings: type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - 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 working directory where the script will run from. - required: false - default: '.' + description: The complete settings object. + required: true permissions: contents: read # to checkout the repo @@ -54,7 +29,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: docs - path: ${{ inputs.WorkingDirectory }}/outputs/docs + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs - name: Install mkdocs-material shell: pwsh @@ -67,16 +42,16 @@ jobs: - name: Structure site uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} Script: | LogGroup "Get folder structure" { $functionDocsFolder = New-Item -Path "outputs/site/docs/Functions" -ItemType Directory -Force Copy-Item -Path "outputs/docs/*" -Destination "outputs/site/docs/Functions" -Recurse -Force - $moduleName = [string]::IsNullOrEmpty('${{ inputs.Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ inputs.Name }}' + $moduleName = [string]::IsNullOrEmpty('${{ fromJson(inputs.Settings).Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ fromJson(inputs.Settings).Name }}' $ModuleSourcePath = Resolve-Path 'src' | Select-Object -ExpandProperty Path $SiteOutputPath = Resolve-Path 'outputs/site' | Select-Object -ExpandProperty Path @@ -162,7 +137,7 @@ jobs: } - name: Build mkdocs-material project - working-directory: ${{ inputs.WorkingDirectory }}/outputs/site + working-directory: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/site shell: pwsh run: | LogGroup 'Build docs - mkdocs build - content' { @@ -176,5 +151,5 @@ jobs: - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: name: github-pages - path: ${{ inputs.WorkingDirectory }}/_site + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/_site retention-days: 1 diff --git a/.github/workflows/Get-CodeCoverage.yml b/.github/workflows/Get-CodeCoverage.yml index 26c7f2c6..c473c91b 100644 --- a/.github/workflows/Get-CodeCoverage.yml +++ b/.github/workflows/Get-CodeCoverage.yml @@ -3,41 +3,10 @@ name: Get-CodeCoverage on: workflow_call: inputs: - StepSummary_Mode: + Settings: type: string - description: | - Controls which sections to show in the GitHub step summary. - Use 'Full' for all sections, 'None' to disable, or a comma-separated list of 'Missed, Executed, Files'. - required: false - default: Missed, Files - CodeCoveragePercentTarget: - type: number - description: The target for code coverage. - 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 working directory where the script will run from. - required: false - default: '.' + description: The complete settings object. + required: true permissions: contents: read # to checkout the repo @@ -51,10 +20,9 @@ jobs: uses: PSModule/Get-PesterCodeCoverage@a7923eefbf55b452f9b1534c5b50ca9bd192f810 # v1.0.3 id: Get-CodeCoverage with: - CodeCoveragePercentTarget: ${{ inputs.CodeCoveragePercentTarget }} - StepSummary_Mode: ${{ inputs.StepSummary_Mode }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + CodeCoveragePercentTarget: ${{ fromJson(inputs.Settings).Test.CodeCoverage.PercentTarget }} + StepSummary_Mode: ${{ fromJson(inputs.Settings).Test.CodeCoverage.StepSummaryMode }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} diff --git a/.github/workflows/Get-Settings.yml b/.github/workflows/Get-Settings.yml index f3063d4f..ff923ca9 100644 --- a/.github/workflows/Get-Settings.yml +++ b/.github/workflows/Get-Settings.yml @@ -3,10 +3,6 @@ name: Get-Settings on: workflow_call: inputs: - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - required: false SettingsPath: type: string description: The path to the settings file. @@ -39,17 +35,8 @@ on: outputs: Settings: - description: The path to the settings file. + description: The complete settings object including test suites value: ${{ jobs.Get-Settings.outputs.Settings }} - SourceCodeTestSuites: - description: Source Code PSModule test suites to run. - value: ${{ jobs.Get-Settings.outputs.SourceCodeTestSuites }} - PSModuleTestSuites: - description: Module PSModule test suites to run. - value: ${{ jobs.Get-Settings.outputs.PSModuleTestSuites }} - ModuleTestSuites: - description: Module local test suites to run. - value: ${{ jobs.Get-Settings.outputs.ModuleTestSuites }} permissions: contents: read # to checkout the repo @@ -59,10 +46,7 @@ jobs: name: Get-Settings runs-on: ubuntu-latest outputs: - Settings: ${{ fromJson(steps.Get-Settings.outputs.result).Settings }} - SourceCodeTestSuites: ${{ fromJson(steps.Get-Settings.outputs.result).SourceCodeTestSuites }} - PSModuleTestSuites: ${{ fromJson(steps.Get-Settings.outputs.result).PSModuleTestSuites }} - ModuleTestSuites: ${{ fromJson(steps.Get-Settings.outputs.result).ModuleTestSuites }} + Settings: ${{ steps.Get-Settings.outputs.Settings }} steps: - name: Checkout Code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -71,305 +55,12 @@ jobs: fetch-depth: 0 - name: Get-Settings - uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 + uses: PSModule/Get-PSModuleSettings@77c3c599734effaecc3a868a34bf39ab0ca7f0a0 # v1.0.0 id: Get-Settings - env: - PSMODULE_GET_SETTINGS_INPUT_Name: ${{ inputs.Name }} - PSMODULE_GET_SETTINGS_INPUT_SettingsPath: ${{ inputs.SettingsPath }} with: - Name: Get-Settings - ShowInfo: false - ShowOutput: true + SettingsPath: ${{ inputs.SettingsPath }} Debug: ${{ inputs.Debug }} Prerelease: ${{ inputs.Prerelease }} Verbose: ${{ inputs.Verbose }} Version: ${{ inputs.Version }} WorkingDirectory: ${{ inputs.WorkingDirectory }} - Script: | - 'powershell-yaml' | Install-PSResource -Repository PSGallery -TrustRepository -Reinstall - - $inputName = $env:PSMODULE_GET_SETTINGS_INPUT_Name - $settingsPath = $env:PSMODULE_GET_SETTINGS_INPUT_SettingsPath - LogGroup "Inputs" { - [pscustomobject]@{ - PWD = (Get-Location).Path - Name = $inputName - SettingsPath = $settingsPath - } | Format-List | Out-String - } - - if (![string]::IsNullOrEmpty($settingsPath) -and (Test-Path -Path $settingsPath)) { - LogGroup "Import settings" { - $settingsFile = Get-Item -Path $settingsPath - $relativeSettingsPath = $settingsFile | Resolve-Path -Relative - Write-Host "Importing settings from [$relativeSettingsPath]" - $content = $settingsFile | Get-Content -Raw - switch -Regex ($settingsFile.Extension) { - '.json' { - $settings = $content | ConvertFrom-Json - Write-Host ($settings | ConvertTo-Json -Depth 5 | Out-String) - } - '.yaml|.yml' { - $settings = $content | ConvertFrom-Yaml - Write-Host ($settings | ConvertTo-Yaml | Out-String) - } - '.psd1' { - $settings = $content | ConvertFrom-Hashtable - Write-Host ($settings | ConvertTo-Hashtable | Format-Hashtable | Out-String) - } - default { - throw "Unsupported settings file format: [$settingsPath]. Supported formats are json, yaml/yml and psd1." - } - } - } - } else { - Write-Host 'No settings file present.' - $settings = @{} - } - - LogGroup "Name" { - [pscustomobject]@{ - InputName = $inputName - SettingsName = $settings.Name - RepositoryName = $env:GITHUB_REPOSITORY_NAME - } | Format-List | Out-String - - if (![string]::IsNullOrEmpty($inputName)) { - $name = $inputName - } elseif (![string]::IsNullOrEmpty($settings.Name)) { - $name = $settings.Name - } else { - $name = $env:GITHUB_REPOSITORY_NAME - } - - Write-Host "Using [$name] as the module name." - } - - $settings = [pscustomobject]@{ - Name = $name - Test = [pscustomobject]@{ - Skip = $settings.Test.Skip ?? $false - Linux = [pscustomobject]@{ - Skip = $settings.Test.Linux.Skip ?? $false - } - MacOS = [pscustomobject]@{ - Skip = $settings.Test.MacOS.Skip ?? $false - } - Windows = [pscustomobject]@{ - Skip = $settings.Test.Windows.Skip ?? $false - } - SourceCode = [pscustomobject]@{ - Skip = $settings.Test.SourceCode.Skip ?? $false - Linux = [pscustomobject]@{ - Skip = $settings.Test.SourceCode.Linux.Skip ?? $false - } - MacOS = [pscustomobject]@{ - Skip = $settings.Test.SourceCode.MacOS.Skip ?? $false - } - Windows = [pscustomobject]@{ - Skip = $settings.Test.SourceCode.Windows.Skip ?? $false - } - } - PSModule = [pscustomobject]@{ - Skip = $settings.Test.PSModule.Skip ?? $false - Linux = [pscustomobject]@{ - Skip = $settings.Test.PSModule.Linux.Skip ?? $false - } - MacOS = [pscustomobject]@{ - Skip = $settings.Test.PSModule.MacOS.Skip ?? $false - } - Windows = [pscustomobject]@{ - Skip = $settings.Test.PSModule.Windows.Skip ?? $false - } - } - Module = [pscustomobject]@{ - Skip = $settings.Test.Module.Skip ?? $false - Linux = [pscustomobject]@{ - Skip = $settings.Test.Module.Linux.Skip ?? $false - } - MacOS = [pscustomobject]@{ - Skip = $settings.Test.Module.MacOS.Skip ?? $false - } - Windows = [pscustomobject]@{ - Skip = $settings.Test.Module.Windows.Skip ?? $false - } - } - TestResults = [pscustomobject]@{ - Skip = $settings.Test.TestResults.Skip ?? $false - } - CodeCoverage = [pscustomobject]@{ - Skip = $settings.Test.CodeCoverage.Skip ?? $false - PercentTarget = $settings.Test.CodeCoverage.PercentTarget ?? 0 - StepSummaryMode = $settings.Test.CodeCoverage.StepSummary_Mode ?? 'Missed, Files' - } - } - Build = [pscustomobject]@{ - Skip = $settings.Build.Skip ?? $false - Module = [pscustomobject]@{ - Skip = $settings.Build.Module.Skip ?? $false - } - Docs = [pscustomobject]@{ - Skip = $settings.Build.Docs.Skip ?? $false - ShowSummaryOnSuccess = $settings.Build.Docs.ShowSummaryOnSuccess ?? $false - } - Site = [pscustomobject]@{ - Skip = $settings.Build.Site.Skip ?? $false - } - } - Publish = [pscustomobject]@{ - Module = [pscustomobject]@{ - Skip = $settings.Publish.Module.Skip ?? $false - AutoCleanup = $settings.Publish.Module.AutoCleanup ?? $true - AutoPatching = $settings.Publish.Module.AutoPatching ?? $true - IncrementalPrerelease = $settings.Publish.Module.IncrementalPrerelease ?? $true - DatePrereleaseFormat = $settings.Publish.Module.DatePrereleaseFormat ?? '' - VersionPrefix = $settings.Publish.Module.VersionPrefix ?? 'v' - MajorLabels = $settings.Publish.Module.MajorLabels ?? 'major, breaking' - MinorLabels = $settings.Publish.Module.MinorLabels ?? 'minor, feature' - PatchLabels = $settings.Publish.Module.PatchLabels ?? 'patch, fix' - IgnoreLabels = $settings.Publish.Module.IgnoreLabels ?? 'NoRelease' - } - - } - Linter = [pscustomobject]@{ - Skip = $settings.Linter.Skip ?? $false - ShowSummaryOnSuccess = $settings.Linter.ShowSummaryOnSuccess ?? $false - env = $settings.Linter.env ?? @{} - } - } - LogGroup "Final settings" { - switch -Regex ($settingsFile.Extension) { - '.yaml|.yml' { - Write-Host ($settings | ConvertTo-Yaml | Out-String) - } - '.psd1' { - Write-Host ($settings | ConvertTo-Hashtable | Format-Hashtable | Out-String) - } - default { - Write-Host ($settings | ConvertTo-Json -Depth 5 | Out-String) - } - } - } - - Set-GitHubOutput -Name Settings -Value ($settings | ConvertTo-Json -Depth 10) - - # Get-TestSuites - if ($settings.Test.Skip) { - Write-Host 'Skipping all tests.' - Set-GitHubOutput -Name SourceCodeTestSuites -Value '[]' - Set-GitHubOutput -Name PSModuleTestSuites -Value '[]' - Set-GitHubOutput -Name ModuleTestSuites -Value '[]' - exit 0 - } - - # Define test configurations as an array of hashtables. - $linux = [PSCustomObject]@{ RunsOn = 'ubuntu-latest'; OSName = 'Linux' } - $macOS = [PSCustomObject]@{ RunsOn = 'macos-latest'; OSName = 'macOS' } - $windows = [PSCustomObject]@{ RunsOn = 'windows-latest'; OSName = 'Windows' } - - LogGroup 'Source Code Test Suites:' { - $sourceCodeTestSuites = if ($settings.Test.SourceCode.Skip) { - Write-Host 'Skipping all source code tests.' - } else { - if (-not $settings.Test.Linux.Skip -and -not $settings.Test.SourceCode.Linux.Skip) { $linux } - if (-not $settings.Test.MacOS.Skip -and -not $settings.Test.SourceCode.MacOS.Skip) { $macOS } - if (-not $settings.Test.Windows.Skip -and -not $settings.Test.SourceCode.Windows.Skip) { $windows } - } - $sourceCodeTestSuites | Format-Table -AutoSize | Out-String - $sourceCodeTestSuites = ($null -ne $sourceCodeTestSuites) ? ($sourceCodeTestSuites | ConvertTo-Json -AsArray) : '[]' - Set-GitHubOutput -Name SourceCodeTestSuites -Value $sourceCodeTestSuites - } - - LogGroup 'PSModule Test Suites:' { - $psModuleTestSuites = if ($settings.Test.PSModule.Skip) { - Write-Host 'Skipping all PSModule tests.' - } else { - if (-not $settings.Test.Linux.Skip -and -not $settings.Test.PSModule.Linux.Skip) { $linux } - if (-not $settings.Test.MacOS.Skip -and -not $settings.Test.PSModule.MacOS.Skip) { $macOS } - if (-not $settings.Test.Windows.Skip -and -not $settings.Test.PSModule.Windows.Skip) { $windows } - } - $psModuleTestSuites | Format-Table -AutoSize | Out-String - $psModuleTestSuites = ($null -ne $psModuleTestSuites) ? ($psModuleTestSuites | ConvertTo-Json -AsArray) : '[]' - Set-GitHubOutput -Name PSModuleTestSuites -Value $psModuleTestSuites - } - - LogGroup 'Module Local Test Suites:' { - $moduleTestSuites = if ($settings.Test.Module.Skip) { - Write-Host 'Skipping all module tests.' - } else { - # Locate the tests directory. - $testsPath = Resolve-Path 'tests' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path - if (-not $testsPath) { - Write-Warning 'No tests found' - } - Write-Host "Tests found at [$testsPath]" - - function Get-TestItemsFromFolder { - param ([string]$FolderPath) - - $configFiles = Get-ChildItem -Path $FolderPath -File -Filter '*.Configuration.ps1' - if ($configFiles.Count -eq 1) { - return @($configFiles) - } elseif ($configFiles.Count -gt 1) { - throw "Multiple configuration files found in [$FolderPath]. Please separate configurations into different folders." - } - - $containerFiles = Get-ChildItem -Path $FolderPath -File -Filter '*.Container.ps1' - if ($containerFiles.Count -ge 1) { - return $containerFiles - } - - $testFiles = Get-ChildItem -Path $FolderPath -File -Filter '*.Tests.ps1' - return $testFiles - } - - function Find-TestDirectories { - param ([string]$Path) - - $directories = @() - $childDirs = Get-ChildItem -Path $Path -Directory - - foreach ($dir in $childDirs) { - $directories += $dir.FullName - $directories += Find-TestDirectories -Path $dir.FullName - } - - return $directories - } - - $allTestFolders = @($testsPath) + (Find-TestDirectories -Path $testsPath) - - foreach ($folder in $allTestFolders) { - $testItems = Get-TestItemsFromFolder -FolderPath $folder - foreach ($item in $testItems) { - if (-not $settings.Test.Linux.Skip -and -not $settings.Test.Module.Linux.Skip) { - [pscustomobject]@{ - RunsOn = $linux.RunsOn - OSName = $linux.OSName - TestPath = Resolve-Path -Path $item.FullName -Relative - TestName = ($item.BaseName).Split('.')[0] - } - } - if (-not $settings.Test.MacOS.Skip -and -not $settings.Test.Module.MacOS.Skip) { - [pscustomobject]@{ - RunsOn = $macOS.RunsOn - OSName = $macOS.OSName - TestPath = Resolve-Path -Path $item.FullName -Relative - TestName = ($item.BaseName).Split('.')[0] - } - } - if (-not $settings.Test.Windows.Skip -and -not $settings.Test.Module.Windows.Skip) { - [pscustomobject]@{ - RunsOn = $windows.RunsOn - OSName = $windows.OSName - TestPath = Resolve-Path -Path $item.FullName -Relative - TestName = ($item.BaseName).Split('.')[0] - } - } - } - } - } - $moduleTestSuites | Format-Table -AutoSize | Out-String - $moduleTestSuites = ($null -ne $moduleTestSuites) ? ($moduleTestSuites | ConvertTo-Json -AsArray) : '[]' - Set-GitHubOutput -Name ModuleTestSuites -Value $moduleTestSuites - } diff --git a/.github/workflows/Get-TestResults.yml b/.github/workflows/Get-TestResults.yml index 52fc82b5..d7f2988a 100644 --- a/.github/workflows/Get-TestResults.yml +++ b/.github/workflows/Get-TestResults.yml @@ -3,43 +3,10 @@ name: Get-TestResults on: workflow_call: inputs: - SourceCodeTestSuites: + Settings: type: string - description: The test suites to run for the source code. + description: The complete settings object including test suites. required: true - PSModuleTestSuites: - type: string - description: The test suites to run for the PSModule. - required: true - ModuleTestSuites: - type: string - description: The test suites to run for the module. - required: true - 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 working directory where the script will run from. - required: false - default: '.' permissions: contents: read # to checkout the repo @@ -53,11 +20,10 @@ jobs: uses: PSModule/Get-PesterTestResults@0c1d8cde9575b192831f76e87d3f7e825a7d8ff4 # v1.0.7 id: Get-TestResults with: - SourceCodeTestSuites: ${{ inputs.SourceCodeTestSuites }} - PSModuleTestSuites: ${{ inputs.PSModuleTestSuites }} - ModuleTestSuites: ${{ inputs.ModuleTestSuites }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + SourceCodeTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.SourceCode) }} + PSModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.PSModule) }} + ModuleTestSuites: ${{ toJSON(fromJson(inputs.Settings).TestSuites.Module) }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} diff --git a/.github/workflows/Lint-Repository.yml b/.github/workflows/Lint-Repository.yml new file mode 100644 index 00000000..4537a0ee --- /dev/null +++ b/.github/workflows/Lint-Repository.yml @@ -0,0 +1,70 @@ +name: Lint-Repository + +on: + workflow_call: + inputs: + Settings: + type: string + description: The settings object as a JSON string. + required: true + +permissions: + contents: read # to checkout the repository + statuses: write # to update the status of the workflow from linter + +jobs: + Lint-Repository: + name: Lint code base + runs-on: ubuntu-latest + env: + Settings: ${{ inputs.Settings }} + steps: + - name: Checkout repo + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Load dynamic envs + shell: pwsh + run: | + Write-Host "Loading settings for super-linter:" + $settings = $env:Settings | ConvertFrom-Json -AsHashtable + $linter = $settings.Linter + $env = $linter.env + + foreach ($key in $env.Keys) { + $value = $env[$key] + + if ($value -is [bool]) { + $value = $value.ToString().ToLower() + } + + Write-Host "$key = $value" + + # Persist for following steps in this job + "$key=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + } + + - name: Lint code base + id: super-linter + uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.3.1 + env: + GITHUB_TOKEN: ${{ github.token }} + DEFAULT_WORKSPACE: ${{ fromJson(env.Settings).WorkingDirectory }} + FILTER_REGEX_INCLUDE: ${{ fromJson(env.Settings).WorkingDirectory }} + ENABLE_GITHUB_ACTIONS_STEP_SUMMARY: false + SAVE_SUPER_LINTER_SUMMARY: true + + - name: Post super-linter summary + if: failure() || fromJson(env.Settings).Linter.ShowSummaryOnSuccess == true + shell: pwsh + run: | + $summaryPath = Join-Path $env:GITHUB_WORKSPACE 'super-linter-output' 'super-linter-summary.md' + Get-Content $summaryPath | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 + + $failed = '${{ steps.super-linter.outcome }}' -eq 'failure' + if ($failed) { + Write-Host "::error::Super-linter found issues. Please review the summary above." + exit 1 + } diff --git a/.github/workflows/Lint-SourceCode.yml b/.github/workflows/Lint-SourceCode.yml index da8e20d6..bbe96827 100644 --- a/.github/workflows/Lint-SourceCode.yml +++ b/.github/workflows/Lint-SourceCode.yml @@ -3,51 +3,22 @@ name: Lint-SourceCode on: workflow_call: inputs: - RunsOn: + Settings: type: string - description: The type of runner to use for the job. + description: The settings object as a JSON string. required: true - OS: - type: string - description: The operating system name. - required: true - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - 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 working directory where the script will run from. - required: false - default: '.' permissions: contents: read # to checkout the repo and create releases on the repo jobs: Lint-SourceCode: - name: Lint-SourceCode (${{ inputs.RunsOn }}) - runs-on: ${{ inputs.RunsOn }} + name: Lint-SourceCode (${{ matrix.OSName }}) + runs-on: ${{ matrix.RunsOn }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(inputs.Settings).TestSuites.SourceCode }} steps: - name: Checkout Code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -57,11 +28,11 @@ jobs: - name: Lint-SourceCode uses: PSModule/Invoke-ScriptAnalyzer@0b13023a981f4c94136bba6193a9abd2d936cbc1 # v4.1.1 with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} Path: src - WorkingDirectory: ${{ inputs.WorkingDirectory }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} TestResult_Enabled: true TestResult_TestSuiteName: PSModuleLint-SourceCode-${{ runner.os }} diff --git a/.github/workflows/Publish-Module.yml b/.github/workflows/Publish-Module.yml new file mode 100644 index 00000000..72ce83d7 --- /dev/null +++ b/.github/workflows/Publish-Module.yml @@ -0,0 +1,60 @@ +name: Publish-Module + +on: + workflow_call: + secrets: + APIKey: + description: The API key for the PowerShell Gallery. + required: true + inputs: + Settings: + type: string + description: The complete settings object. + required: true + +permissions: + contents: read # to checkout the repo + +jobs: + Publish-Module: + name: Publish-Module + runs-on: ubuntu-latest + env: + SETTINGS: ${{ inputs.Settings }} + steps: + - name: Checkout Code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Download module artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: module + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module + + - name: Update Microsoft.PowerShell.PSResourceGet + shell: pwsh + run: | + Install-PSResource -Name Microsoft.PowerShell.PSResourceGet -Repository PSGallery -TrustRepository + + - name: Publish module + uses: PSModule/Publish-PSModule@6c25d139fe51b890f75c057897bd58ac344b192a # v2.0.8 + env: + GH_TOKEN: ${{ github.token }} + with: + Name: ${{ fromJson(inputs.Settings).Name }} + ModulePath: outputs/module + 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 }} + 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 }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} diff --git a/.github/workflows/Publish-Site.yml b/.github/workflows/Publish-Site.yml new file mode 100644 index 00000000..235f8d0f --- /dev/null +++ b/.github/workflows/Publish-Site.yml @@ -0,0 +1,29 @@ +name: Publish-Site + +on: + workflow_call: + inputs: + Settings: + type: string + description: The complete settings object. + required: true + +permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + +jobs: + Publish-Site: + name: Publish-Site + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + env: + Settings: ${{ inputs.Settings }} + steps: + - uses: actions/configure-pages@v5 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/Test-Module.yml b/.github/workflows/Test-Module.yml index 033f3205..bcedef37 100644 --- a/.github/workflows/Test-Module.yml +++ b/.github/workflows/Test-Module.yml @@ -25,43 +25,10 @@ on: description: The classic personal access token for running tests. required: false inputs: - RunsOn: + Settings: type: string - description: The type of runner to use for the job. + description: The settings object as a JSON string. required: true - OS: - type: string - description: The operating system name. - required: true - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - 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 working directory where the script will run from. - required: false - default: '.' env: TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} @@ -77,8 +44,12 @@ permissions: jobs: Test-Module: - name: Test-Module (${{ inputs.RunsOn }}) - runs-on: ${{ inputs.RunsOn }} + name: Test-Module (${{ matrix.OSName }}) + runs-on: ${{ matrix.RunsOn }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(inputs.Settings).TestSuites.PSModule }} steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -89,22 +60,26 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: module - path: ${{ inputs.WorkingDirectory }}/outputs/module + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Test-Module uses: PSModule/Test-PSModule@7d94ed751a60973867e84ea8d44521e94b7c485d # v3.0.7 with: - Name: ${{ inputs.Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Name: ${{ fromJson(inputs.Settings).Name }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} Settings: Module Lint-Module: - name: Lint-Module (${{ inputs.RunsOn }}) - runs-on: ${{ inputs.RunsOn }} + name: Lint-Module (${{ matrix.OSName }}) + runs-on: ${{ matrix.RunsOn }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(inputs.Settings).TestSuites.PSModule }} steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -115,16 +90,16 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: module - path: ${{ inputs.WorkingDirectory }}/outputs/module + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Lint-Module uses: PSModule/Invoke-ScriptAnalyzer@0b13023a981f4c94136bba6193a9abd2d936cbc1 # v4.1.1 with: Path: outputs/module - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} TestResult_Enabled: true TestResult_TestSuiteName: PSModuleLint-Module-${{ runner.os }} diff --git a/.github/workflows/Test-ModuleLocal.yml b/.github/workflows/Test-ModuleLocal.yml index a0bc403e..63b3c1f9 100644 --- a/.github/workflows/Test-ModuleLocal.yml +++ b/.github/workflows/Test-ModuleLocal.yml @@ -25,53 +25,10 @@ on: description: The classic personal access token for running tests. required: false inputs: - RunsOn: + Settings: type: string - description: The type of runner to use for the job. + description: The settings object as a JSON string. required: true - OSName: - type: string - description: The operating system name. - required: true - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - required: false - TestPath: - type: string - description: The path to the tests folder. - required: false - default: tests - TestName: - type: string - description: The name of the test suite. - required: false - default: tests - 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 working directory where the script will run from. - required: false - default: '.' permissions: contents: read # to checkout the repo and create releases on the repo @@ -88,8 +45,12 @@ env: jobs: Test-ModuleLocal: - name: Test-${{ inputs.TestName }} (${{ inputs.RunsOn }}) - runs-on: ${{ inputs.RunsOn }} + name: Test-${{ matrix.TestName }} (${{ matrix.OSName }}) + runs-on: ${{ matrix.RunsOn }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(inputs.Settings).TestSuites.Module }} steps: - name: Checkout Code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -101,7 +62,7 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: module - path: ${{ inputs.WorkingDirectory }}/outputs/module + path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Install-PSModuleHelpers uses: PSModule/Install-PSModuleHelpers@d60d63e4be477d1ca0c67c6085101fb109bce8f1 # v1.0.6 @@ -109,7 +70,7 @@ jobs: - name: Import-Module id: import-module shell: pwsh - working-directory: ${{ inputs.WorkingDirectory }} + working-directory: ${{ fromJson(inputs.Settings).WorkingDirectory }} run: | $name = Get-ChildItem "outputs/module" | Select-Object -ExpandProperty Name $path = Install-PSModule -Path "outputs/module/$name" -PassThru @@ -120,20 +81,20 @@ jobs: - name: Test-ModuleLocal uses: PSModule/Invoke-Pester@882994cbe1ff07c3fc8afdac52404c940f99b331 # v4.2.2 with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - TestResult_TestSuiteName: ${{ inputs.TestName }}-${{ inputs.OSName }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + TestResult_TestSuiteName: ${{ matrix.TestName }}-${{ matrix.OSName }} TestResult_Enabled: true CodeCoverage_Enabled: true Output_Verbosity: Detailed CodeCoverage_OutputFormat: JaCoCo CodeCoverage_CoveragePercentTarget: 0 Filter_ExcludeTag: Flaky - Path: ${{ inputs.TestPath }} + Path: ${{ matrix.TestPath }} Run_Path: ${{ steps.import-module.outputs.path }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + 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 999.0.0 diff --git a/.github/workflows/Test-SourceCode.yml b/.github/workflows/Test-SourceCode.yml index adf6ccb1..186b6f16 100644 --- a/.github/workflows/Test-SourceCode.yml +++ b/.github/workflows/Test-SourceCode.yml @@ -3,51 +3,22 @@ name: Test-SourceCode on: workflow_call: inputs: - RunsOn: + Settings: type: string - description: The type of runner to use for the job. + description: The settings object as a JSON string. required: true - OS: - type: string - description: The operating system name. - required: true - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - 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 working directory where the script will run from. - required: false - default: '.' permissions: contents: read # to checkout the repo and create releases on the repo jobs: Test-SourceCode: - name: Test-SourceCode (${{ inputs.RunsOn }}) - runs-on: ${{ inputs.RunsOn }} + name: Test-SourceCode (${{ matrix.OSName }}) + runs-on: ${{ matrix.RunsOn }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(inputs.Settings).TestSuites.SourceCode }} steps: - name: Checkout Code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -58,9 +29,9 @@ jobs: - name: Test-SourceCode uses: PSModule/Test-PSModule@7d94ed751a60973867e84ea8d44521e94b7c485d # v3.0.7 with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Debug: ${{ fromJson(inputs.Settings).Debug }} + Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} + Verbose: ${{ fromJson(inputs.Settings).Verbose }} + Version: ${{ fromJson(inputs.Settings).Version }} + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} Settings: SourceCode diff --git a/.github/workflows/Workflow-Test-Default.yml b/.github/workflows/Workflow-Test-Default.yml index bd82710f..36d41d9a 100644 --- a/.github/workflows/Workflow-Test-Default.yml +++ b/.github/workflows/Workflow-Test-Default.yml @@ -25,4 +25,3 @@ jobs: secrets: inherit with: WorkingDirectory: tests/srcTestRepo - Name: PSModuleTest2 diff --git a/.github/workflows/Workflow-Test-WithManifest.yml b/.github/workflows/Workflow-Test-WithManifest.yml index 85409571..3d747300 100644 --- a/.github/workflows/Workflow-Test-WithManifest.yml +++ b/.github/workflows/Workflow-Test-WithManifest.yml @@ -25,4 +25,3 @@ jobs: secrets: inherit with: WorkingDirectory: tests/srcWithManifestTestRepo - SettingsPath: .github/PSModule.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 3899af5a..fd3fccd4 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -28,10 +28,6 @@ on: description: The classic personal access token for running tests. required: false inputs: - Name: - type: string - description: The name of the module to process. Scripts default to the repository name if nothing is specified. - required: false SettingsPath: type: string description: The path to the settings file. Settings in the settings file take precedence over the action inputs. @@ -70,10 +66,6 @@ permissions: pages: write # to deploy to Pages id-token: write # to verify the deployment originates from an appropriate source -defaults: - run: - shell: pwsh - jobs: # Runs on: # - ✅ Open/Updated PR - Always runs to load configuration @@ -83,7 +75,6 @@ jobs: Get-Settings: uses: ./.github/workflows/Get-Settings.yml with: - Name: ${{ inputs.Name }} SettingsPath: ${{ inputs.SettingsPath }} Debug: ${{ inputs.Debug }} Prerelease: ${{ inputs.Prerelease }} @@ -97,62 +88,12 @@ jobs: # - ❌ Abandoned PR - No need to lint abandoned changes # - ❌ Manual run - Only runs for PR events Lint-Repository: - name: Lint code base - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.event.pull_request.merged != true && github.event.action != 'closed' && fromJson(needs.Get-Settings.outputs.Settings).Linter.Skip != true + if: fromJson(needs.Get-Settings.outputs.Settings).Run.LintRepository needs: - Get-Settings - steps: - - name: Checkout repo - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Load dynamic envs - env: - SETTINGS: ${{ needs.Get-Settings.outputs.Settings }} - run: | - Write-Host "Loading settings for super-linter:" - $settings = $env:SETTINGS | ConvertFrom-Json -AsHashtable - $linter = $settings.Linter - $env = $linter.env - - foreach ($key in $env.Keys) { - $value = $env[$key] - - if ($value -is [bool]) { - $value = $value.ToString().ToLower() - } - - Write-Host "$key = $value" - - # Persist for following steps in this job - "$key=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - } - - - name: Lint code base - id: super-linter - uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.3.1 - env: - GITHUB_TOKEN: ${{ github.token }} - DEFAULT_WORKSPACE: ${{ inputs.WorkingDirectory }} - FILTER_REGEX_INCLUDE: ${{ inputs.WorkingDirectory }} - ENABLE_GITHUB_ACTIONS_STEP_SUMMARY: false - SAVE_SUPER_LINTER_SUMMARY: true - - - name: Post super-linter summary - if: failure() || fromJson(needs.Get-Settings.outputs.Settings).Linter.ShowSummaryOnSuccess == true - shell: pwsh - run: | - $summaryPath = Join-Path $env:GITHUB_WORKSPACE 'super-linter-output' 'super-linter-summary.md' - Get-Content $summaryPath | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 - - $failed = '${{ steps.super-linter.outcome }}' -eq 'failure' - if ($failed) { - Write-Host "::error::Super-linter found issues. Please review the summary above." - exit 1 - } + uses: ./.github/workflows/Lint-Repository.yml + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Builds module for testing @@ -160,52 +101,12 @@ jobs: # - ❌ Abandoned PR - Skips building abandoned changes # - ✅ Manual run - Builds module when manually triggered Build-Module: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && fromJson(needs.Get-Settings.outputs.Settings).Build.Module.Skip != true }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.BuildModule uses: ./.github/workflows/Build-Module.yml needs: - Get-Settings with: - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - - # Runs on: - # - ✅ Open/Updated PR - Builds documentation for review - # - ✅ Merged PR - Builds documentation for publishing - # - ❌ Abandoned PR - Skips building docs for abandoned changes - # - ✅ Manual run - Builds documentation when manually triggered - Build-Docs: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && fromJson(needs.Get-Settings.outputs.Settings).Build.Docs.Skip != true }} - needs: - - Get-Settings - - Build-Module - uses: ./.github/workflows/Build-Docs.yml - with: - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - ShowSummaryOnSuccess: ${{ fromJson(needs.Get-Settings.outputs.Settings).Build.Docs.ShowSummaryOnSuccess }} - - # Runs on: - # - ✅ Open/Updated PR - Builds site for preview - # - ✅ Merged PR - Builds site for publishing - # - ❌ Abandoned PR - Skips building site for abandoned changes - # - ✅ Manual run - Builds site when manually triggered - Build-Site: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && fromJson(needs.Get-Settings.outputs.Settings).Build.Site.Skip != true }} - needs: - - Get-Settings - - Build-Docs - uses: ./.github/workflows/Build-Site.yml - with: - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Tests source code changes @@ -213,23 +114,12 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests source code when manually triggered Test-SourceCode: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Get-Settings.outputs.SourceCodeTestSuites != '[]' }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestSourceCode needs: - Get-Settings - strategy: - fail-fast: false - matrix: - include: ${{ fromJson(needs.Get-Settings.outputs.SourceCodeTestSuites) }} uses: ./.github/workflows/Test-SourceCode.yml with: - RunsOn: ${{ matrix.RunsOn }} - OS: ${{ matrix.OSName }} - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Lints source code changes @@ -237,23 +127,12 @@ jobs: # - ❌ Abandoned PR - Skips linting abandoned changes # - ✅ Manual run - Lints source code when manually triggered Lint-SourceCode: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Get-Settings.outputs.SourceCodeTestSuites != '[]' }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.LintSourceCode needs: - Get-Settings - strategy: - fail-fast: false - matrix: - include: ${{ fromJson(needs.Get-Settings.outputs.SourceCodeTestSuites) }} uses: ./.github/workflows/Lint-SourceCode.yml with: - RunsOn: ${{ matrix.RunsOn }} - OS: ${{ matrix.OSName }} - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Tests built module @@ -261,25 +140,14 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests built module when manually triggered Test-Module: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Build-Module.result == 'success' && !cancelled() && needs.Get-Settings.outputs.PSModuleTestSuites != '[]' }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestModule && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - Get-Settings - strategy: - fail-fast: false - matrix: - include: ${{ fromJson(needs.Get-Settings.outputs.PSModuleTestSuites) }} uses: ./.github/workflows/Test-Module.yml secrets: inherit with: - RunsOn: ${{ matrix.RunsOn }} - OS: ${{ matrix.OSName }} - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Runs setup scripts before local module tests @@ -287,48 +155,13 @@ jobs: # - ❌ Abandoned PR - Skips setup for abandoned changes # - ✅ Manual run - Runs setup scripts when manually triggered BeforeAll-ModuleLocal: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Build-Module.result == 'success' && !cancelled() && needs.Get-Settings.outputs.ModuleTestSuites != '[]' }} - name: BeforeAll-ModuleLocal - runs-on: ubuntu-latest + if: fromJson(needs.Get-Settings.outputs.Settings).Run.BeforeAllModuleLocal && needs.Build-Module.result == 'success' && !cancelled() + uses: ./.github/workflows/BeforeAll-ModuleLocal.yml needs: - Build-Module - Get-Settings - steps: - - name: Checkout Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Run BeforeAll Setup Scripts - uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 - with: - Name: BeforeAll-ModuleLocal - ShowInfo: false - ShowOutput: true - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - Script: | - LogGroup "Running BeforeAll Setup Scripts" { - $beforeAllScript = 'tests/BeforeAll.ps1' - - if (-not (Test-Path $beforeAllScript)) { - Write-Host "No BeforeAll.ps1 script found at [$beforeAllScript] - exiting successfully" - exit 0 - } - - Write-Host "Running BeforeAll setup script: $beforeAllScript" - try { - & $beforeAllScript - Write-Host "BeforeAll script completed successfully: $beforeAllScript" - } catch { - Write-Error "BeforeAll script failed: $beforeAllScript - $_" - throw - } - } + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Tests module in local environment @@ -336,28 +169,15 @@ jobs: # - ❌ Abandoned PR - Skips testing abandoned changes # - ✅ Manual run - Tests module in local environment when manually triggered Test-ModuleLocal: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Build-Module.result == 'success' && !cancelled() && needs.Get-Settings.outputs.ModuleTestSuites != '[]' }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.TestModuleLocal && needs.Build-Module.result == 'success' && !cancelled() needs: - Build-Module - Get-Settings - BeforeAll-ModuleLocal - strategy: - fail-fast: false - matrix: - include: ${{ fromJson(needs.Get-Settings.outputs.ModuleTestSuites) }} uses: ./.github/workflows/Test-ModuleLocal.yml secrets: inherit with: - RunsOn: ${{ matrix.RunsOn }} - OSName: ${{ matrix.OSName }} - TestPath: ${{ matrix.TestPath }} - TestName: ${{ matrix.TestName }} - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Runs teardown scripts after local module tests @@ -365,48 +185,13 @@ jobs: # - ✅ Abandoned PR - Runs teardown if tests were started (cleanup) # - ✅ Manual run - Runs teardown scripts after local module tests AfterAll-ModuleLocal: - if: ${{ needs.Test-ModuleLocal.result != 'skipped' && always() }} - name: AfterAll-ModuleLocal - runs-on: ubuntu-latest + if: fromJson(needs.Get-Settings.outputs.Settings).Run.AfterAllModuleLocal && needs.Test-ModuleLocal.result != 'skipped' && always() + uses: ./.github/workflows/AfterAll-ModuleLocal.yml needs: + - Get-Settings - Test-ModuleLocal - steps: - - name: Checkout Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Run AfterAll Teardown Scripts - if: always() - uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 - with: - Name: AfterAll-ModuleLocal - ShowInfo: false - ShowOutput: true - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - Script: | - LogGroup "Running AfterAll Teardown Scripts" { - $afterAllScript = 'tests/AfterAll.ps1' - - if (-not (Test-Path $afterAllScript)) { - Write-Host "No AfterAll.ps1 script found at [$afterAllScript] - exiting successfully" - exit 0 - } - - Write-Host "Running AfterAll teardown script: $afterAllScript" - try { - & $afterAllScript - Write-Host "AfterAll script completed successfully: $afterAllScript" - } catch { - Write-Warning "AfterAll script failed: $afterAllScript - $_" - # Don't throw for teardown scripts to ensure other cleanup scripts can run - } - } + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Collects and reports test results @@ -414,7 +199,7 @@ jobs: # - ❌ Abandoned PR - Skips collecting results for abandoned changes # - ✅ Manual run - Collects and reports test results when manually triggered Get-TestResults: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Get-Settings.result == 'success' && !fromJson(needs.Get-Settings.outputs.Settings).Test.TestResults.Skip && (needs.Get-Settings.outputs.SourceCodeTestSuites != '[]' || needs.Get-Settings.outputs.PSModuleTestSuites != '[]' || needs.Get-Settings.outputs.ModuleTestSuites != '[]') && (always() && !cancelled()) }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.GetTestResults && needs.Get-Settings.result == 'success' && always() && !cancelled() needs: - Get-Settings - Test-SourceCode @@ -424,13 +209,7 @@ jobs: uses: ./.github/workflows/Get-TestResults.yml secrets: inherit with: - ModuleTestSuites: ${{ needs.Get-Settings.outputs.ModuleTestSuites }} - SourceCodeTestSuites: ${{ needs.Get-Settings.outputs.SourceCodeTestSuites }} - PSModuleTestSuites: ${{ needs.Get-Settings.outputs.PSModuleTestSuites }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Calculates and reports code coverage @@ -438,7 +217,7 @@ jobs: # - ❌ Abandoned PR - Skips coverage for abandoned changes # - ✅ Manual run - Calculates and reports code coverage when manually triggered Get-CodeCoverage: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Get-Settings.result == 'success' && !fromJson(needs.Get-Settings.outputs.Settings).Test.CodeCoverage.Skip && (needs.Get-Settings.outputs.PSModuleTestSuites != '[]' || needs.Get-Settings.outputs.ModuleTestSuites != '[]') && (always() && !cancelled()) }} + if: fromJson(needs.Get-Settings.outputs.Settings).Run.GetCodeCoverage && needs.Get-Settings.result == 'success' && always() && !cancelled() needs: - Get-Settings - Test-Module @@ -446,91 +225,65 @@ jobs: uses: ./.github/workflows/Get-CodeCoverage.yml secrets: inherit with: - CodeCoveragePercentTarget: ${{ fromJson(needs.Get-Settings.outputs.Settings).Test.CodeCoverage.PercentTarget }} - StepSummary_Mode: ${{ fromJson(needs.Get-Settings.outputs.Settings).Test.CodeCoverage.StepSummaryMode }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - # Runs on: - # - ❌ Open/Updated PR - Site not published for PRs in progress - # - ✅ Merged PR - Deploys site to GitHub Pages after successful merge - # - ❌ Abandoned PR - Site not published for abandoned changes - # - ❌ Manual run - Only publishes on merged PRs, not manual runs - Publish-Site: - if: ${{ !(github.event.action == 'closed' && github.event.pull_request.merged != true) && needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success' && !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.merged == true }} - needs: - - Get-Settings - - Get-TestResults - - Get-CodeCoverage - - Build-Site - permissions: - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 + Settings: ${{ needs.Get-Settings.outputs.Settings }} # Runs on: # - ✅ Open/Updated PR - Publishes prerelease when all tests/coverage/build succeed # - ✅ Merged PR - Publishes release when all tests/coverage/build succeed # - ✅ Abandoned PR - Publishes cleanup/retraction version # - ✅ Manual run - Publishes when all tests/coverage/build succeed - # Publish-Module: - if: | - needs.Get-Settings.result == 'success' && !cancelled() && github.event_name == 'pull_request' && ( - (github.event.action == 'closed' && github.event.pull_request.merged != true) || - (needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success') - ) + 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') + uses: ./.github/workflows/Publish-Module.yml + secrets: inherit needs: - Get-Settings - Get-TestResults - Get-CodeCoverage - Build-Site - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - fetch-depth: 0 + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} - - name: Download module artifact - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - name: module - path: ${{ inputs.WorkingDirectory }}/outputs/module + # Runs on: + # - ✅ Open/Updated PR - Builds documentation for review + # - ✅ Merged PR - Builds documentation for publishing + # - ❌ 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 + needs: + - Get-Settings + - Build-Module + uses: ./.github/workflows/Build-Docs.yml + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} - - name: Update Microsoft.PowerShell.PSResourceGet - shell: pwsh - run: | - Install-PSResource -Name Microsoft.PowerShell.PSResourceGet -Repository PSGallery -TrustRepository + # Runs on: + # - ✅ Open/Updated PR - Builds site for preview + # - ✅ Merged PR - Builds site for publishing + # - ❌ 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 + needs: + - Get-Settings + - Build-Docs + uses: ./.github/workflows/Build-Site.yml + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} - - name: Publish module - uses: PSModule/Publish-PSModule@6c25d139fe51b890f75c057897bd58ac344b192a # v2.0.8 - env: - GH_TOKEN: ${{ github.token }} - with: - Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} - ModulePath: outputs/module - APIKey: ${{ secrets.APIKEY }} - WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }} - AutoCleanup: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.AutoCleanup }} - AutoPatching: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.AutoPatching }} - DatePrereleaseFormat: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.DatePrereleaseFormat }} - IgnoreLabels: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.IgnoreLabels }} - IncrementalPrerelease: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.IncrementalPrerelease }} - MajorLabels: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.MajorLabels }} - MinorLabels: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.MinorLabels }} - PatchLabels: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.PatchLabels }} - VersionPrefix: ${{ fromJson(needs.Get-Settings.outputs.Settings).Publish.Module.VersionPrefix }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} + # Runs on: + # - ❌ Open/Updated PR - Site not published for PRs in progress + # - ✅ Merged PR - Deploys site to GitHub Pages after successful merge + # - ❌ Abandoned PR - Site not published for abandoned changes + # - ❌ Manual run - Only publishes on merged PRs, not manual runs + 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() + uses: ./.github/workflows/Publish-Site.yml + needs: + - Get-Settings + - Get-TestResults + - Get-CodeCoverage + - Build-Site + with: + Settings: ${{ needs.Get-Settings.outputs.Settings }} diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md deleted file mode 100644 index 5b52fc91..00000000 --- a/.specify/memory/constitution.md +++ /dev/null @@ -1,753 +0,0 @@ -# Process-PSModule Constitution - -## Product Overview - -**Process-PSModule** is a **reusable workflow product** that provides an **opinionated flow and structure** for building PowerShell modules using GitHub Actions. It is NOT a library or toolkit; it is a complete CI/CD workflow framework designed to be consumed by PowerShell module repositories. - -### Product Characteristics - -- **Opinionated Architecture**: Defines a specific workflow execution order and module structure -- **Reusable Workflows**: Consuming repositories call Process-PSModule workflows via `uses:` syntax -- **Configurable via Settings**: Behavior customized through `.github/PSModule.yml` (or JSON/PSD1) in consuming repos -- **Structure Requirements**: Consuming repos MUST follow documented structure in GitHub Actions README files -- **Not for Local Development**: Designed exclusively for GitHub Actions execution environment - -### Consuming Repository Requirements - -Repositories that consume Process-PSModule workflows MUST: -- Follow the module source structure documented in framework actions (see Required Module Structure below) -- Provide configuration file (`.github/PSModule.yml`) with appropriate settings -- Adhere to the opinionated workflow execution order -- Reference Process-PSModule workflows using stable version tags (e.g., `@v4`) -- Review action README documentation for structure and configuration requirements -- Use the [Template-PSModule](https://github.com/PSModule/Template-PSModule) repository as a starting point - -### Required Module Structure - -**Process-PSModule enforces an opinionated module structure.** Consuming repositories MUST organize their PowerShell module following this complete structure: - -#### Complete Repository Structure - -```plaintext -/ # Repository root -├── .github/ # GitHub Actions configuration -│ ├── linters/ # Linter configuration files -│ │ ├── .jscpd.json # Copy/paste detector settings -│ │ ├── .markdown-lint.yml # Markdown linter settings -│ │ ├── .powershell-psscriptanalyzer.psd1 # PSScriptAnalyzer rules -│ │ └── .textlintrc # Text linter settings -│ ├── workflows/ # GitHub Actions workflows -│ │ ├── Linter.yml # Linting workflow (optional) -│ │ ├── Nightly-Run.yml # Scheduled validation (optional) -│ │ └── Process-PSModule.yml # Main workflow (REQUIRED) -│ ├── CODEOWNERS # Code ownership and review assignments -│ ├── dependabot.yml # Dependency update automation -│ ├── mkdocs.yml # Material for MkDocs site configuration -│ ├── PSModule.yml # Process-PSModule settings (YAML/JSON/PSD1 supported) -│ └── release.yml # GitHub release configuration -├── examples/ # Usage examples (optional) -│ └── General.ps1 # Example script -├── icon/ # Module icon (optional, referenced in manifest) -│ └── icon.png # PNG icon file -├── src/ # MODULE SOURCE CODE (REQUIRED) -│ ├── assemblies/ # .NET assemblies (.dll) to load -│ ├── classes/ # PowerShell classes and enums -│ │ ├── private/ # Private (not exported) -│ │ └── public/ # Public (exported) -│ ├── data/ # Configuration data files loaded as private variables (.psd1) -│ │ ├── Config.psd1 # Example configuration file -│ │ └── Settings.psd1 # Example settings file -│ ├── formats/ # Format definition files (.ps1xml) -│ ├── functions/ # The functions for the PowerShell module -│ │ ├── private/ # Private (not exported) -│ │ └── public/ # Public (exported) -│ │ └── / # Optional: Group functions by category -│ │ ├── .md # Optional: Category documentation -│ │ └── Get-*.ps1 # Example: Get commands -│ ├── init/ # Initialization scripts -│ │ └── initializer.ps1 # Example initialization script -│ ├── modules/ # Nested PowerShell modules (.psm1) -│ ├── scripts/ # Script files that are loaded to the users runtime on import (.ps1) -│ │ └── loader.ps1 # Example script file -│ ├── types/ # Type definition files (.ps1xml) -│ ├── variables/ # Variables for the PowerShell module -│ │ ├── private/ # Private (not exported) -│ │ │ └── PrivateVariables.ps1 -│ │ └── public/ # Public (exported) -│ │ ├── Moons.ps1 -│ │ ├── Planets.ps1 -│ │ └── SolarSystems.ps1 -│ ├── finally.ps1 # Code executed at module load end (optional) -│ ├── header.ps1 # Code executed at module load start (optional) -│ ├── manifest.psd1 # PowerShell module manifest (optional, auto-generated if missing) -│ └── README.md # Documentation reference (points to Build-PSModule) -├── tests/ # Module tests (REQUIRED for Test-ModuleLocal) -│ ├── Environments/ # Optional: Test environment configurations -│ │ └── Environment.Tests.ps1 -│ ├── MyTests/ # Optional: Additional test suites -│ │ └── .Tests.ps1 -│ ├── AfterAll.ps1 # Teardown script (optional, runs once after all test matrix jobs) -│ ├── BeforeAll.ps1 # Setup script (optional, runs once before all test matrix jobs) -│ ├── .Tests.ps1 # Module functional tests (Pester) -│ └── Environment.Tests.ps1 # Environment validation tests (optional) -├── .gitattributes # Git line ending configuration -├── .gitignore # Git ignore patterns -├── LICENSE # License file (referenced in manifest) -└── README.md # Module documentation and usage -``` - -#### Module Source Structure Details (`src/` folder) - -The `src/` folder contains the module source code that Build-PSModule compiles into a production-ready module: - -**Required Files/Folders**: - -- At least one `.ps1` file in `functions/public/` to export functionality -- `tests/` folder at repository root with at least one Pester test file - -**Optional Configuration Files**: - -- `manifest.psd1` - PowerShell module manifest (auto-generated if missing with GitHub metadata) -- `header.ps1` - Code executed at module load start (before any other code) -- `finally.ps1` - Code executed at module load end (after all other code) -- `README.md` - Documentation pointer (typically references Build-PSModule for structure) - -**Source Folders** (all optional, include only what your module needs): - -- `assemblies/` - .NET assemblies (`.dll`) loaded into module session -- `classes/private/` - Private PowerShell classes (not exported) -- `classes/public/` - Public PowerShell classes (exported via TypeAccelerators) -- `data/` - Configuration data files (`.psd1`) loaded as module variables -- `formats/` - Format definition files (`.ps1xml`) for object display -- `functions/private/` - Private functions (internal implementation) - - Supports subdirectories for grouping (e.g., `functions/public/ComponentA/`, `functions/public/ComponentB/`) -- `functions/public/` - Public functions (exported to module consumers) - - Supports subdirectories for grouping (e.g., `functions/public/ComponentA/`, `functions/public/ComponentB/`) - - Optional category documentation files (e.g., `functions/public/PSModule/PSModule.md`) -- `init/` - Initialization scripts (executed first during module load) -- `modules/` - Nested PowerShell modules (`.psm1`) or additional assemblies -- `scripts/` - Script files (`.ps1`) to process in caller's scope -- `types/` - Type definition files (`.ps1xml`) for custom type extensions -- `variables/private/` - Private variables (module scope only) -- `variables/public/` - Public variables (exported to module consumers) - -**Build Processing**: - -- Build-PSModule compiles `src/` into a single root module file (`.psm1`) -- Source folders are removed from output after processing (only compiled module remains) -- Files processed in alphabetical order within each folder -- See "Build Process Requirements" section for detailed compilation flow - -#### Repository Configuration Details - -**GitHub Actions Configuration** (`.github/` folder): - -- `PSModule.yml` (or `.json`/`.psd1`) - **REQUIRED** configuration file controlling Process-PSModule behavior -- `workflows/Process-PSModule.yml` - **REQUIRED** workflow file calling reusable Process-PSModule workflow -- `mkdocs.yml` - Material for MkDocs configuration for GitHub Pages documentation -- `linters/` - Linter configuration files (optional, uses framework defaults if missing) -- Other workflows (Linter, Nightly-Run) are optional supplementary workflows - -**Documentation Assets**: - -- `LICENSE` - Referenced in module manifest `LicenseUri` property -- `icon/icon.png` - Referenced in module manifest `IconUri` property (public URL) -- `README.md` - Project documentation, referenced in GitHub repository metadata -- `examples/` - Usage examples for module consumers - -**Testing Requirements**: - -- `tests/` folder at repository root (NOT inside `src/`) -- Pester test files (`.Tests.ps1`) for module validation -- Optional `BeforeAll.ps1` and `AfterAll.ps1` scripts for external test resource management (see below) -- Tests executed by Test-ModuleLocal workflow across all platforms -- See "Workflow Execution Order" section for BeforeAll/AfterAll/Test workflow sequence - -**BeforeAll/AfterAll Test Scripts** (Optional): - -Process-PSModule supports optional test setup and teardown scripts that execute once per workflow run (not per platform): - -- **`tests/BeforeAll.ps1`** - Runs once before all Test-ModuleLocal matrix jobs - - **Purpose**: Setup external test resources independent of test platform/OS - - **Intended Use**: Deploy cloud infrastructure via APIs, create external database instances, initialize test data in third-party services - - **NOT Intended For**: OS-specific dependencies, platform-specific test files, test-specific resources for individual matrix combinations - - **Execution**: Runs in `tests/` directory on ubuntu-latest with full access to environment secrets - - **Error Handling**: Script failures halt the testing workflow (setup must succeed) - - **Example Use Cases**: Deploy Azure/AWS resources via APIs, create external PostgreSQL databases, initialize SaaS test accounts - -- **`tests/AfterAll.ps1`** - Runs once after all Test-ModuleLocal matrix jobs complete - - **Purpose**: Cleanup external test resources independent of test platform/OS - - **Intended Use**: Remove cloud infrastructure via APIs, delete external database instances, cleanup test data in third-party services - - **NOT Intended For**: OS-specific cleanup, platform-specific file removal, test-specific cleanup for individual matrix combinations - - **Execution**: Runs in `tests/` directory on ubuntu-latest with full access to environment secrets - - **Error Handling**: Script failures logged as warnings but don't halt workflow (cleanup is best-effort) - - **Always Executes**: Runs even if tests fail (via `if: always()` condition) - - **Example Use Cases**: Delete Azure/AWS resources via APIs, remove external databases, cleanup SaaS test accounts - -**Key Distinction**: BeforeAll/AfterAll are for managing **external resources via APIs** that exist outside GitHub Actions execution environment. Test-specific resources for individual OS/platform combinations should be created within the tests themselves using Pester `BeforeAll`/`AfterAll` blocks. - -**Key Points**: - -- **Private vs Public**: `private/` folders contain internal implementations; `public/` folders contain exported elements -- **Optional Components**: Not all folders are required; include only what your module needs -- **Function Organization**: Functions can be organized in subdirectories with optional category documentation -- **Manifest Generation**: If `manifest.psd1` is not provided, Build-PSModule auto-generates with GitHub metadata -- **Minimal Structure**: At minimum, provide `src/functions/public/.ps1` and `tests/.Tests.ps1` -- **Template Reference**: Use [Template-PSModule](https://github.com/PSModule/Template-PSModule) as starting point - -**Documentation References**: - -- [Build-PSModule README](https://github.com/PSModule/Build-PSModule) - Complete build process details -- [Template-PSModule](https://github.com/PSModule/Template-PSModule) - Reference implementation -- [Process-PSModule Configuration](#configuration) - Settings file documentation - -### Workflow Integration Requirements - -Consuming repositories MUST create a workflow file (e.g., `.github/workflows/Process-PSModule.yml`) that calls the reusable Process-PSModule workflow: - -```yaml -name: Process-PSModule - -on: - pull_request: - branches: [main] - types: [closed, opened, reopened, synchronize, labeled] - -jobs: - Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v4 - secrets: - APIKEY: ${{ secrets.APIKEY }} # PowerShell Gallery API key -``` - -**Configuration Requirements**: - -- Configuration file at `.github/PSModule.yml` (YAML, JSON, or PSD1 format supported) -- Reference: [Process-PSModule Configuration Documentation](https://github.com/PSModule/Process-PSModule#configuration) -- Use Template-PSModule as starting point: [Template-PSModule](https://github.com/PSModule/Template-PSModule) - -### Build Process Requirements - -**Process-PSModule uses the Build-PSModule action to compile module source code into a production-ready PowerShell module.** The build process is automated and opinionated, following a specific execution flow: - -#### Build Execution Flow - -1. **Execute Custom Build Scripts** (Optional) - - Build-PSModule searches for `*build.ps1` files **anywhere in the repository** - - Scripts are executed in **alphabetical order by filename** (path-independent) - - Allows custom pre-build logic (e.g., code generation, asset processing, configuration setup) - - Example: `1-build.ps1` runs before `2-build.ps1` regardless of directory location - - Custom scripts can modify source files before the build process continues -2. **Copy Source Code** - - All files from `src/` folder are copied to the output folder - - Existing root module file (`.psm1`) is **excluded** (recreated in step 4) - - Creates a clean build environment for compilation -3. **Build Module Manifest** (`.psd1`) - - Searches for existing `manifest.psd1` or `.psd1` in source - - If found, uses it as base (preserving specified properties) - - If not found, creates new manifest from scratch - - **Automatically Derived Properties**: - - `RootModule`: Set to `.psm1` - - `ModuleVersion`: Temporary value (`999.0.0`) - updated by Publish-PSModule during release - - `Author`: GitHub repository owner (or preserved from source manifest) - - `CompanyName`: GitHub repository owner (or preserved from source manifest) - - `Copyright`: Generated as `(c) YYYY . All rights reserved.` (or preserved from source manifest) - - `Description`: GitHub repository description (or preserved from source manifest) - - `GUID`: New GUID generated by New-ModuleManifest - - `FileList`: All files in the module output folder - - `RequiredAssemblies`: All `*.dll` files from `assemblies/` and `modules/` (depth = 1) - - `NestedModules`: All `*.psm1`, `*.ps1`, `*.dll` files from `modules/` (one level down) - - `ScriptsToProcess`: All `*.ps1` files from `scripts/` folder (loaded into caller session) - - `TypesToProcess`: All `*.Types.ps1xml` files (searched recursively) - - `FormatsToProcess`: All `*.Format.ps1xml` files (searched recursively) - - `DscResourcesToExport`: All `*.psm1` files from `resources/` folder - - `FunctionsToExport`: All functions from `functions/public/` (determined by AST parsing) - - `CmdletsToExport`: Empty array (or preserved from source manifest) - - `VariablesToExport`: All variables from `variables/public/` (determined by AST parsing) - - `AliasesToExport`: All aliases from `functions/public/` (determined by AST parsing) - - `ModuleList`: All `*.psm1` files in source folder (excluding root module) - - `RequiredModules`: Gathered from `#Requires -Modules` statements in source files - - `PowerShellVersion`: Gathered from `#Requires -Version` statements in source files - - `CompatiblePSEditions`: Gathered from `#Requires -PSEdition` statements (defaults to `@('Core','Desktop')`) - - `Tags`: GitHub repository topics plus compatibility tags from source files - - `LicenseUri`: Public URL to `LICENSE` file (or preserved from source manifest) - - `ProjectUri`: GitHub repository URL (or preserved from source manifest) - - `IconUri`: Public URL to `icon/icon.png` (or preserved from source manifest) - - **Preserved from Source Manifest** (if provided): - - `PowerShellHostName`, `PowerShellHostVersion`, `DotNetFrameworkVersion`, `ClrVersion`, `ProcessorArchitecture` - - `RequireLicenseAcceptance` (defaults to `false` if not specified) - - `ExternalModuleDependencies`, `HelpInfoURI`, `DefaultCommandPrefix` - - `ReleaseNotes` (not automated - can be set via PR/release description) - - `Prerelease` (managed by Publish-PSModule during release) -4. **Build Root Module** (`.psm1`) - - Creates new root module file (ignoring any existing `.psm1` in source) - - **Compilation Order**: - 1. **Module Header**: - - Adds content from `header.ps1` if exists (then removes file) - - If no `header.ps1`, adds default `[CmdletBinding()]` parameter block - - Adds PSScriptAnalyzer suppression for cross-platform compatibility - 2. **Post-Header Initialization**: - - Loads module manifest information (`$script:PSModuleInfo`) - - Adds platform detection (`$IsWindows` for PS 5.1 compatibility) - 3. **Data Loader** (if `data/` folder exists): - - Recursively imports all `*.psd1` files from `data/` folder - - Creates module-scoped variables: `$script:` for each data file - - Example: `data/Config.psd1` becomes `$script:Config` - 4. **Source File Integration** (in this specific order): - - Processes each folder alphabetically within the folder, files on root first, then subfolders - - Files are wrapped with debug logging regions - - After processing, source folders are **removed** from output: - 1. `init/` - Initialization scripts (executed first during module load) - 2. `classes/private/` - Private PowerShell classes (not exported) - 3. `classes/public/` - Public PowerShell classes (exported via TypeAccelerators) - 4. `functions/private/` - Private functions (not exported) - 5. `functions/public/` - Public functions (exported to module consumers) - 6. `variables/private/` - Private variables (not exported) - 7. `variables/public/` - Public variables (exported to module consumers) - 8. Any remaining `*.ps1` files on module root - 5. **Class and Enum Exporter** (if `classes/public/` exists): - - Uses `System.Management.Automation.TypeAccelerators` for type registration - - Exports enums from `classes/public/` as type accelerators - - Exports classes from `classes/public/` as type accelerators - - Adds `OnRemove` handler to clean up type accelerators when module is removed - - Provides Write-Verbose output for each exported type - 6. **Export-ModuleMember**: - - Adds `Export-ModuleMember` call with explicit lists - - Only exports items from `public/` folders: - - **Functions**: From `functions/public/` - - **Cmdlets**: From manifest (usually empty for script modules) - - **Variables**: From `variables/public/` - - **Aliases**: From functions in `functions/public/` - 7. **Format with PSScriptAnalyzer**: - - Entire root module content is formatted using `Invoke-Formatter` - - Uses PSScriptAnalyzer settings from `Build/PSScriptAnalyzer.Tests.psd1` - - Ensures consistent code style and UTF-8 BOM encoding -5. **Update Manifest Aliases** - - Re-analyzes root module to extract actual aliases defined - - Updates `AliasesToExport` in manifest with discovered aliases -6. **Upload Module Artifact** - - Built module is packaged and uploaded as workflow artifact - - Artifact name defaults to `module` (configurable via action input) - - Available for subsequent workflow steps (testing, publishing) - -#### Build Process Constraints - -- **No Manual Root Module**: Any existing `.psm1` file in `src/` is **ignored and replaced** -- **Source Folder Removal**: Processed source folders are removed from output (only compiled root module remains) -- **Alphabetical Processing**: Files within each folder are processed alphabetically -- **Manifest Precedence**: Source manifest values take precedence over generated values -- **UTF-8 BOM Encoding**: Final root module uses UTF-8 with BOM encoding -- **PowerShell 7.4+ Target**: Build process and generated code target PowerShell 7.4+ - -#### Build Process References - -- [Build-PSModule Action](https://github.com/PSModule/Build-PSModule) -- [PowerShell Gallery Publishing Guidelines](https://learn.microsoft.com/powershell/gallery/concepts/publishing-guidelines) -- [PowerShell Module Manifests](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_module_manifests) -- [PowerShell Module Authoring](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/performance/module-authoring-considerations) - -## Core Principles - -### I. Workflow-First Design (NON-NEGOTIABLE) - -Every feature MUST be implemented as a reusable GitHub Actions workflow component. This is NOT a local development framework; it is designed for CI/CD execution. Workflows MUST: -- Be composable and callable from other workflows using `uses:` syntax -- Use clearly defined inputs and outputs with proper documentation -- Follow the single responsibility principle (one workflow = one concern) -- Support matrix strategies for parallel execution where appropriate -- Be independently testable via the CI validation workflow (`.github/workflows/ci.yml`) -- Delegate logic to reusable GitHub Actions (from github.com/PSModule organization) -- **Avoid inline PowerShell code** in workflow YAML; use action-based script files instead -- Reference actions by specific versions or tags for stability - -**Rationale**: Reusable workflow architecture enables maintainability, reduces duplication, and allows consuming repositories to leverage Process-PSModule capabilities consistently. Action-based scripts provide better testability, reusability, and version control than inline code. - -### II. Test-Driven Development (NON-NEGOTIABLE) - -All code changes MUST follow strict TDD practices using Pester and PSScriptAnalyzer: - -- Tests MUST be written before implementation -- Tests MUST fail initially (Red phase) -- Implementation proceeds only after failing tests exist (Green phase) -- Code MUST be refactored while maintaining passing tests (Refactor phase) -- PSScriptAnalyzer rules MUST pass for all PowerShell code -- Manual testing procedures MUST be documented when automated testing is insufficient -- **Workflow functionality MUST be validated** through CI workflow tests (`.github/workflows/ci.yml`) -- Consuming module repositories SHOULD use CI workflow for nightly validation - -**Rationale**: TDD ensures code quality, prevents regressions, and creates living documentation through tests. This is fundamental to project reliability. CI workflow validation ensures the entire framework functions correctly in real-world scenarios. - -### III. Platform Independence with Modern PowerShell - -**Modules MUST be built to be cross-platform.** All workflows, features, and consuming modules MUST support cross-platform execution (Linux, macOS, Windows) using **PowerShell 7.4 or newer**: -- Use platform-agnostic PowerShell Core 7.4+ constructs exclusively -- **Modules MUST function identically** on Linux, macOS, and Windows -- Cross-platform compatibility is **verified through Test-ModuleLocal** workflow -- Test-ModuleLocal executes module tests on: `ubuntu-latest`, `windows-latest`, `macos-latest` -- **BeforeAll/AfterAll scripts** execute on `ubuntu-latest` only (external resource setup) -- Implement matrix testing across all supported operating systems for all workflow components -- Document any platform-specific behaviors or limitations explicitly -- Test failures on any platform MUST block merging -- Provide skip mechanisms for platform-specific tests when justified (with clear rationale) -- **No backward compatibility** required for Windows PowerShell 5.1 or earlier PowerShell Core versions - -**Rationale**: PowerShell 7.4+ provides consistent cross-platform behavior and modern language features. Focusing on a single modern version reduces complexity and maintenance burden. Modules built with Process-PSModule framework must work seamlessly across all platforms, verified through automated matrix testing in Test-ModuleLocal, ensuring maximum compatibility for consuming projects on contemporary platforms. - -### IV. Quality Gates and Observability - -Every workflow execution MUST produce verifiable quality metrics: - -- Test results MUST be captured in structured formats (JSON reports) -- Code coverage MUST be measured and reported -- Linting results MUST be captured and enforced -- All quality gates MUST fail the workflow if thresholds are not met -- Workflow steps MUST produce clear, actionable error messages -- Debug mode MUST be available for troubleshooting - -**Rationale**: Measurable quality gates prevent degradation over time and provide clear feedback. Observability enables rapid debugging and continuous improvement. - -### V. Continuous Delivery with Semantic Versioning - -Release management MUST be automated and follow SemVer 2.0.0: - -- Version bumps MUST be determined by PR labels (major, minor, patch) -- Releases MUST be automated on merge to main branch -- PowerShell Gallery publishing MUST be automatic for labeled releases -- GitHub releases MUST be created with complete changelogs -- Prerelease versioning MUST support incremental or date-based formats -- Documentation MUST be published to GitHub Pages automatically - -**Rationale**: Automated releases reduce human error, ensure consistency, and enable rapid iteration while maintaining clear version semantics. - -## Pull Request Workflow and Publishing Process - -Process-PSModule implements an **automated publishing workflow** triggered by pull request events. The workflow behavior is controlled by PR labels following Semantic Versioning (SemVer 2.0.0) conventions. - -### PR Label-Based Release Types - -Pull requests MUST use labels to determine release behavior: - -#### Version Increment Labels (SemVer) - -- **`major`** - Breaking changes, incompatible API changes - - Increments major version: `1.2.3` → `2.0.0` - - Resets minor and patch to zero - -- **`minor`** - New features, backward-compatible functionality additions - - Increments minor version: `1.2.3` → `1.3.0` - - Resets patch to zero - -- **`patch`** - bugfixes, backward-compatible patches (default) - - Increments patch version: `1.2.3` → `1.2.4` - - Applied by default when no version label specified (if AutoPatching enabled) - -#### Special Release Labels - -- **`prerelease`** - Creates prerelease version (unmerged PR publishing) - - Publishes module to PowerShell Gallery with prerelease tag - - Creates GitHub Release marked as prerelease - - Prerelease tag format: `-` - - Example: `1.2.4-featureauth001` for branch `feature/auth` - - Only applies to **unmerged PRs** (opened, reopened, synchronized, labeled) - - When merged to main, normal release takes precedence (prerelease label ignored) - -- **`NoRelease`** - Skips all publishing - - Workflow runs build and test jobs - - Publishing jobs (Publish-Module, Publish-Site) are skipped - - Used for documentation-only changes or work-in-progress validation - -### Workflow Conditional Execution - -The Process-PSModule workflow uses **dynamic conditions** to determine job execution: - -#### Always Execute (All PR States) - -- **Get-Settings** - Configuration loading -- **Build-Module** - Module compilation -- **Build-Docs** - Documentation generation -- **Build-Site** - Static site generation -- **Test-SourceCode** - Source code validation -- **Lint-SourceCode** - Code quality checks -- **Test-Module** - Built module validation -- **Test-ModuleLocal** - Pester tests across platforms -- **Get-TestResults** - Test aggregation -- **Get-CodeCoverage** - Coverage analysis - -#### Conditional Execution (Based on PR State and Labels) - -**Publish-Site** (GitHub Pages deployment): - -- **Executes when**: PR is **merged** to default branch AND tests pass -- **Skipped when**: PR is open/synchronized OR not merged OR scheduled run OR manual trigger -- Condition: `github.event_name == 'pull_request' AND github.event.pull_request.merged == true` - -**Publish-Module** (PowerShell Gallery publishing): - -- **Executes when**: - - PR is **merged** to default branch AND tests pass (normal release), OR - - PR has **`prerelease` label** AND PR is **not merged** AND tests pass (prerelease) -- **Skipped when**: - - PR has `NoRelease` label, OR - - Scheduled run (cron trigger), OR - - Manual run (workflow_dispatch), OR - - Tests fail -- Condition: `(github.event_name == 'pull_request' AND github.event.pull_request.merged == true) OR (labels contains 'prerelease' AND NOT merged)` - -### Publishing Behavior Examples - -| PR State | Labels | Build/Test | Publish-Module | Publish-Site | Version | -| -------------------------- | --------------------- | ---------- | ------------------------ | ------------ | ---------------------------- | -| Opened | `minor` | ✅ Yes | ❌ No | ❌ No | N/A (not published) | -| Opened | `prerelease` | ✅ Yes | ✅ Yes (prerelease) | ❌ No | `1.3.0-branchname001` | -| Opened | `prerelease`, `minor` | ✅ Yes | ✅ Yes (prerelease) | ❌ No | `1.3.0-branchname001` | -| Synchronized | `major` | ✅ Yes | ❌ No | ❌ No | N/A (not published) | -| Synchronized | `prerelease` | ✅ Yes | ✅ Yes (prerelease) | ❌ No | `1.3.0-branchname002` | -| Merged | `minor` | ✅ Yes | ✅ Yes (normal) | ✅ Yes | `1.3.0` | -| Merged | `major` | ✅ Yes | ✅ Yes (normal) | ✅ Yes | `2.0.0` | -| Merged | `patch` | ✅ Yes | ✅ Yes (normal) | ✅ Yes | `1.2.4` | -| Merged | (no label) | ✅ Yes | ✅ Yes (if AutoPatching) | ✅ Yes | `1.2.4` (patch) | -| Merged | `NoRelease` | ✅ Yes | ❌ No | ❌ No | N/A (skipped) | -| Merged | `prerelease`, `minor` | ✅ Yes | ✅ Yes (normal) | ✅ Yes | `1.3.0` (prerelease ignored) | -| Scheduled (cron) | N/A | ✅ Yes | ❌ No | ❌ No | N/A (validation only) | -| Manual (workflow_dispatch) | N/A | ✅ Yes | ❌ No | ❌ No | N/A (validation only) | - -### Version Calculation Process - -The Publish-PSModule action determines the new version using this process: - -1. **Get Latest Version** - - Query PowerShell Gallery for latest published version - - Query GitHub Releases for latest release version - - Use the higher of the two as base version -2. **Determine Version Increment** - - Check PR labels for `major`, `minor`, or `patch` - - If no version label and AutoPatching enabled, default to `patch` - - If no version label and AutoPatching disabled, skip publishing -3. **Calculate New Version** - - Apply SemVer increment based on label - - Major: `1.2.3` → `2.0.0` - - Minor: `1.2.3` → `1.3.0` - - Patch: `1.2.3` → `1.2.4` -4. **Add Prerelease Tag** (if `prerelease` label present on unmerged PR) - - Extract branch name, sanitize to alphanumeric only - - Query existing prerelease versions with same branch name - - Increment prerelease counter - - Format: `-` - - Example: `1.3.0-featureauth001`, `1.3.0-featureauth002` -5. **Publish to PowerShell Gallery** - - Upload module with calculated version - - Set prerelease flag if prerelease tag present - - Validate publication success -6. **Create GitHub Release** - - Generate release notes from PR description and commits - - Create release with version tag (e.g., `v1.3.0` or `v1.3.0-featureauth001`) - - Mark as prerelease if prerelease tag present - - Attach module artifact - -### Configuration Options - -Repositories can configure publishing behavior in `.github/PSModule.yml`: - -```yaml -Publish: - Module: - AutoPatching: true # Auto-apply patch when no label - AutoCleanup: false # Remove old prereleases - IncrementalPrerelease: true # Use incremental prerelease counter - DatePrereleaseFormat: '' # Alternative: date-based prerelease (yyyyMMddHHmmss) - VersionPrefix: 'v' # Prefix for git tags (e.g., v1.2.3) - MajorLabels: ['major', 'breaking'] # Labels that trigger major bump - MinorLabels: ['minor', 'feature'] # Labels that trigger minor bump - PatchLabels: ['patch', 'fix', 'bug'] # Labels that trigger patch bump - IgnoreLabels: ['NoRelease', 'skip'] # Labels that skip publishing -``` - -### Workflow Trigger Configuration - -Consuming repositories MUST configure their workflow file to trigger on appropriate PR events: - -```yaml -name: Process-PSModule - -on: - pull_request: - branches: [main] - types: - - closed # Detect merged PRs - - opened # Initial PR creation - - reopened # Reopened PR - - synchronize # New commits pushed - - labeled # Label added/changed - -jobs: - Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v4 - secrets: - APIKEY: ${{ secrets.APIKEY }} # Required for publishing -``` - -**Key Points**: - -- **`closed` event** with `github.event.pull_request.merged == true` triggers normal releases -- **`labeled` event** allows immediate prerelease publishing when `prerelease` label added -- **`synchronize` event** with `prerelease` label publishes new prerelease on each push -- **Secrets** MUST include `APIKEY` for PowerShell Gallery publishing (optional for CI-only runs) - -### Publishing Constraints - -- **API Key Required**: PowerShell Gallery publishing requires valid API key in secrets -- **Test Pass Requirement**: Publishing jobs only execute if all tests pass -- **Branch Protection**: Recommended to protect main branch and require PR reviews -- **Label Discipline**: Teams MUST follow label conventions for predictable versioning -- **Prerelease Cleanup**: Consider enabling AutoCleanup to remove old prerelease versions -- **Version Conflicts**: Publishing fails if version already exists in PowerShell Gallery -- **Incremental Prereleases**: Each push to prerelease PR increments counter (001, 002, 003...) -- **Branch Name Sanitization**: Prerelease tags use alphanumeric-only branch names - -## Quality Standards - -### Technical Constraints - -- **PowerShell Version**: 7.4 or newer (no backward compatibility with 5.1 or older Core versions) -- **Execution Environment**: GitHub Actions runners (not designed for local development) -- **Code Organization**: Action-based scripts preferred over inline workflow code -- **Action References**: Use PSModule organization actions (github.com/PSModule) with version tags -- **Workflow Structure**: Reusable workflows in `.github/workflows/` using `workflow_call` trigger - -### Code Quality - -- All PowerShell code MUST pass PSScriptAnalyzer with project-defined rules -- Source code structure MUST follow PSModule framework conventions -- Code coverage target MUST be configurable per repository (default 0% for flexibility) -- All workflow YAML MUST be valid and pass linting -- Action scripts MUST be testable and maintainable -- Inline code in workflows SHOULD be avoided; extract to action scripts -- **Git credential handling**: Workflows MUST use `persist-credentials: false` for checkout actions to prevent credential leakage -- **Repository depth**: Workflows SHOULD use `fetch-depth: 0` for full git history when needed for versioning or changelog generation - -### Documentation - -- README MUST provide clear setup instructions and workflow usage examples -- All workflows MUST include descriptive comments explaining inputs, outputs, and purpose -- Changes MUST update relevant documentation in the same PR -- GitHub Pages documentation MUST be generated automatically using Material for MkDocs - -### Testing - -- Source code tests MUST validate framework compliance -- Module tests MUST validate built module integrity -- Local module tests (Pester) MUST validate functional behavior across all platforms -- **Test-ModuleLocal workflow** verifies cross-platform module compatibility on: - - `ubuntu-latest` (Linux) - - `windows-latest` (Windows) - - `macos-latest` (macOS) -- BeforeAll/AfterAll setup and teardown scripts MUST be supported for test environments -- Test matrices MUST be configurable via repository settings -- **CI validation workflows** (`.github/workflows/Workflow-Test-*.yml`) MUST be maintained for integration testing -- **Unified production workflow** (`.github/workflows/workflow.yml`) is the primary consumer-facing workflow -- Consuming repositories SHOULD use CI validation workflows for nightly regression testing - -## Development Workflow - -### Branching and Pull Requests - -- Follow GitHub Flow: feature branches → PR → main -- PR MUST be opened for all changes -- CI workflows MUST execute on PR synchronize, open, reopen, label events -- **PR labels determine release behavior**: `major`, `minor`, `patch`, `prerelease`, `NoRelease` -- **`prerelease` label** enables publishing of prerelease versions from unmerged PRs -- **Merged PRs** trigger normal releases (major/minor/patch based on labels) -- **Unmerged PRs with `prerelease` label** trigger prerelease publishing with incremental tags -- **`NoRelease` label** skips publishing but runs all build and test jobs -- **AutoPatching** (if enabled) applies patch increment when no version label present -- **Prerelease tags** format: `-` (e.g., `1.3.0-featureauth001`) -- **Version labels** follow SemVer: `major` (breaking), `minor` (features), `patch` (fixes) -- See "Pull Request Workflow and Publishing Process" section for detailed behavior - -### Workflow Execution Order - -The standard execution order for Process-PSModule workflows MUST be: -1. **Get-Settings** - Reads configuration and prepares test matrices -2. **Build-Module** - Compiles source into module -3. **Test-SourceCode** - Parallel matrix testing of source code standards -4. **Lint-SourceCode** - Parallel matrix linting of source code -5. **Test-Module** - Framework validation and linting of built module -6. **BeforeAll-ModuleLocal** - Optional: Execute tests/BeforeAll.ps1 setup script once before all test matrix jobs - - **Runs on ubuntu-latest only** (external resource setup via APIs) - - Script failures halt workflow execution - - Skipped if tests/BeforeAll.ps1 does not exist -7. **Test-ModuleLocal** - Runs Pester tests across platform matrix (ubuntu-latest, windows-latest, macos-latest) - - **Verifies cross-platform module compatibility** - - Tests module functionality across all supported platforms - - Depends on BeforeAll-ModuleLocal (waits for external resource setup) -8. **AfterAll-ModuleLocal** - Optional: Execute tests/AfterAll.ps1 teardown script once after all test matrix jobs - - **Always runs even if tests fail** (via `if: always()` condition) - - **Runs on ubuntu-latest only** (external resource cleanup via APIs) - - Script failures logged as warnings but don't halt workflow - - Skipped if tests/AfterAll.ps1 does not exist -9. **Get-TestResults** - Aggregates and validates test results -10. **Get-CodeCoverage** - Validates coverage thresholds -11. **Build-Docs** and **Build-Site** - Generates documentation -12. **Publish-Module** and **Publish-Site** - Automated publishing on release - -**Workflow Types**: - -- **Unified Production Workflow** (`.github/workflows/workflow.yml`) - Single workflow handling both CI and CD for consuming repositories - - Intelligently executes appropriate jobs based on PR state (open/merged/abandoned) - - Eliminates need for separate CI and release workflows - - Uses conditional execution to optimize for different scenarios -- **CI Validation Workflows** (`.github/workflows/Workflow-Test-*.yml`) - Integration tests for framework development -- Consuming repositories use the unified production workflow for all scenarios - -### Configuration - -- Settings MUST be stored in `.github/PSModule.yml` (or JSON/PSD1 format) in consuming repositories -- Skip flags MUST be available for all major workflow steps -- OS-specific skip flags MUST be supported (Linux, macOS, Windows) -- Settings MUST support test configuration, build options, and publish behavior -- **Consuming repositories** configure behavior through settings file (opinionated defaults provided) -- **Structure requirements** documented in GitHub Actions README files MUST be followed by consumers -- Configuration options MUST be backward compatible within major versions - -## Governance - -### Constitution Authority - -This constitution supersedes all other development practices **for Process-PSModule framework development**. When conflicts arise between this document and other guidance, the constitution takes precedence. - -**For Consuming Repositories**: This constitution defines how the Process-PSModule framework is built and maintained. Consuming repositories follow the opinionated structure and configuration documented in framework action README files. - -### Amendments - -Changes to this constitution require: - -1. Documentation of the proposed change with clear rationale -2. Review and approval by project maintainers -3. Migration plan for existing code/workflows if applicable -4. Version bump according to impact: - - MAJOR: Backward incompatible principle removals or redefinitions - - MINOR: New principles or materially expanded guidance - - PATCH: Clarifications, wording fixes, non-semantic refinements - -### Compliance - -- All PRs MUST be validated against constitutional principles **for framework development** -- Workflow design MUST align with Workflow-First Design principle -- Test-First principle compliance is NON-NEGOTIABLE and enforced by review -- **Platform Independence MUST be verified** through Test-ModuleLocal matrix testing (ubuntu-latest, windows-latest, macos-latest) -- **Modules MUST function identically** across all platforms -- Quality Gates MUST be enforced by workflow automation -- PowerShell 7.4+ compatibility MUST be verified -- Action-based implementation preferred over inline workflow code -- CI validation workflow MUST pass before merging changes to core workflows -- **Consuming repositories** MUST follow the Required Module Structure documented in Product Overview - -### Runtime Development Guidance - -For agent-specific runtime development guidance **when developing the framework**, agents should reference: -- GitHub Copilot: `.github/copilot-instructions.md` (if exists) -- Other agents: Check for `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, or `QWEN.md` - -**For Consuming Repositories**: Follow the Required Module Structure and Workflow Integration Requirements documented in the Product Overview section. Start with [Template-PSModule](https://github.com/PSModule/Template-PSModule). - -**Version**: 1.6.1 | **Ratified**: TODO(RATIFICATION_DATE) | **Last Amended**: 2025-10-03 diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md index c1a663b8..8fa54670 100644 --- a/.specify/templates/plan-template.md +++ b/.specify/templates/plan-template.md @@ -10,7 +10,7 @@ 2. Fill Technical Context (scan for NEEDS CLARIFICATION) → Detect Project Type from file system structure or context (web=frontend+backend, mobile=app+API) → Set Structure Decision based on project type -3. Fill the Constitution Check section based on the content of the constitution document. +3. Fill the Constitution Check section based on the content of the main README. 4. Evaluate Constitution Check section below → If violations exist: Document them in Complexity Tracking → If no justification is possible: ERROR "Simplify approach first" @@ -269,4 +269,4 @@ directories captured above] - [ ] Complexity deviations documented --- -*Based on Constitution - See `.specify/memory/constitution.md`* +*Based on Constitution - See `README.md` Principles and Architecture section* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..47a7fd15 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "chat.promptFilesRecommendations": { + "PSModule.constitution": true, + "PSModule.specify": true, + "PSModule.plan": true, + "PSModule.tasks": true, + "PSModule.implement": true, + "PSModule.analyze": true, + "PSModule.clarify": true, + "PSModule.pr": true + }, + "chat.tools.terminal.autoApprove": { + ".specify/scripts/": true + } +} diff --git a/README.md b/README.md index 5a329c7c..ec339342 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,245 @@ # Process-PSModule -A workflow for crafting PowerShell modules using the PSModule framework, which builds, tests, and publishes PowerShell modules to the PowerShell -Gallery and produces documentation that is published to GitHub Pages. The workflow is used by all PowerShell modules in the PSModule organization. +Process-PSModule is the corner-stone of the PSModule framework. It is an end-to-end GitHub Actions workflow that automates the entire lifecycle of a +PowerShell module. The workflow builds the PowerShell module, runs cross-platform tests, enforces code quality and coverage requirements, generates +documentation, and publishes module to the PowerShell Gallery and its documentation site to GitHub Pages. It is the core workflow used across all +PowerShell modules in the [PSModule organization](https://github.com/PSModule), ensuring reliable, automated, and maintainable delivery of PowerShell +projects. ## How to get started 1. [Create a repository from the Template-Module](https://github.com/new?template_name=Template-PSModule&template_owner=PSModule&description=Add%20a%20description%20(required)&name=%3CModule%20name%3E). -1. Configure the repository: - 1. Enable GitHub Pages in the repository settings. Set it to deploy from `GitHub Actions`. - 1. This will create an environment called `github-pages` that GitHub deploys your site to. -
Within the environment, remove the branch protection for main. - Remove the branch protection on main +2. Configure the repository: + 1. Enable GitHub Pages in the repository settings. Set it to deploy from **GitHub Actions**. + 2. This will create an environment called `github-pages` that GitHub deploys your site to. +
Within the github-pages environment, remove the branch protection for main. + Remove the branch protection on main
- 1. [Create an API key on the PowerShell Gallery](https://www.powershellgallery.com/account/apikeys). Give it enough permission to manage the module you are working on. - 1. Create a new secret in the repository called `APIKEY` and set it to the API key for the PowerShell Gallery. -1. Create a branch, make your changes, create a PR and let the workflow run. + 3. [Create an API key on the PowerShell Gallery](https://www.powershellgallery.com/account/apikeys). Give it permission to manage the module you + are working on. + 4. Create a new secret called `APIKEY` in the repository and set the API key for the PowerShell Gallery as its value. + 5. If you are planning on creating many modules, you could use a glob pattern for the API key permissions in PowerShell Gallery and store the + secret on the organization. +3. Clone the repo locally, create a branch, make your changes, push the changes, create a PR and let the workflow run. + - Adding a `Prerelease` label to the PR will create a prerelease version of the module. +4. When merging to `main`, the workflow automatically builds, tests, and publishes your module to the PowerShell Gallery and maintains the + documentation on GitHub Pages. By default the process releases a patch version, which you can change by applying labels like `minor` or `major` on + the PR to bump the version accordingly. ## How it works +Everything is packaged into this single workflow to simplify full configuration of the workflow via this repository. Simplifying management and +operations across all PowerShell module projects. A user can configure how it works by simply configuring settings using a single file. + +### Workflow overview + The workflow is designed to be triggered on pull requests to the repository's default branch. When a pull request is opened, closed, reopened, synchronized (push), or labeled, the workflow will run. -Depending on the labels in the pull requests, the workflow will result in different outcomes. +Depending on the labels in the pull requests, the [workflow will result in different outcomes](#scenario-matrix). ![Process diagram](./media/Process-PSModule.png) -- [Get settings](./.github/workflows/Get-Settings.yml) - - Reads the settings file from a file in the module repository to configure the workflow. - - Gathers tests and creates test configuration based on the settings and the tests available in the module repository. - - This includes the selection of what OSes to run the tests on. -- [Build module](./.github/workflows/Build-Module.yml) - - Compiles the module source code into a PowerShell module. -- [Test source code](./.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 to later evaluate the results of the tests. -- [Lint source code](./.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 to later evaluate the results of the linter. -- [Framework test](./.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). - - This produces a json based report that is used to later evaluate the results of the tests. -- [Test module](./.github/workflows/Test-ModuleLocal.yml) - - Import 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 test environment (e.g., deploy infrastructure, download test data) - - `AfterAll`: Runs once after all test matrix jobs complete to clean up test environment (e.g., remove test resources, cleanup databases) - - Setup/teardown scripts are automatically detected in test directories and executed with the same environment variables as tests - - This produces a json based report that is used to later evaluate the results of the tests. -- [Get test results](./.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](./.github/workflows/Get-CodeCoverage.yml) - - Gathers the code coverage from the previous steps and creates a summary of the results. - - If the code coverage is below the target, the workflow will fail here. -- [Build docs](./.github/workflows/Build-Docs.yml) - - Generates documentation and lints the documentation using: - - [super-linter](https://github.com/super-linter/super-linter). -- [Build site](./.github/workflows/Build-Site.yml) - - Generates a static site using: - - [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). -- [Publish site](./.github/workflows/Publish-Site.yml) - - Publishes the static site with the module documentation to GitHub Pages. -- [Publish module](./.github/workflows/Publish-Module.yml) - - Publishes the module to the PowerShell Gallery. - - Creates a release on the GitHub repository. +- [Process-PSModule](#process-psmodule) + - [How to get started](#how-to-get-started) + - [How it works](#how-it-works) + - [Workflow overview](#workflow-overview) + - [Get-Settings](#get-settings) + - [Lint-Repository](#lint-repository) + - [Get settings](#get-settings-1) + - [Build module](#build-module) + - [Test source code](#test-source-code) + - [Lint source code](#lint-source-code) + - [Framework test](#framework-test) + - [Test module](#test-module) + - [Setup and Teardown Scripts](#setup-and-teardown-scripts) + - [Setup - `BeforeAll.ps1`](#setup---beforeallps1) + - [Example - `BeforeAll.ps1`](#example---beforeallps1) + - [Teardown - `AfterAll.ps1`](#teardown---afterallps1) + - [Example - `AfterAll.ps1`](#example---afterallps1) + - [Module tests](#module-tests) + - [Get test results](#get-test-results) + - [Get code coverage](#get-code-coverage) + - [Publish module](#publish-module) + - [Build docs](#build-docs) + - [Build site](#build-site) + - [Publish Docs](#publish-docs) + - [Usage](#usage) + - [Inputs](#inputs) + - [Secrets](#secrets) + - [Permissions](#permissions) + - [Scenario Matrix](#scenario-matrix) + - [Configuration](#configuration) + - [Example 1 - Defaults with Code Coverage target](#example-1---defaults-with-code-coverage-target) + - [Example 2 - Rapid testing](#example-2---rapid-testing) + - [Example 3 - Configuring the Repository Linter](#example-3---configuring-the-repository-linter) + - [Disabling the Linter](#disabling-the-linter) + - [Configuring Linter Validation Rules](#configuring-linter-validation-rules) + - [Additional Configuration](#additional-configuration) + - [Showing Linter Summary on Success](#showing-linter-summary-on-success) + - [Skipping Individual Framework Tests](#skipping-individual-framework-tests) + - [How to Skip Tests](#how-to-skip-tests) + - [Available Framework Tests](#available-framework-tests) + - [SourceCode Tests](#sourcecode-tests) + - [Module Tests](#module-tests-1) + - [Example Usage](#example-usage) + - [Best Practices](#best-practices) + - [Related Configuration](#related-configuration) + - [Repository structure](#repository-structure) + - [Module source code structure](#module-source-code-structure) + - [Principles and practices](#principles-and-practices) + +### Get-Settings + +[workflow](./.github/workflows/Get-Settings.yml) + +### Lint-Repository + +[workflow](./.github/workflows/Lint-Repository.yml) + +### Get settings + +[workflow](#get-settings) +- 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 wether + to setup testing infrastructure and what operating systems to run the tests on. + +### 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. + +The [PSModule - SourceCode tests](./scripts/tests/SourceCode/PSModule/PSModule.Tests.ps1) verifies the following coding practices that the framework enforces: + +| ID | Category | Description | +|---------------------|---------------------|--------------------------------------------------------------------------------------------| +| NumberOfProcessors | General | Should use `[System.Environment]::ProcessorCount` instead of `$env:NUMBER_OF_PROCESSORS`. | +| Verbose | General | Should not contain `-Verbose` unless it is explicitly disabled with `:$false`. | +| OutNull | General | Should use `$null = ...` instead of piping output to `Out-Null`. | +| NoTernary | General | Should not use ternary operations to maintain compatibility with PowerShell 5.1 and below. | +| LowercaseKeywords | General | All PowerShell keywords should be written in lowercase. | +| FunctionCount | Functions (Generic) | Each script file should contain exactly one function or filter. | +| FunctionName | Functions (Generic) | Script filenames should match the name of the function or filter they contain. | +| CmdletBinding | Functions (Generic) | Functions should include the `[CmdletBinding()]` attribute. | +| 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. + +### 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) +- This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the tests. + +### 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). + - `AfterAll`: Runs once after all test matrix jobs complete to clean up the test environment (e.g., remove test resources, clean up databases). +- Setup/teardown scripts are automatically detected in test directories and executed with the same environment variables as the tests. +- This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the tests. + +#### Setup and Teardown Scripts + +The workflow supports automatic execution of setup and teardown scripts for module tests: + +- Scripts are automatically detected and executed if present. +- If no scripts are found, the workflow continues normally. + +##### Setup - `BeforeAll.ps1` + +- Place in your test directories (`tests/BeforeAll.ps1`). +- Runs once before all test matrix jobs to prepare the test environment. +- Deploy test infrastructure, download test data, initialize databases, or configure services. +- Has access to the same environment variables as your tests (secrets, GitHub token, etc.). + +###### Example - `BeforeAll.ps1` + +```powershell +Write-Host "Setting up test environment..." +# Deploy test infrastructure +# Download test data +# Initialize test databases +Write-Host "Test environment ready!" +``` + +##### Teardown - `AfterAll.ps1` + +- Place in your test directories (`tests/AfterAll.ps1`). +- Runs once after all test matrix jobs complete to clean up the test environment. +- Remove test resources, clean up databases, stop services, or upload artifacts. +- Has access to the same environment variables as your tests. + +###### Example - `AfterAll.ps1` + +```powershell +Write-Host "Cleaning up test environment..." +# Remove test resources +# Clean up databases +# Stop services +Write-Host "Cleanup completed!" +``` + + +#### Module tests + +The [PSModule - Module tests](./scripts/tests/Module/PSModule/PSModule.Tests.ps1) verifies the following coding practices that the framework enforces: + +| Name | Description | +| ------ | ----------- | +| Module Manifest exists | Verifies that a module manifest file is present. | +| Module Manifest is valid | Verifies that the module manifest file is valid. | + +### 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. +- If the code coverage is below the target, the workflow will fail here. + +### Publish module + +[workflow](./.github/workflows/Publish-Module.yml) +- Publishes the module to the PowerShell Gallery. +- Creates a release on the GitHub repository. + +### 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/). + +### Publish Docs + +[workflow](./.github/workflows/Publish-Docs.yml) ## Usage @@ -108,78 +281,35 @@ jobs: uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5 secrets: APIKEY: ${{ secrets.APIKEY }} - ``` +
### Inputs | Name | Type | Description | Required | Default | | ---- | ---- | ----------- | -------- | ------- | -| `Name` | `string` | The name of the module to process. This defaults to the repository name if nothing is specified. | `false` | N/A | -| `SettingsPath` | `string` | The path to the settings file. Settings in the settings file take precedence over the action inputs. | `false` | `.github/PSModule.yml` | +| `SettingsPath` | `string` | The path to the settings file. All workflow configuration is controlled through this settings file. | `false` | `.github/PSModule.yml` | +| `Debug` | `boolean` | Enable debug output. | `false` | `false` | +| `Verbose` | `boolean` | Enable verbose output. | `false` | `false` | | `Version` | `string` | Specifies the version of the GitHub module to be installed. The value must be an exact version. | `false` | `''` | | `Prerelease` | `boolean` | Whether to use a prerelease version of the 'GitHub' module. | `false` | `false` | -| `Debug` | `boolean` | Whether to enable debug output. Adds a `debug` step to every job. | `false` | `false` | -| `Verbose` | `boolean` | Whether to enable verbose output. | `false` | `false` | -| `WorkingDirectory` | `string` | The path to the root of the repo. | `false` | `.` | - -### Setup and Teardown Scripts - -The workflow supports automatic execution of setup and teardown scripts for module tests: - -- Scripts are automatically detected and executed if present -- If no scripts are found, the workflow continues normally - -#### Setup - `BeforeAll.ps1` - -- Place in your test directories (`tests/BeforeAll.ps1`) -- Runs once before all test matrix jobs to prepare the test environment -- Deploy test infrastructure, download test data, initialize databases, or configure services -- Has access to the same environment variables as your tests (secrets, GitHub token, etc.) - -##### Example - `BeforeAll.ps1` - -```powershell -Write-Host "Setting up test environment..." -# Deploy test infrastructure -# Download test data -# Initialize test databases -Write-Host "Test environment ready!" -``` - -#### Teardown - `AfterAll.ps1` - -- Place in your test directories (`tests/AfterAll.ps1`) -- Runs once after all test matrix jobs complete to clean up the test environment -- Remove test resources, clean up databases, stop services, or upload artifacts -- Has access to the same environment variables as your tests - -##### Example - `AfterAll.ps1` - -```powershell -Write-Host "Cleaning up test environment..." -# Remove test resources -# Cleanup databases -# Stop services -Write-Host "Cleanup completed!" -``` +| `WorkingDirectory` | `string` | The path to the root of the repo. | `false` | `'.'` | ### Secrets -The following secrets are used by the workflow. They can be automatically provided (if available) by setting the `secrets: inherit` -in the workflow file. +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 personal access token with org access for running tests. | N/A | -| `TEST_USER_USER_FG_PAT` | GitHub secrets | The fine-grained personal access token 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 | 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 | ### Permissions @@ -194,7 +324,7 @@ permissions: id-token: write # to verify the Pages deployment originates from an appropriate source ``` -For more info see [Deploy GitHub Pages site](https://github.com/marketplace/actions/deploy-github-pages-site). +For more info, see [Deploy GitHub Pages site](https://github.com/marketplace/actions/deploy-github-pages-site). ### Scenario Matrix @@ -218,9 +348,9 @@ This table shows when each job runs based on the trigger scenario: | **Publish-Site** | ❌ No | ✅ Yes | ❌ No | ❌ No | | **Publish-Module** | ✅ Yes** | ✅ Yes** | ✅ Yes*** | ✅ Yes** | -\* Runs for cleanup if tests were started -\*\* Only when all tests/coverage/build succeed -\*\*\* Publishes cleanup/retraction version +- \* Runs for cleanup if tests were started +- \*\* Only when all tests/coverage/build succeed +- \*\*\* Publishes cleanup/retraction version ## Configuration @@ -229,52 +359,52 @@ The file can be a `JSON`, `YAML`, or `PSD1` file. By default, it will look for ` The following settings are available in the settings file: -| Name | Type | Description | Default | -|----------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|---------------------| -| `Name` | `String` | Name of the module to publish. Defaults to repository name. | `null` | -| `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` | -| `Test.Windows.Skip` | `Boolean` | Skip tests on Windows | `false` | -| `Test.SourceCode.Skip` | `Boolean` | Skip source code tests | `false` | -| `Test.SourceCode.Linux.Skip` | `Boolean` | Skip source code tests on Linux | `false` | -| `Test.SourceCode.MacOS.Skip` | `Boolean` | Skip source code tests on macOS | `false` | -| `Test.SourceCode.Windows.Skip` | `Boolean` | Skip source code tests on Windows | `false` | -| `Test.PSModule.Skip` | `Boolean` | Skip PSModule framework tests | `false` | -| `Test.PSModule.Linux.Skip` | `Boolean` | Skip PSModule framework tests on Linux | `false` | -| `Test.PSModule.MacOS.Skip` | `Boolean` | Skip PSModule framework tests on macOS | `false` | -| `Test.PSModule.Windows.Skip` | `Boolean` | Skip PSModule framework tests on Windows | `false` | -| `Test.Module.Skip` | `Boolean` | Skip module tests | `false` | -| `Test.Module.Linux.Skip` | `Boolean` | Skip module tests on Linux | `false` | -| `Test.Module.MacOS.Skip` | `Boolean` | Skip module tests on macOS | `false` | -| `Test.Module.Windows.Skip` | `Boolean` | Skip module tests on Windows | `false` | -| `Test.TestResults.Skip` | `Boolean` | Skip test result processing | `false` | -| `Test.CodeCoverage.Skip` | `Boolean` | Skip code coverage tests | `false` | -| `Test.CodeCoverage.PercentTarget` | `Integer` | Target code coverage percentage | `0` | -| `Test.CodeCoverage.StepSummaryMode` | `String` | Step summary mode for code coverage reports | `'Missed, Files'` | -| `Build.Skip` | `Boolean` | Skip all build tasks | `false` | -| `Build.Module.Skip` | `Boolean` | Skip module build | `false` | -| `Build.Docs.Skip` | `Boolean` | Skip documentation build | `false` | -| `Build.Docs.ShowSummaryOnSuccess` | `Boolean` | Show super-linter summary on success for documentation linting | `false` | -| `Build.Site.Skip` | `Boolean` | Skip site build | `false` | -| `Publish.Module.Skip` | `Boolean` | Skip module publishing | `false` | -| `Publish.Module.AutoCleanup` | `Boolean` | Automatically cleanup old prerelease module versions | `true` | -| `Publish.Module.AutoPatching` | `Boolean` | Automatically patch module version | `true` | -| `Publish.Module.IncrementalPrerelease` | `Boolean` | Use incremental prerelease versioning | `true` | -| `Publish.Module.DatePrereleaseFormat` | `String` | Format for date-based prerelease ([.NET DateTime][netdt]) | `''` | -| `Publish.Module.VersionPrefix` | `String` | Prefix for version tags | `'v'` | -| `Publish.Module.MajorLabels` | `String` | Labels indicating a major version bump | `'major, breaking'` | -| `Publish.Module.MinorLabels` | `String` | Labels indicating a minor version bump | `'minor, feature'` | -| `Publish.Module.PatchLabels` | `String` | Labels indicating a patch version bump | `'patch, fix'` | -| `Publish.Module.IgnoreLabels` | `String` | Labels indicating no release | `'NoRelease'` | -| `Linter.Skip` | `Boolean` | Skip repository linting | `false` | -| `Linter.ShowSummaryOnSuccess` | `Boolean` | Show super-linter summary on success for repository linting | `false` | -| `Linter.env` | `Object` | Environment variables for super-linter configuration | `{}` | +| Name | Type | Description | Default | +| -------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------- | ------------------- | +| `Name` | `String` | Name of the module to publish. Defaults to the repository name. | `null` | +| `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` | +| `Test.Windows.Skip` | `Boolean` | Skip tests on Windows | `false` | +| `Test.SourceCode.Skip` | `Boolean` | Skip source code tests | `false` | +| `Test.SourceCode.Linux.Skip` | `Boolean` | Skip source code tests on Linux | `false` | +| `Test.SourceCode.MacOS.Skip` | `Boolean` | Skip source code tests on macOS | `false` | +| `Test.SourceCode.Windows.Skip` | `Boolean` | Skip source code tests on Windows | `false` | +| `Test.PSModule.Skip` | `Boolean` | Skip PSModule framework tests | `false` | +| `Test.PSModule.Linux.Skip` | `Boolean` | Skip PSModule framework tests on Linux | `false` | +| `Test.PSModule.MacOS.Skip` | `Boolean` | Skip PSModule framework tests on macOS | `false` | +| `Test.PSModule.Windows.Skip` | `Boolean` | Skip PSModule framework tests on Windows | `false` | +| `Test.Module.Skip` | `Boolean` | Skip module tests | `false` | +| `Test.Module.Linux.Skip` | `Boolean` | Skip module tests on Linux | `false` | +| `Test.Module.MacOS.Skip` | `Boolean` | Skip module tests on macOS | `false` | +| `Test.Module.Windows.Skip` | `Boolean` | Skip module tests on Windows | `false` | +| `Test.TestResults.Skip` | `Boolean` | Skip test result processing | `false` | +| `Test.CodeCoverage.Skip` | `Boolean` | Skip code coverage tests | `false` | +| `Test.CodeCoverage.PercentTarget` | `Integer` | Target code coverage percentage | `0` | +| `Test.CodeCoverage.StepSummaryMode` | `String` | Step summary mode for code coverage reports | `'Missed, Files'` | +| `Build.Skip` | `Boolean` | Skip all build tasks | `false` | +| `Build.Module.Skip` | `Boolean` | Skip module build | `false` | +| `Build.Docs.Skip` | `Boolean` | Skip documentation build | `false` | +| `Build.Docs.ShowSummaryOnSuccess` | `Boolean` | Show super-linter summary on success for documentation linting | `false` | +| `Build.Site.Skip` | `Boolean` | Skip site build | `false` | +| `Publish.Module.Skip` | `Boolean` | Skip module publishing | `false` | +| `Publish.Module.AutoCleanup` | `Boolean` | Automatically clean up old prerelease module versions | `true` | +| `Publish.Module.AutoPatching` | `Boolean` | Automatically patch module version | `true` | +| `Publish.Module.IncrementalPrerelease` | `Boolean` | Use incremental prerelease versioning | `true` | +| `Publish.Module.DatePrereleaseFormat` | `String` | Format for date-based prerelease (uses [.NET DateTime format strings](https://learn.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings)) | `''` | +| `Publish.Module.VersionPrefix` | `String` | Prefix for version tags | `'v'` | +| `Publish.Module.MajorLabels` | `String` | Labels indicating a major version bump | `'major, breaking'` | +| `Publish.Module.MinorLabels` | `String` | Labels indicating a minor version bump | `'minor, feature'` | +| `Publish.Module.PatchLabels` | `String` | Labels indicating a patch version bump | `'patch, fix'` | +| `Publish.Module.IgnoreLabels` | `String` | Labels indicating no release | `'NoRelease'` | +| `Linter.Skip` | `Boolean` | Skip repository linting | `false` | +| `Linter.ShowSummaryOnSuccess` | `Boolean` | Show super-linter summary on success for repository linting | `false` | +| `Linter.env` | `Object` | Environment variables for super-linter configuration | `{}` |
`PSModule.yml` with all defaults -```yml +```yaml Name: null Build: @@ -343,8 +473,8 @@ Linter: Skip: false ShowSummaryOnSuccess: false env: {} - ``` +
### Example 1 - Defaults with Code Coverage target @@ -359,7 +489,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. +This example ends up running Get-Settings, Build-Module and Test-Module (tests from the module repo) on **ubuntu-latest** only. ```yaml Test: @@ -397,7 +527,7 @@ Linter: #### Configuring Linter Validation Rules -The workflow supports all environment variables that super-linter provides. You can configure these through the `Linter.env` object: +The workflow supports all environment variables that **super-linter** provides. You can configure these through the `Linter.env` object: ```yaml Linter: @@ -549,13 +679,115 @@ For broader test control, use the configuration file settings: See the [Configuration](#configuration) section for more details. -## Specifications and practices +## Repository structure + +Process-PSModule expects repositories to follow the staged layout produced by Template-PSModule. The workflow inspects this structure to decide what to compile, document, and publish. + +```plaintext +/ +├── .github/ # Workflow config, doc/site templates, automation policy +│ ├── linters/ # Rule sets applied by shared lint steps +│ │ ├── .markdown-lint.yml # Markdown rules enforced via super-linter +│ │ ├── .powershell-psscriptanalyzer.psd1 # Analyzer profile for test jobs +│ │ └── .textlintrc # Text lint rules surfaced in Build Docs summaries +│ ├── workflows/ # Entry points for the reusable workflow +│ │ └── Process-PSModule.yml # Consumer hook into this workflow bundle +│ ├── CODEOWNERS # Default reviewers enforced by Process-PSModule checks +│ ├── dependabot.yml # Dependency update cadence handled by GitHub +│ ├── mkdocs.yml # MkDocs config consumed during site builds +│ ├── PSModule.yml # Settings parsed to drive matrices +│ └── release.yml # Release automation template invoked on publish +├── examples/ # Samples referenced in generated documentation +│ └── General.ps1 # Example script ingested by Document-PSModule +├── icon/ # Icon assets linked from manifest and documentation +│ └── icon.png # Default module icon (PNG format) +├── src/ # Module source, see "Module source code structure" below +├── tests/ # Pester suites executed during validation +│ ├── AfterAll.ps1 (optional) # Cleanup script for ModuleLocal runs +│ ├── BeforeAll.ps1 (optional) # Setup script for ModuleLocal runs +│ └── .Tests.ps1 # Primary test entry point +├── .gitattributes # Normalizes line endings across platforms +├── .gitignore # Excludes build artifacts from source control +├── LICENSE # License text surfaced in manifest metadata +└── README.md # Repository overview rendered on GitHub and docs landing +``` + +Key expectations: + +- Keep at least one exported function under `src/functions/public/` and corresponding tests in `tests/`. +- Optional folders (`assemblies`, `formats`, `types`, `variables`, and others) are processed automatically when present. +- Markdown files in `src/functions/public` subfolders become documentation pages alongside generated help. +- The build step compiles `src/` into a root module file and removes the original project layout from the artifact. +- Documentation generation mirrors the `src/functions/public` hierarchy so help content always aligns with source. + +## Module source code structure + +How the module is built. + +```plaintext +├── src/ # Module source compiled and documented by the pipeline +│ ├── assemblies/ # Bundled binaries copied into the build artifact +│ ├── classes/ # Class scripts merged into the root module +│ │ ├── private/ # Internal classes kept out of exports +│ │ │ └── SecretWriter.ps1 # Example internal class implementation +│ │ └── public/ # Public classes exported via type accelerators +│ │ └── Book.ps1 # Example public class documented for consumers +│ ├── data/ # Configuration loaded into `$script:` scope at runtime +│ │ ├── Config.psd1 # Example config surfaced in generated help +│ │ └── Settings.psd1 # Additional configuration consumed on import +│ ├── formats/ # Formatting metadata registered during build +│ │ ├── CultureInfo.Format.ps1xml # Example format included in manifest +│ │ └── Mygciview.Format.ps1xml # Additional format loaded at import +│ ├── functions/ # Function scripts exported by the module +│ │ ├── private/ # Helper functions scoped to the module +│ │ │ ├── Get-InternalPSModule.ps1 # Sample internal helper +│ │ │ └── Set-InternalPSModule.ps1 # Sample internal helper +│ │ └── public/ # Public commands documented and tested +│ │ ├── Category/ # Optional: organize commands into categories +│ │ │ ├── Get-CategoryCommand.ps1 # Command file within category +│ │ │ └── Category.md # Category overview merged into docs output +│ │ ├── Get-PSModuleTest.ps1 # Example command captured by Microsoft.PowerShell.PlatyPS +│ │ ├── New-PSModuleTest.ps1 # Example command exported and tested +│ │ ├── Set-PSModuleTest.ps1 # Example command exported and tested +│ │ └── Test-PSModuleTest.ps1 # Example command exported and tested +│ ├── init/ # Initialization scripts executed during module load +│ │ └── initializer.ps1 # Example init script included in build output +│ ├── modules/ # Nested modules packaged with the compiled output +│ │ └── OtherPSModule.psm1 # Example nested module staged for export +│ ├── scripts/ # Scripts listed in 'ScriptsToProcess' +│ │ └── loader.ps1 # Loader executed when the module imports +│ ├── types/ # Type data merged into the manifest +│ │ ├── DirectoryInfo.Types.ps1xml # Type definition registered on import +│ │ └── FileInfo.Types.ps1xml # Type definition registered on import +│ ├── variables/ # Variable scripts exported by the module +│ │ ├── private/ # Internal variables scoped to the module +│ │ │ └── PrivateVariables.ps1 # Example private variable seed +│ │ └── public/ # Public variables exported and documented +│ │ ├── Moons.ps1 # Example variable surfaced in generated docs +│ │ ├── Planets.ps1 # Example variable surfaced in generated docs +│ │ └── SolarSystems.ps1 # Example variable surfaced in generated docs +│ ├── finally.ps1 # Cleanup script appended to the root module +│ ├── header.ps1 # Optional header injected at the top of the module +│ ├── manifest.psd1 (optional) # Source manifest reused when present +│ └── README.md # Module-level docs ingested by Document-PSModule +``` + +## Principles and practices + +The contribution and release process is based on the idea that a PR is a release, and we only maintain a single linear ancestry of versions, not going +back to patch and update old versions of the modules. This means that if we are on version `2.1.3` of a module and there is a security issue, we only +patch the latest version with a fix, not releasing new versions based on older versions of the module, i.e. not updating the latest 1.x with the +patch. + +If you need to work forth a bigger release, create a branch representing the release (a release branch) and open a PR towards `main` for this branch. +For each topic or feature to add to the release, open a new branch representing the feature (a feature branch) and open a PR towards the release +branch. Optionally add the `Prerelease` label on the PR for the release branch, to release preview versions before merging and releasing a published +version of the PowerShell module. + The process is compatible with: -- [Test-Driven Development](https://testdriven.io/test-driven-development/) using [Pester](https://pester.dev) and [PSScriptAnalyzer](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules) +- [Test-Driven Development](https://testdriven.io/test-driven-development/) using [Pester](https://pester.dev) and [PSScriptAnalyzer](https://learn.microsoft.com/powershell/utility-modules/psscriptanalyzer/overview) - [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow) - [SemVer 2.0.0 specifications](https://semver.org) - [Continuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery) - -[netdt]: https://learn.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings diff --git a/tests/srcTestRepo/.github/PSModule.yml b/tests/srcTestRepo/.github/PSModule.yml index 2607ddb5..92d30f1e 100644 --- a/tests/srcTestRepo/.github/PSModule.yml +++ b/tests/srcTestRepo/.github/PSModule.yml @@ -1,2 +1,3 @@ +Name: PSModuleTest2 Linter: Skip: true diff --git a/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md b/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md index 79741cf4..8c0f01c9 100644 --- a/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md +++ b/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md @@ -1 +1 @@ -# This is PSModule +# PSModule