diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index 09cc3d0..d173360 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -50,6 +50,7 @@ } } ExcludeRules = @( + 'PSAvoidUsingWriteHost', # Write-Host is acceptable in GitHub Actions runners 'PSMissingModuleManifestField', # This rule is not applicable until the module is built. 'PSUseToExportFieldsInManifest' ) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 02ee8ed..957a340 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -17,8 +17,8 @@ permissions: pull-requests: read jobs: - ActionTestBasic: - name: Action-Test - [Basic] + ActionTestValid: + name: Action-Test - [Valid Configuration] runs-on: ubuntu-latest steps: # Need to check out as part of the test, as its a local action @@ -28,6 +28,33 @@ jobs: persist-credentials: false - name: Action-Test + id: get-settings uses: ./ with: - Subject: PSModule + SettingsPath: './tests/scenarios/valid/PSModule.yml' + + ActionTestInvalidMissingTestConfig: + name: Action-Test - [Invalid - Missing Test Config] + runs-on: ubuntu-latest + steps: + # Need to check out as part of the test, as its a local action + - name: Checkout repo + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + + - name: Action-Test (Expect Failure) + id: get-settings + continue-on-error: true + uses: ./ + with: + SettingsPath: './tests/scenarios/invalid-percent-target/PSModule.yml' + + - name: Verify Action Failed as Expected + shell: pwsh + run: | + if ('${{ steps.get-settings.outcome }}' -eq 'success') { + Write-Error 'Expected action to fail for invalid configuration, but it succeeded' + exit 1 + } + Write-Host '✓ Action failed as expected for invalid configuration' diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 2029e94..6d40bc9 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -28,6 +28,10 @@ jobs: uses: super-linter/super-linter@d5b0a2ab116623730dd094f15ddc1b6b25bf7b99 # v8.3.2 env: GITHUB_TOKEN: ${{ github.token }} + VALIDATE_BIOME_FORMAT: false + VALIDATE_BIOME_LINT: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_JSCPD: false VALIDATE_JSON_PRETTIER: false VALIDATE_MARKDOWN_PRETTIER: false VALIDATE_YAML_PRETTIER: false diff --git a/LICENSE b/LICENSE index 75789b6..22f9ce1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 PSModule +Copyright (c) 2026 PSModule Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d560186..0c23b4d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,3 @@ -# Template-Action +# Get-PSModuleSettings -A template repository for GitHub Actions - -## Usage - -### Inputs - -### Secrets - -### Outputs - -### Example - -```yaml -Example here -``` +This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule). diff --git a/action.yml b/action.yml index fdc968d..5819017 100644 --- a/action.yml +++ b/action.yml @@ -1,15 +1,17 @@ -name: Template-Action -description: A template action for GitHub Actions using PowerShell +name: Get-PSModuleSettings +description: Get settings for a PowerShell module workflow author: PSModule branding: - icon: upload-cloud - color: white + icon: settings + color: blue inputs: - Subject: - description: The subject to greet + Name: + description: Name of the module. + required: false + SettingsPath: + description: Path to the settings file (json, yaml/yml, or psd1) required: false - default: World Debug: description: Enable debug output. required: false @@ -30,15 +32,29 @@ inputs: required: false default: ${{ github.workspace }} +outputs: + Settings: + description: The complete settings object as JSON, including test suites + value: ${{ fromJson(steps.Get-PSModuleSettings.outputs.result).Settings }} + runs: using: composite steps: - - name: Template-Action + - name: Get-PSModuleSettings uses: PSModule/GitHub-Script@8b9d2739d6896975c0e5448d2021ae2b94b6766a # v1.7.6 + id: Get-PSModuleSettings env: - PSMOUDLE_TEMPLATE_ACTION_INPUT_Subject: ${{ inputs.Subject }} + PSMODULE_GET_SETTINGS_INPUT_Name: ${{ inputs.Name }} + PSMODULE_GET_SETTINGS_INPUT_SettingsPath: ${{ inputs.SettingsPath }} + PSMODULE_GET_SETTINGS_INPUT_Debug: ${{ inputs.Debug }} + PSMODULE_GET_SETTINGS_INPUT_Verbose: ${{ inputs.Verbose }} + PSMODULE_GET_SETTINGS_INPUT_Version: ${{ inputs.Version }} + PSMODULE_GET_SETTINGS_INPUT_Prerelease: ${{ inputs.Prerelease }} + PSMODULE_GET_SETTINGS_INPUT_WorkingDirectory: ${{ inputs.WorkingDirectory }} with: - Name: Template-Action + Name: Get-PSModuleSettings + ShowInfo: false + ShowOutput: true Debug: ${{ inputs.Debug }} Prerelease: ${{ inputs.Prerelease }} Verbose: ${{ inputs.Verbose }} diff --git a/scripts/Settings.schema.json b/scripts/Settings.schema.json new file mode 100644 index 0000000..409c550 --- /dev/null +++ b/scripts/Settings.schema.json @@ -0,0 +1,410 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/PSModule/Get-PSModuleSettings/refs/heads/main/scripts/Settings.schema.json", + "title": "PSModule Settings", + "description": "Configuration schema for PSModule GitHub Action settings", + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "The name of the module" + }, + "Test": { + "type": "object", + "description": "Test configuration settings", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip all tests" + }, + "Linux": { + "$ref": "#/definitions/osConfig", + "description": "Linux-specific test configuration" + }, + "MacOS": { + "$ref": "#/definitions/osConfig", + "description": "macOS-specific test configuration" + }, + "Windows": { + "$ref": "#/definitions/osConfig", + "description": "Windows-specific test configuration" + }, + "SourceCode": { + "$ref": "#/definitions/testCategory", + "description": "Source code test configuration" + }, + "PSModule": { + "$ref": "#/definitions/testCategory", + "description": "PSModule test configuration" + }, + "Module": { + "$ref": "#/definitions/testCategory", + "description": "Module test configuration" + }, + "TestResults": { + "type": "object", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip test results collection" + } + } + }, + "CodeCoverage": { + "type": "object", + "description": "Code coverage configuration", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip code coverage collection" + }, + "PercentTarget": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Target code coverage percentage" + }, + "StepSummaryMode": { + "type": "string", + "description": "Mode for displaying step summary" + } + } + } + } + }, + "Build": { + "type": "object", + "description": "Build configuration settings", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip all build steps" + }, + "Module": { + "type": "object", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip module build" + } + } + }, + "Docs": { + "type": "object", + "description": "Documentation build configuration", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip documentation build" + }, + "ShowSummaryOnSuccess": { + "type": "boolean", + "description": "Show summary on successful build" + } + } + }, + "Site": { + "type": "object", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip site build" + } + } + } + } + }, + "Publish": { + "type": "object", + "description": "Publish configuration settings", + "properties": { + "Module": { + "type": "object", + "description": "Module publish configuration", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip module publish" + }, + "AutoCleanup": { + "type": "boolean", + "description": "Automatically cleanup after publish" + }, + "AutoPatching": { + "type": "boolean", + "description": "Automatically apply patches" + }, + "IncrementalPrerelease": { + "type": "boolean", + "description": "Use incremental prerelease versioning" + }, + "DatePrereleaseFormat": { + "type": "string", + "description": "Date format for prerelease versions" + }, + "VersionPrefix": { + "type": "string", + "description": "Prefix for version tags" + }, + "MajorLabels": { + "type": "string", + "description": "Comma-separated labels that trigger major version bump" + }, + "MinorLabels": { + "type": "string", + "description": "Comma-separated labels that trigger minor version bump" + }, + "PatchLabels": { + "type": "string", + "description": "Comma-separated labels that trigger patch version bump" + }, + "IgnoreLabels": { + "type": "string", + "description": "Comma-separated labels that prevent release" + } + } + } + } + }, + "Linter": { + "type": "object", + "description": "Linter configuration settings", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip linting" + }, + "ShowSummaryOnSuccess": { + "type": "boolean", + "description": "Show summary on successful linting" + }, + "env": { + "type": "object", + "description": "Environment variables for linter", + "additionalProperties": { + "type": "boolean" + } + } + } + }, + "TestSuites": { + "type": "object", + "description": "Test suite configurations", + "properties": { + "SourceCode": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/testSuite" + } + } + ] + }, + "PSModule": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/testSuite" + } + } + ] + }, + "Module": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/moduleTestSuite" + } + } + ] + } + } + }, + "SettingsPath": { + "type": "string", + "description": "Path to the settings file" + }, + "Debug": { + "type": "string", + "enum": [ + "true", + "false" + ], + "description": "Enable debug mode" + }, + "Verbose": { + "type": "string", + "enum": [ + "true", + "false" + ], + "description": "Enable verbose mode" + }, + "Version": { + "type": "string", + "description": "Module version" + }, + "Prerelease": { + "type": "string", + "enum": [ + "true", + "false" + ], + "description": "Indicates if this is a prerelease" + }, + "WorkingDirectory": { + "type": "string", + "description": "Working directory path" + }, + "Run": { + "type": "object", + "description": "Runtime execution flags", + "properties": { + "LintRepository": { + "type": "boolean", + "description": "Run repository linting" + }, + "BuildModule": { + "type": "boolean", + "description": "Build the module" + }, + "TestSourceCode": { + "type": "boolean", + "description": "Test source code" + }, + "LintSourceCode": { + "type": "boolean", + "description": "Lint source code" + }, + "TestModule": { + "type": "boolean", + "description": "Test the module" + }, + "BeforeAllModuleLocal": { + "type": "boolean", + "description": "Run before all module local tests" + }, + "TestModuleLocal": { + "type": "boolean", + "description": "Test module locally" + }, + "AfterAllModuleLocal": { + "type": "boolean", + "description": "Run after all module local tests" + }, + "GetTestResults": { + "type": "boolean", + "description": "Collect test results" + }, + "GetCodeCoverage": { + "type": "boolean", + "description": "Collect code coverage" + }, + "PublishModule": { + "type": "boolean", + "description": "Publish the module" + }, + "BuildDocs": { + "type": "boolean", + "description": "Build documentation" + }, + "BuildSite": { + "type": "boolean", + "description": "Build the site" + }, + "PublishSite": { + "type": "boolean", + "description": "Publish the site" + } + } + } + }, + "definitions": { + "osConfig": { + "type": "object", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip tests for this OS" + } + } + }, + "testCategory": { + "type": "object", + "properties": { + "Skip": { + "type": "boolean", + "description": "Skip this test category" + }, + "Linux": { + "$ref": "#/definitions/osConfig", + "description": "Linux-specific configuration" + }, + "MacOS": { + "$ref": "#/definitions/osConfig", + "description": "macOS-specific configuration" + }, + "Windows": { + "$ref": "#/definitions/osConfig", + "description": "Windows-specific configuration" + } + } + }, + "testSuite": { + "type": "object", + "properties": { + "RunsOn": { + "type": "string", + "description": "GitHub Actions runner to use" + }, + "OSName": { + "type": "string", + "description": "Operating system name" + } + }, + "required": [ + "RunsOn", + "OSName" + ] + }, + "moduleTestSuite": { + "type": "object", + "properties": { + "RunsOn": { + "type": "string", + "description": "GitHub Actions runner to use" + }, + "OSName": { + "type": "string", + "description": "Operating system name" + }, + "TestPath": { + "type": "string", + "description": "Relative path to the test file" + }, + "TestName": { + "type": "string", + "description": "Name of the test" + } + }, + "required": [ + "RunsOn", + "OSName", + "TestPath", + "TestName" + ] + } + } +} diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 480b20a..f213a98 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -1,24 +1,447 @@ -#Requires -Modules GitHub +'powershell-yaml', 'Hashtable' | Install-PSResource -Repository PSGallery -TrustRepository -[CmdletBinding()] -param( - [Parameter()] - [string] $Subject = $env:PSMOUDLE_TEMPLATE_ACTION_INPUT_Subject -) +$name = $env:PSMODULE_GET_SETTINGS_INPUT_Name +$settingsPath = $env:PSMODULE_GET_SETTINGS_INPUT_SettingsPath +$debug = $env:PSMODULE_GET_SETTINGS_INPUT_Debug +$verbose = $env:PSMODULE_GET_SETTINGS_INPUT_Verbose +$version = $env:PSMODULE_GET_SETTINGS_INPUT_Version +$prerelease = $env:PSMODULE_GET_SETTINGS_INPUT_Prerelease +$workingDirectory = $env:PSMODULE_GET_SETTINGS_INPUT_WorkingDirectory -begin { - $scriptName = $MyInvocation.MyCommand.Name - Write-Debug "[$scriptName] - Start" +LogGroup 'Inputs' { + [pscustomobject]@{ + PWD = (Get-Location).Path + Name = $name + SettingsPath = $settingsPath + } | Format-List | Out-String } -process { - try { - Write-Output "Hello, $Subject!" - } catch { - throw $_ +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." + } + } + } + + LogGroup 'Validate settings against schema' { + $schemaPath = Join-Path $PSScriptRoot 'Settings.schema.json' + if (Test-Path -Path $schemaPath) { + Write-Host 'Validating settings against schema...' + $schema = Get-Content $schemaPath -Raw + + # Convert settings to JSON for validation + $settingsJson = $settings | ConvertTo-Json -Depth 10 + + try { + $isValid = Test-Json -Json $settingsJson -Schema $schema -ErrorAction Stop + if ($isValid) { + Write-Host '✓ Settings conform to schema' + } else { + throw 'Settings do not conform to the schema' + } + } catch { + Write-Error "Schema validation failed: $_" + Write-Error 'Your settings file does not match the expected schema structure.' + throw + } + } else { + Write-Warning "Schema file not found at [$schemaPath]. Skipping validation." + } + } +} else { + Write-Host 'No settings file present.' + $settings = @{} +} + +LogGroup 'Name' { + [pscustomobject]@{ + InputName = $name + SettingsName = $settings.Name + RepositoryName = $env:GITHUB_REPOSITORY_NAME + } | Format-List | Out-String + + if (![string]::IsNullOrEmpty($name)) { + Write-Host "Using name from input parameter: [$name]" + } elseif (![string]::IsNullOrEmpty($settings.Name)) { + $name = $settings.Name + Write-Host "Using name from settings file: [$name]" + } else { + $name = $env:GITHUB_REPOSITORY_NAME + Write-Host "Using repository name: [$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.StepSummaryMode ?? '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 ?? @{} } } -end { - Write-Debug "[$scriptName] - End" +# Add input properties to settings +$settings | Add-Member -MemberType NoteProperty -Name SettingsPath -Value $settingsPath +$settings | Add-Member -MemberType NoteProperty -Name Debug -Value $debug +$settings | Add-Member -MemberType NoteProperty -Name Verbose -Value $verbose +$settings | Add-Member -MemberType NoteProperty -Name Version -Value $version +$settings | Add-Member -MemberType NoteProperty -Name Prerelease -Value $prerelease +$settings | Add-Member -MemberType NoteProperty -Name WorkingDirectory -Value $workingDirectory + +# Calculate job run conditions +LogGroup 'Calculate Job Run Conditions:' { + # Common conditions + $isPR = $env:GITHUB_EVENT_NAME -eq 'pull_request' + $isOpenOrUpdatedPR = $isPR -and $env:GITHUB_EVENT_ACTION -ne 'closed' + $isAbandonedPR = $isPR -and $env:GITHUB_EVENT_ACTION -eq 'closed' -and $env:GITHUB_EVENT_PULL_REQUEST_MERGED -ne 'true' + $isMergedPR = $isPR -and $env:GITHUB_EVENT_PULL_REQUEST_MERGED -eq 'true' + $isNotAbandonedPR = -not $isAbandonedPR + + [pscustomobject]@{ + isPR = $isPR + isOpenOrUpdatedPR = $isOpenOrUpdatedPR + isAbandonedPR = $isAbandonedPR + isMergedPR = $isMergedPR + isNotAbandonedPR = $isNotAbandonedPR + } | Format-List | Out-String +} + +# Get-TestSuites +if ($settings.Test.Skip) { + Write-Host 'Skipping all tests.' + $sourceCodeTestSuites = $null + $psModuleTestSuites = $null + $moduleTestSuites = $null + + # Add TestSuites to settings + $settings | Add-Member -MemberType NoteProperty -Name TestSuites -Value ([pscustomobject]@{ + SourceCode = $null + PSModule = $null + Module = $null + }) +} else { + + # Define test configurations as an array of hashtables. + $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.' + $null + } else { + $result = @() + if (-not $settings.Test.Linux.Skip -and -not $settings.Test.SourceCode.Linux.Skip) { $result += $linux } + if (-not $settings.Test.MacOS.Skip -and -not $settings.Test.SourceCode.MacOS.Skip) { $result += $macOS } + if (-not $settings.Test.Windows.Skip -and -not $settings.Test.SourceCode.Windows.Skip) { $result += $windows } + if ($result.Count -gt 0) { $result } else { $null } + } + $sourceCodeTestSuites | Format-Table -AutoSize | Out-String + } + + LogGroup 'PSModule Test Suites:' { + $psModuleTestSuites = if ($settings.Test.PSModule.Skip) { + Write-Host 'Skipping all PSModule tests.' + $null + } else { + $result = @() + if (-not $settings.Test.Linux.Skip -and -not $settings.Test.PSModule.Linux.Skip) { $result += $linux } + if (-not $settings.Test.MacOS.Skip -and -not $settings.Test.PSModule.MacOS.Skip) { $result += $macOS } + if (-not $settings.Test.Windows.Skip -and -not $settings.Test.PSModule.Windows.Skip) { $result += $windows } + if ($result.Count -gt 0) { $result } else { $null } + } + $psModuleTestSuites | Format-Table -AutoSize | Out-String + } + + LogGroup 'Module Local Test Suites:' { + $moduleTestSuites = if ($settings.Test.Module.Skip) { + Write-Host 'Skipping all module tests.' + $null + } else { + # Locate the tests directory. + $testsPath = Resolve-Path 'tests' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path + if (-not $testsPath) { + Write-Warning 'No tests found' + return $null + } + Write-Host "Tests found at [$testsPath]" + + function Get-TestItemsFromFolder { + <# + .SYNOPSIS + Retrieves test items from a specified folder. + + .DESCRIPTION + This function searches for test-related files in the specified folder. + It looks for configuration files (*.Configuration.ps1), container files (*.Container.ps1), + and test files (*.Tests.ps1) in that order of precedence. + + .PARAMETER FolderPath + The path to the folder to search for test items. + + .OUTPUTS + System.IO.FileInfo[] + Returns an array of test-related files found in the folder. + #> + 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-TestDirectory { + <# + .SYNOPSIS + Finds test directories recursively. + + .DESCRIPTION + This function recursively searches for all directories starting from the specified path. + It returns a flat array of all directory paths found. + + .PARAMETER Path + The root path to start searching for directories. + + .OUTPUTS + System.String[] + Returns an array of directory paths. + #> + param ([string]$Path) + + $directories = @() + $childDirs = Get-ChildItem -Path $Path -Directory + + foreach ($dir in $childDirs) { + $directories += $dir.FullName + $directories += Find-TestDirectory -Path $dir.FullName + } + + return $directories + } + + $allTestFolders = @($testsPath) + (Find-TestDirectory -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 + } + + # Add TestSuites to settings + $settings | Add-Member -MemberType NoteProperty -Name TestSuites -Value ([pscustomobject]@{ + SourceCode = $sourceCodeTestSuites + PSModule = $psModuleTestSuites + Module = $moduleTestSuites + }) +} + +# Calculate job-specific conditions and add to settings +LogGroup 'Calculate Job Run Conditions:' { + # Create Run object with all job-specific conditions + $run = [pscustomobject]@{ + LintRepository = $isOpenOrUpdatedPR -and (-not $settings.Linter.Skip) + BuildModule = $isNotAbandonedPR -and (-not $settings.Build.Module.Skip) + TestSourceCode = $isNotAbandonedPR -and ($null -ne $settings.TestSuites.SourceCode) + LintSourceCode = $isNotAbandonedPR -and ($null -ne $settings.TestSuites.SourceCode) + TestModule = $isNotAbandonedPR -and ($null -ne $settings.TestSuites.PSModule) + BeforeAllModuleLocal = $isNotAbandonedPR -and ($null -ne $settings.TestSuites.Module) + TestModuleLocal = $isNotAbandonedPR -and ($null -ne $settings.TestSuites.Module) + AfterAllModuleLocal = $true # Always runs if Test-ModuleLocal was not skipped + GetTestResults = $isNotAbandonedPR -and (-not $settings.Test.TestResults.Skip) -and ( + ($null -ne $settings.TestSuites.SourceCode) -or ($null -ne $settings.TestSuites.PSModule) -or ($null -ne $settings.TestSuites.Module) + ) + GetCodeCoverage = $isNotAbandonedPR -and (-not $settings.Test.CodeCoverage.Skip) -and ( + ($null -ne $settings.TestSuites.PSModule) -or ($null -ne $settings.TestSuites.Module) + ) + PublishModule = $isPR -and ($isAbandonedPR -or ($isOpenOrUpdatedPR -or $isMergedPR)) + BuildDocs = $isNotAbandonedPR -and (-not $settings.Build.Docs.Skip) + BuildSite = $isNotAbandonedPR -and (-not $settings.Build.Site.Skip) + PublishSite = $isMergedPR + } + $settings | Add-Member -MemberType NoteProperty -Name Run -Value $run + + Write-Host 'Job Run Conditions:' + $run | Format-List | Out-String +} + +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) +} + +LogGroup 'Validate output settings against schema' { + $schemaPath = Join-Path $PSScriptRoot 'Settings.schema.json' + if (Test-Path -Path $schemaPath) { + Write-Host 'Validating output settings against schema...' + $schema = Get-Content $schemaPath -Raw + + # Convert output settings to JSON for validation + $outputJson = $settings | ConvertTo-Json -Depth 10 + + try { + $isValid = Test-Json -Json $outputJson -Schema $schema -ErrorAction Stop + if ($isValid) { + Write-Host '✓ Output settings conform to schema' + } else { + throw 'Output settings do not conform to the schema' + } + } catch { + Write-Error "Output schema validation failed: $_" + Write-Error 'The generated settings object does not match the expected schema structure.' + Write-Error 'This indicates a bug in the action. Please report this issue.' + throw + } + } else { + Write-Warning "Schema file not found at [$schemaPath]. Skipping output validation." + } } diff --git a/tests/Environment.Tests.ps1 b/tests/Environment.Tests.ps1 new file mode 100644 index 0000000..a4bc7da --- /dev/null +++ b/tests/Environment.Tests.ps1 @@ -0,0 +1,13 @@ +BeforeAll { + # Minimal test file for validation purposes +} + +Describe 'Environment' { + It 'Should have PowerShell available' { + $PSVersionTable.PSVersion | Should -Not -BeNullOrEmpty + } + + It 'Should have expected OS platform' { + $PSVersionTable.Platform | Should -BeIn @('Win32NT', 'Unix') + } +} diff --git a/tests/PSModuleTest.Tests.ps1 b/tests/PSModuleTest.Tests.ps1 new file mode 100644 index 0000000..7b7295c --- /dev/null +++ b/tests/PSModuleTest.Tests.ps1 @@ -0,0 +1,13 @@ +BeforeAll { + # Minimal test file for validation purposes +} + +Describe 'PSModuleTest' { + It 'Should pass basic test' { + $true | Should -Be $true + } + + It 'Should have test context available' { + $PSScriptRoot | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 43816d3..0000000 --- a/tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Placeholder for tests - -Location for tests of the action. diff --git a/tests/scenarios/invalid-percent-target/PSModule.yml b/tests/scenarios/invalid-percent-target/PSModule.yml new file mode 100644 index 0000000..3349481 --- /dev/null +++ b/tests/scenarios/invalid-percent-target/PSModule.yml @@ -0,0 +1,22 @@ +Name: PSModuleTest +Test: + SourceCode: + Skip: true + PSModule: + Linux: + Skip: true + Module: + Skip: false + CodeCoverage: + PercentTarget: 101 +Publish: + AutoCleanup: false +Linter: + env: + VALIDATE_BIOME_FORMAT: false + VALIDATE_BIOME_LINT: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_JSCPD: false + VALIDATE_JSON_PRETTIER: false + VALIDATE_MARKDOWN_PRETTIER: false + VALIDATE_YAML_PRETTIER: false diff --git a/tests/scenarios/valid/PSModule.yml b/tests/scenarios/valid/PSModule.yml new file mode 100644 index 0000000..ceaf2ff --- /dev/null +++ b/tests/scenarios/valid/PSModule.yml @@ -0,0 +1,22 @@ +Name: PSModuleTest +Test: + SourceCode: + Skip: true + PSModule: + Linux: + Skip: true + Module: + Skip: false + CodeCoverage: + PercentTarget: 1 +Publish: + AutoCleanup: false +Linter: + env: + VALIDATE_BIOME_FORMAT: false + VALIDATE_BIOME_LINT: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_JSCPD: false + VALIDATE_JSON_PRETTIER: false + VALIDATE_MARKDOWN_PRETTIER: false + VALIDATE_YAML_PRETTIER: false