diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json new file mode 100644 index 00000000..15d4513d --- /dev/null +++ b/.github/linters/.jscpd.json @@ -0,0 +1,11 @@ +{ + "threshold": 0, + "reporters": [ + "consoleFull" + ], + "ignore": [ + "**/tests/**", + "**/workflows/**" + ], + "absolute": true +} diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index 40d11d60..09cc3d0c 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -1,18 +1,56 @@ -#Documentation: https://github.com/PowerShell/PSScriptAnalyzer/blob/master/docs/Cmdlets/Invoke-ScriptAnalyzer.md#-settings -@{ - #CustomRulePath='path\to\CustomRuleModule.psm1' - #RecurseCustomRulePath='path\of\customrules' - #Severity = @( - # 'Error' - # 'Warning' - #) - #IncludeDefaultRules=${true} +@{ + Rules = @{ + PSAlignAssignmentStatement = @{ + Enable = $true + CheckHashtable = $true + } + PSAvoidLongLines = @{ + Enable = $true + MaximumLineLength = 150 + } + PSAvoidSemicolonsAsLineTerminators = @{ + Enable = $true + } + PSPlaceCloseBrace = @{ + Enable = $true + NewLineAfter = $false + IgnoreOneLineBlock = $true + NoEmptyLineBefore = $false + } + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + } + PSProvideCommentHelp = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + VSCodeSnippetCorrection = $false + Placement = 'begin' + } + PSUseConsistentIndentation = @{ + Enable = $true + IndentationSize = 4 + PipelineIndentation = 'IncreaseIndentationForFirstPipeline' + Kind = 'space' + } + PSUseConsistentWhitespace = @{ + Enable = $true + CheckInnerBrace = $true + CheckOpenBrace = $true + CheckOpenParen = $true + CheckOperator = $true + CheckPipe = $true + CheckPipeForRedundantWhitespace = $true + CheckSeparator = $true + CheckParameter = $true + IgnoreAssignmentOperatorInsideHashTable = $true + } + } ExcludeRules = @( - 'PSMissingModuleManifestField' - 'PSAvoidUsingWriteHost' + 'PSMissingModuleManifestField', # This rule is not applicable until the module is built. + 'PSUseToExportFieldsInManifest' ) - #IncludeRules = @( - # 'PSAvoidUsingWriteHost', - # 'MyCustomRuleName' - #) } diff --git a/.github/linters/.textlintrc b/.github/linters/.textlintrc new file mode 100644 index 00000000..db48de80 --- /dev/null +++ b/.github/linters/.textlintrc @@ -0,0 +1,513 @@ +{ + "filters": { + "comments": true + }, + "rules": { + "terminology": { + "defaultTerms": false, + "terms": [ + "Airbnb", + "Android", + "AppleScript", + "AppVeyor", + "AVA", + "BrowserStack", + "Browsersync", + "Codecov", + "CodePen", + "CodeSandbox", + "DefinitelyTyped", + "EditorConfig", + "ESLint", + "GitHub", + "GraphQL", + "GraphiQL", + "iOS", + "JavaScript", + "JetBrains", + "jQuery", + "LinkedIn", + "Lodash", + "MacBook", + "Markdown", + "OpenType", + "PayPal", + "PhpStorm", + "PowerShell", + "PlayStation", + "RubyMine", + "Sass", + "SemVer", + "TypeScript", + "UglifyJS", + "Wasm", + "WebAssembly", + "WebStorm", + "WordPress", + "YouTube", + [ + "Common[ .]js", + "CommonJS" + ], + [ + "JSDocs?", + "JSDoc" + ], + [ + "Node[ .]js", + "Node.js" + ], + [ + "React[ .]js", + "React" + ], + [ + "SauceLabs", + "Sauce Labs" + ], + [ + "StackOverflow", + "Stack Overflow" + ], + [ + "styled ?components", + "styled-components" + ], + [ + "HTTP[ /]2(?:\\.0)?", + "HTTP/2" + ], + [ + "OS X", + "macOS" + ], + [ + "Mac ?OS", + "macOS" + ], + [ + "a npm", + "an npm" + ], + "ECMAScript", + [ + "ES2015", + "ES6" + ], + [ + "ES7", + "ES2016" + ], + "3D", + [ + "3-D", + "3D" + ], + "Ajax", + "API", + "APIs", + "API's", + [ + "(?> $env:GITHUB_OUTPUT + "path=$path" >> $env:GITHUB_OUTPUT + + - name: Test-ModuleLocal + uses: PSModule/Invoke-Pester@v4 + env: + TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} + TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} + TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} + TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} + TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} + TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} + TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} + with: + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + TestResult_TestSuiteName: ${{ inputs.TestName }}-${{ inputs.OS }} + TestResult_Enabled: true + CodeCoverage_Enabled: true + Output_Verbosity: Detailed + CodeCoverage_OutputFormat: JaCoCo + CodeCoverage_CoveragePercentTarget: 0 + Filter_ExcludeTag: Flaky + Path: ${{ inputs.TestPath }} + Run_Path: ${{ steps.import-module.outputs.path }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} diff --git a/.github/workflows/Test-SourceCode.yml b/.github/workflows/Test-SourceCode.yml new file mode 100644 index 00000000..d14fd5e6 --- /dev/null +++ b/.github/workflows/Test-SourceCode.yml @@ -0,0 +1,63 @@ +name: Test-SourceCode + +on: + workflow_call: + inputs: + RunsOn: + type: string + description: The type of runner to use for the job. + 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 }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Test-SourceCode + uses: PSModule/Test-PSModule@v3 + with: + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} + Settings: SourceCode diff --git a/.github/workflows/Workflow-Test-Default-CI.yml b/.github/workflows/Workflow-Test-Default-CI.yml index 7f91a079..10ba430a 100644 --- a/.github/workflows/Workflow-Test-Default-CI.yml +++ b/.github/workflows/Workflow-Test-Default-CI.yml @@ -22,8 +22,5 @@ jobs: uses: ./.github/workflows/CI.yml secrets: inherit with: - Name: PSModuleTest - Path: tests/srcTestRepo/src - ModulesOutputPath: tests/srcTestRepo/outputs/modules - DocsOutputPath: tests/srcTestRepo/outputs/docs - SkipTests: Module + WorkingDirectory: tests/srcTestRepo + Name: PSModuleTest2 diff --git a/.github/workflows/Workflow-Test-Default.yml b/.github/workflows/Workflow-Test-Default.yml index 3ae91ac9..bd82710f 100644 --- a/.github/workflows/Workflow-Test-Default.yml +++ b/.github/workflows/Workflow-Test-Default.yml @@ -24,9 +24,5 @@ jobs: uses: ./.github/workflows/workflow.yml secrets: inherit with: - Name: PSModuleTest - Path: tests/srcTestRepo/src - ModulesOutputPath: tests/srcTestRepo/outputs/modules - DocsOutputPath: tests/srcTestRepo/outputs/docs - TestProcess: true - SkipTests: Module + WorkingDirectory: tests/srcTestRepo + Name: PSModuleTest2 diff --git a/.github/workflows/Workflow-Test-WithManifest-CI.yml b/.github/workflows/Workflow-Test-WithManifest-CI.yml index 663c7e5e..8c9de2e1 100644 --- a/.github/workflows/Workflow-Test-WithManifest-CI.yml +++ b/.github/workflows/Workflow-Test-WithManifest-CI.yml @@ -22,8 +22,6 @@ jobs: uses: ./.github/workflows/CI.yml secrets: inherit with: - Name: PSModuleTest - Path: tests/srcWithManifestTestRepo/src - ModulesOutputPath: tests/srcWithManifestTestRepo/outputs/modules - DocsOutputPath: tests/srcWithManifestTestRepo/outputs/docs - SkipTests: Linux + WorkingDirectory: tests/srcWithManifestTestRepo + Name: PSModuleTest2 + SettingsPath: .github/PSModule.json diff --git a/.github/workflows/Workflow-Test-WithManifest.yml b/.github/workflows/Workflow-Test-WithManifest.yml index 40034ff2..8caaabc1 100644 --- a/.github/workflows/Workflow-Test-WithManifest.yml +++ b/.github/workflows/Workflow-Test-WithManifest.yml @@ -24,9 +24,6 @@ jobs: uses: ./.github/workflows/workflow.yml secrets: inherit with: - Name: PSModuleTest - Path: tests/srcWithManifestTestRepo/src - ModulesOutputPath: tests/srcWithManifestTestRepo/outputs/modules - DocsOutputPath: tests/srcWithManifestTestRepo/outputs/docs - TestProcess: true - SkipTests: Linux + WorkingDirectory: tests/srcWithManifestTestRepo + Name: PSModuleTest2 + SettingsPath: .github/PSModule.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 3cbf404f..bc750250 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -32,41 +32,11 @@ on: type: string description: The name of the module to process. Scripts default to the repository name if nothing is specified. required: false - Path: + SettingsPath: type: string - description: The path to the source code of the module. + description: The path to the settings file. Settings in the settings file take precedence over the action inputs. required: false - default: src - ModulesOutputPath: - type: string - description: The path to the output directory for the modules. - required: false - default: outputs/modules - DocsOutputPath: - type: string - description: The path to the output directory for the documentation. - required: false - default: outputs/docs - SiteOutputPath: - type: string - description: The path to the output directory for the site. - required: false - default: outputs/site - SkipTests: - type: string - description: Defines what types of tests to skip. Allowed values are 'All', 'SourceCode', 'Module', 'None', 'macOS', 'Windows', 'Linux'. - required: false - default: None - TestProcess: - type: boolean - description: Whether to test the process. - required: false - default: false - PublishDocs: - type: boolean - description: Whether to publish the documentation using MkDocs and GitHub Pages. - required: false - default: true + default: .github/PSModule.yml Debug: type: boolean description: Enable debug output. @@ -81,21 +51,17 @@ on: 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 - -env: - GITHUB_TOKEN: ${{ github.token }} # Used for GitHub CLI authentication - TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} - TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} - TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} - TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} - TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} - TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} - TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} + WorkingDirectory: + type: string + description: The path to the root of the repo. + required: false + default: '.' permissions: contents: write # to checkout the repo and create releases on the repo @@ -105,599 +71,208 @@ permissions: id-token: write # to verify the deployment originates from an appropriate source jobs: - TestSourceCode-pwsh-ubuntu-latest: - name: Test source code (pwsh, ubuntu-latest) - if: ${{ !(contains(inputs.SkipTests, 'All') || contains(inputs.SkipTests, 'SourceCode') || contains(inputs.SkipTests, 'Linux')) }} - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Test source code - id: test - uses: PSModule/Test-PSModule@v2 - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.Path }} - TestType: SourceCode - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - TestSourceCode-pwsh-macos-latest: - name: Test source code (pwsh, macos-latest) - if: ${{ !(contains(inputs.SkipTests, 'All') || contains(inputs.SkipTests, 'SourceCode' ) || contains(inputs.SkipTests, 'macOS')) }} - runs-on: macos-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Test source code - id: test - uses: PSModule/Test-PSModule@v2 - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.Path }} - TestType: SourceCode - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - TestSourceCode-pwsh-windows-latest: - name: Test source code (pwsh, windows-latest) - if: ${{ !(contains(inputs.SkipTests, 'All' ) || contains(inputs.SkipTests, 'SourceCode' ) || contains(inputs.SkipTests, 'Windows')) }} - runs-on: windows-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Test source code - id: test - uses: PSModule/Test-PSModule@v2 - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.Path }} - TestType: SourceCode - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - BuildModule: - name: Build module - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Build module - uses: PSModule/Build-PSModule@v3 - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.Path }} - ModulesOutputPath: ${{ inputs.ModulesOutputPath }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - BuildDocs: - name: Build docs + Get-Settings: + uses: ./.github/workflows/Get-Settings.yml + with: + Name: ${{ inputs.Name }} + SettingsPath: ${{ inputs.SettingsPath }} + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} + + Build-Module: + if: ${{ fromJson(needs.Get-Settings.outputs.Settings).Build.Module.Skip != true }} + uses: ./.github/workflows/Build-Module.yml needs: - - BuildModule - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Document module - uses: PSModule/Document-PSModule@v0 - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.Path }} - ModulesOutputPath: ${{ inputs.ModulesOutputPath }} - DocsOutputPath: ${{ inputs.DocsOutputPath }} - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Commit all changes - continue-on-error: true - shell: pwsh - run: | - # Rename the gitignore file to .gitignore.bak - Rename-Item -Path '.gitignore' -NewName '.gitignore.bak' -Force - - try { - # Add all changes to the repository - git add . - git commit -m 'Update documentation' - } catch { - Write-Host "No changes to commit" - } - - # Restore the gitignore file - Rename-Item -Path '.gitignore.bak' -NewName '.gitignore' -Force - - - name: Lint documentation - uses: super-linter/super-linter/slim@latest - env: - FILTER_REGEX_INCLUDE: '${{ inputs.DocsOutputPath }}/**' - DEFAULT_BRANCH: main - DEFAULT_WORKSPACE: ${{ github.workspace }} - ENABLE_GITHUB_ACTIONS_GROUP_TITLE: true - GITHUB_TOKEN: ${{ github.token }} - RUN_LOCAL: true - VALIDATE_ALL_CODEBASE: true - VALIDATE_JSCPD: false - VALIDATE_MARKDOWN_PRETTIER: false - VALIDATE_YAML_PRETTIER: false - VALIDATE_JSON_PRETTIER: false - VALIDATE_GITLEAKS: false - - - uses: actions/configure-pages@v5 - - - name: Install mkdoks-material - shell: pwsh - run: | - pip install mkdocs-material - pip install mkdocs-git-authors-plugin - pip install mkdocs-git-revision-date-localized-plugin - pip install mkdocs-git-committers-plugin-2 - - - name: Structure site - uses: PSModule/GitHub-Script@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - Script: | - New-Item -Path "$env:GITHUB_WORKSPACE/${{ inputs.SiteOutputPath }}/docs/Functions" -ItemType Directory -Force - Copy-Item -Path "$env:GITHUB_WORKSPACE/${{ inputs.DocsOutputPath }}/*" -Destination "$env:GITHUB_WORKSPACE/${{ inputs.SiteOutputPath }}/docs/Functions" -Recurse -Force - $moduleName = [string]::IsNullOrEmpty('${{ inputs.Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ inputs.Name }}' - $ModuleSourcePath = Join-Path $PWD -ChildPath '${{ inputs.Path }}' - $SiteOutputPath = Join-Path $PWD -ChildPath '${{ inputs.SiteOutputPath }}' - - LogGroup "Get folder structure" { - Get-ChildItem -Recurse | Select-Object -ExpandProperty FullName | Sort-Object | Format-List - } - - $functionDocsFolder = Join-Path -Path $SiteOutputPath -ChildPath 'docs/Functions' | Get-Item - Get-ChildItem -Path $functionDocsFolder -Recurse -Force -Include '*.md' | ForEach-Object { - $fileName = $_.Name - LogGroup " - $fileName" { - Show-FileContent -Path $_ - } - } - - LogGroup 'Build docs - Process about topics' { - $aboutDocsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/About' - $aboutDocsFolder = New-Item -Path $aboutDocsFolderPath -ItemType Directory -Force - $aboutSourceFolder = Get-ChildItem -Path $ModuleSourcePath -Directory | Where-Object { $_.Name -eq 'en-US' } - Get-ChildItem -Path $aboutSourceFolder -Filter *.txt | Copy-Item -Destination $aboutDocsFolder -Force -Verbose -PassThru | - Rename-Item -NewName { $_.Name -replace '.txt', '.md' } - } - - LogGroup 'Build docs - Copy icon to assets' { - $assetsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/Assets' - $null = New-Item -Path $assetsFolderPath -ItemType Directory -Force - $rootPath = Split-Path -Path $ModuleSourcePath -Parent - $iconPath = Join-Path -Path $rootPath -ChildPath 'icon\icon.png' - Copy-Item -Path $iconPath -Destination $assetsFolderPath -Force -Verbose - } - - LogGroup 'Build docs - Copy readme.md' { - $rootPath = Split-Path -Path $ModuleSourcePath -Parent - $readmePath = Join-Path -Path $rootPath -ChildPath 'README.md' - $readmeTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/README.md' - Copy-Item -Path $readmePath -Destination $readmeTargetPath -Force -Verbose - } - - LogGroup 'Build docs - Create mkdocs.yml' { - $rootPath = Split-Path -Path $ModuleSourcePath -Parent - # This should be moved to an action so that we can use a default one, and not have to copy it from the repo. - $mkdocsSourcePath = Join-Path -Path $rootPath -ChildPath 'mkdocs.yml' - $mkdocsTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'mkdocs.yml' - $mkdocsContent = Get-Content -Path $mkdocsSourcePath -Raw - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_NAME }}-', $ModuleName) - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) - $mkdocsContent | Set-Content -Path $mkdocsTargetPath -Force - Show-FileContent -Path $mkdocsTargetPath - } - - - name: Debug File system - shell: pwsh - run: | - Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse | Select-Object -ExpandProperty FullName | Sort-Object - - - name: Build mkdocs-material project - working-directory: ${{ inputs.SiteOutputPath }} - shell: pwsh - run: | - Start-LogGroup 'Build docs - mkdocs build - content' - Show-FileContent -Path mkdocs.yml - Stop-LogGroup - Start-LogGroup 'Build docs - mkdocs build' - mkdocs build --config-file mkdocs.yml --site-dir ${{ github.workspace }}/_site - Stop-LogGroup - - - uses: actions/upload-pages-artifact@v3 - - #This is necessary as there is no way to get output from a matrix job - TestModule-pwsh-ubuntu-latest: - name: Test module (pwsh, ubuntu-latest) - if: ${{ needs.BuildModule.result == 'success' && !(contains(inputs.SkipTests, 'All') || contains(inputs.SkipTests, 'Module') || contains(inputs.SkipTests, 'Linux')) && !cancelled() }} - needs: BuildModule - runs-on: ubuntu-latest - outputs: - passed: ${{ steps.test.outputs.passed }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Download module artifact - uses: actions/download-artifact@v4 - with: - name: module - path: ${{ inputs.ModulesOutputPath }} - - - name: Test built module - id: test - uses: PSModule/Test-PSModule@v2 - continue-on-error: true - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.ModulesOutputPath }} - TestType: Module - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Status - shell: pwsh - run: | - Write-Host "Passed: [${{ steps.test.outputs.passed }}]" - - - name: Failed test - if: steps.test.outcome != 'success' - shell: pwsh - run: Write-Host "Complete successfully" - - TestModule-pwsh-macos-latest: - name: Test module (pwsh, macos-latest) - if: ${{ needs.BuildModule.result == 'success' && !(contains(inputs.SkipTests, 'All') || contains(inputs.SkipTests, 'Module') || contains(inputs.SkipTests, 'macOS')) && !cancelled() }} - needs: BuildModule - runs-on: macos-latest - outputs: - passed: ${{ steps.test.outputs.passed }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Download module artifact - uses: actions/download-artifact@v4 - with: - name: module - path: ${{ inputs.ModulesOutputPath }} - - - name: Test built module - id: test - uses: PSModule/Test-PSModule@v2 - continue-on-error: true - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.ModulesOutputPath }} - TestType: Module - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Status - shell: pwsh - run: | - Write-Host "Passed: [${{ steps.test.outputs.passed }}]" - - - name: Failed test - if: steps.test.outcome != 'success' - shell: pwsh - run: Write-Host "Complete successfully" - - TestModule-pwsh-windows-latest: - name: Test module (pwsh, windows-latest) - if: ${{ needs.BuildModule.result == 'success' && !(contains(inputs.SkipTests, 'All') || contains(inputs.SkipTests, 'Module') || contains(inputs.SkipTests, 'Windows')) && !cancelled() }} - needs: BuildModule - runs-on: windows-latest - outputs: - passed: ${{ steps.test.outputs.passed }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Download module artifact - uses: actions/download-artifact@v4 - with: - name: module - path: ${{ inputs.ModulesOutputPath }} - - - name: Test built module - id: test - uses: PSModule/Test-PSModule@v2 - continue-on-error: true - with: - Name: ${{ inputs.Name }} - Path: ${{ inputs.ModulesOutputPath }} - TestType: Module - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Status - shell: pwsh - run: | - Write-Host "Passed: [${{ steps.test.outputs.passed }}]" - - - name: Failed test - if: steps.test.outcome != 'success' - shell: pwsh - run: Write-Host "Complete successfully" - - TestModuleStatus: - name: Test module status - if: ${{ contains(fromJson('["success", "skipped"]'), needs.TestModule-pwsh-ubuntu-latest.result) && contains(fromJson('["success", "skipped"]'), needs.TestModule-pwsh-macos-latest.result) && contains(fromJson('["success", "skipped"]'), needs.TestModule-pwsh-windows-latest.result) && !cancelled() }} + - Get-Settings + with: + Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} + Debug: ${{ inputs.Debug }} + Prerelease: ${{ inputs.Prerelease }} + Verbose: ${{ inputs.Verbose }} + Version: ${{ inputs.Version }} + WorkingDirectory: ${{ inputs.WorkingDirectory }} + + Build-Docs: + if: ${{ fromJson(needs.Get-Settings.outputs.Settings).Build.Docs.Skip != true }} needs: - - TestModule-pwsh-ubuntu-latest - - TestModule-pwsh-macos-latest - - TestModule-pwsh-windows-latest + - 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 }} + + Build-Site: + if: ${{ 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 }} + + Test-SourceCode: + if: ${{ needs.Get-Settings.outputs.SourceCodeTestSuites != '[]' }} + 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 }} + + Lint-SourceCode: + if: ${{ needs.Get-Settings.outputs.SourceCodeTestSuites != '[]' }} + 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 }} + + Test-Module: + if: ${{ needs.Build-Module.result == 'success' && !cancelled() && needs.Get-Settings.outputs.PSModuleTestSuites != '[]' }} + 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 }} + + Test-ModuleLocal: + if: ${{ needs.Build-Module.result == 'success' && !cancelled() && needs.Get-Settings.outputs.ModuleTestSuites != '[]' }} + needs: + - Build-Module + - Get-Settings + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.Get-Settings.outputs.ModuleTestSuites) }} + uses: ./.github/workflows/Test-ModuleLocal.yml + secrets: inherit + with: + RunsOn: ${{ matrix.RunsOn }} + OS: ${{ 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 }} + + Get-TestResults: + if: 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()) + needs: + - Get-Settings + - Test-SourceCode + - Lint-SourceCode + - Test-Module + - Test-ModuleLocal + 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 }} + + Get-CodeCoverage: + if: 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()) + needs: + - Get-Settings + - Test-Module + - Test-ModuleLocal + 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 }} + + Publish-Site: + if: ${{ 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: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - - - name: Download module artifact - uses: actions/download-artifact@v4 - with: - name: module - path: ${{ inputs.ModulesOutputPath }} - - - name: Summerize tests - uses: PSModule/GitHub-Script@v1 - with: - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - Script: | - LogGroup -Name 'Test analysis' { - - $linuxPassed = '${{ needs.TestModule-pwsh-ubuntu-latest.outputs.passed }}' -eq 'true' - $linuxSkipped = '${{ needs.TestModule-pwsh-ubuntu-latest.result }}' -eq 'skipped' - $macOSPassed = '${{ needs.TestModule-pwsh-macos-latest.outputs.passed }}' -eq 'true' - $macOSSkipped = '${{ needs.TestModule-pwsh-macos-latest.result }}' -eq 'skipped' - $windowsPassed = '${{ needs.TestModule-pwsh-windows-latest.outputs.passed }}' -eq 'true' - $windowsSkipped = '${{ needs.TestModule-pwsh-windows-latest.result }}' -eq 'skipped' - $anyPassed = $linuxPassed -or $macOSPassed -or $windowsPassed - $allSkipped = $linuxSkipped -and $macOSSkipped -and $windowsSkipped - - $Status = @( - [pscustomobject]@{ - Name = 'Linux' - Icon = $linuxSkipped ? '⚠️' : $linuxPassed ? '✅' : '❌' - Status = $linuxSkipped ? 'Skipped' : $linuxPassed ? 'Passed' : 'Failed' - } - [pscustomobject]@{ - Name = 'MacOS' - Icon = $macOSSkipped ? '⚠️' : $macOSPassed ? '✅' : '❌' - Status = $macOSSkipped ? 'Skipped' : $macOSPassed ? 'Passed' : 'Failed' - } - [pscustomobject]@{ - Name = 'Windows' - Icon = $windowsSkipped ? '⚠️' : $windowsPassed ? '✅' : '❌' - Status = $windowsSkipped ? 'Skipped' : $windowsPassed ? 'Passed' : 'Failed' - } - [pscustomobject]@{ - Name = 'Result' - Icon = $allSkipped ? '⚠️' : $anyPassed ? '✅' : '❌' - Status = $allSkipped ? 'Skipped' : $anyPassed ? 'Passed' : 'Failed' - } - ) - - Write-Host ($Status | Format-Table | Out-String) - ($Status | New-MDTable) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append - - if (-not $anyPassed -and -not $allSkipped) { - Write-Host "::[error]::No tests passed" - exit 1 - } - } - - LogGroup 'Data' { - $moduleName = [string]::IsNullOrEmpty('${{ inputs.Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ inputs.Name }}' - $path = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "${{ inputs.ModulesOutputPath }}\$moduleName" - $moduleManifestPath = Join-Path -Path $Path -ChildPath "$moduleName.psd1" - - # Obay module manifest overrides - $sourcePath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "${{ inputs.Path }}" - $sourceModuleManifestPath = Join-Path -Path $sourcePath -ChildPath "manifest.psd1" - if (Test-Path -Path $sourceModuleManifestPath) { - $sourceManifest = Import-PowerShellDataFile -Path $sourceModuleManifestPath - } - - $data = [pscustomobject]@{ - ModuleName = $moduleName - Path = $path - ModuleManifestPath = $moduleManifestPath - } - Write-Verbose ($data | Format-List | Out-String) -Verbose - - # If the source module manifest has a PowerShellVersion, use that, otherwise use the latest version. - $powerShellVersion = $sourceManifest.PowerShellVersion ?? '7.4' - Set-ModuleManifest -Path $moduleManifestPath -PowerShellVersion $powerShellVersion - - Add-ModuleManifestData -Path $moduleManifestPath -CompatiblePSEditions 'Core' - Add-ModuleManifestData -Path $moduleManifestPath -Tags 'PSEdition_Core' - - if ($linuxPassed) { - Add-ModuleManifestData -Path $moduleManifestPath -Tags 'Linux' - } - - if ($macOSPassed) { - Add-ModuleManifestData -Path $moduleManifestPath -Tags 'MacOS' - } - - if ($windowsPassed) { - Add-ModuleManifestData -Path $moduleManifestPath -Tags 'Windows' - } - } - - LogGroup 'Module Manifest - Source' { - Show-FileContent -Path $moduleManifestPath - } - - LogGroup 'Module Manifest' { - Show-FileContent -Path $moduleManifestPath - } + - uses: actions/configure-pages@v5 - - name: Upload module artifact - uses: actions/upload-artifact@v4 - with: - name: module - path: ${{ inputs.ModulesOutputPath }} - if-no-files-found: error - retention-days: 1 - overwrite: true + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 - PublishModule: - name: Publish module - if: ${{ needs.TestModuleStatus.result == 'success' && !cancelled() && github.event_name == 'pull_request' }} + Publish-Module: + if: ${{ needs.Get-Settings.result == 'success' && needs.Get-TestResults.result == 'success' && needs.Get-CodeCoverage.result == 'success' && needs.Build-Site.result == 'success' && !cancelled() && github.event_name == 'pull_request' }} needs: - - TestModuleStatus - - BuildDocs + - Get-Settings + - Get-TestResults + - Get-CodeCoverage + - Build-Site runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - name: Initialize environment uses: PSModule/Initialize-PSModule@v1 with: @@ -710,38 +285,25 @@ jobs: uses: actions/download-artifact@v4 with: name: module - path: ${{ inputs.ModulesOutputPath }} + path: ${{ inputs.WorkingDirectory }}/outputs/module - name: Publish module - uses: PSModule/Publish-PSModule@v1 + uses: PSModule/Publish-PSModule@v2 with: - Name: ${{ inputs.Name }} - ModulePath: ${{ inputs.ModulesOutputPath }} + Name: ${{ fromJson(needs.Get-Settings.outputs.Settings).Name }} + ModulePath: ${{ inputs.WorkingDirectory }}/outputs/module APIKey: ${{ secrets.APIKEY }} - WhatIf: ${{ inputs.TestProcess }} + WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }} Debug: ${{ inputs.Debug }} Prerelease: ${{ inputs.Prerelease }} Verbose: ${{ inputs.Verbose }} Version: ${{ inputs.Version }} - - PublishSite: - name: Publish documentation - if: ${{ inputs.PublishDocs && needs.BuildDocs.result == 'success' && !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.merged == true }} - needs: - - TestModuleStatus - - BuildDocs - 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: - - name: Debug - if: ${{ inputs.Debug }} - uses: PSModule/Debug@v0 - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + 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 }} diff --git a/.gitignore b/.gitignore index af4061f4..eea2b29c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ -## Ignore Visual Studio Code temporary files, build results, and -## files generated by popular Visual Studio Code add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore +# VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json !.vscode/extensions.json +!.vscode/*.code-snippets + *.code-workspace # Local History for Visual Studio Code -.history/ +.history/* diff --git a/LICENSE b/LICENSE index 92f43842..75789b63 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 PSModule +Copyright (c) 2025 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 f700871a..28259b48 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,20 @@ # Process-PSModule -A workflow for the PSModule process, stitching together the `Initialize`, `Build`, `Document`, `Test`, and `Publish` actions to create a complete -CI/CD pipeline for PowerShell modules. The workflow is used by all PowerShell modules in the PSModule organization. - -## Specifications and practices - -Process-PSModule follows: - -- [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) -- [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow) -- [SemVer 2.0.0 specifications](https://semver.org) -- [Continiuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery) +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. + +## 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 +
+ 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. ## How it works @@ -20,13 +24,39 @@ Depending on the labels in the pull requests, the workflow will result in differ ![Process diagram](./media/Process-PSModule.png) -- [Test-PSModule](https://github.com/PSModule/Test-PSModule/) - Tests and lints the source code. This runs on 3 different environments to check compatibility. - - PowerShell LTS on Windows, Ubuntu and macOS. -- [Build-PSModule](https://github.com/PSModule/Build-PSModule/) - Compiles the repository into an efficient PowerShell module. -- [Document-PSModule](https://github.com/PSModule/Document-PSModule/) - Generates documentation and deploys it to GitHub Pages. -- [Test-PSModule](https://github.com/PSModule/Test-PSModule/) - Tests the compiled module. This runs on 4 different environments to check compatibility. - - PowerShell LTS on Windows, Ubuntu and macOS. -- [Publish-PSModule](https://github.com/PSModule/Publish-PSModule/) - Publishes the module to the PowerShell Gallery, docs to GitHub Pages, and creates a release on the GitHub repository. +- [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 foor 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. + - 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 documentationto GitHub Pages. +- [Publish module](./.github/workflows/Publish-Module.yml) + - Publishes the module to the PowerShell Gallery. + - Creates a release on the GitHub repository. To use the workflow, create a new file in the `.github/workflows` directory of the module repository and add the following content.
@@ -57,11 +87,162 @@ permissions: jobs: Process-PSModule: uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v2 - secrets: inherit + secrets: + APIKEY: ${{ secrets.APIKEY }} + +``` +
+ +## Configuration + +The workflow is configured using a settings file in the module repository. +The file can be a `JSON`, `YAML` or `PSD1` file. By default it will look for `.github/PSModule.yml`. + +The following settings are available in the settings file: +Here's a Markdown-formatted table describing your PowerShell object structure clearly and concisely: + +| 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.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](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'` | + +
+`PSModule.yml` with all defaults + +```yml +Name: null + +Build: + Skip: false + Module: + Skip: false + Docs: + Skip: false + Site: + Skip: false + +Test: + Skip: false + Linux: + Skip: false + MacOS: + Skip: false + Windows: + Skip: false + SourceCode: + Skip: false + Linux: + Skip: false + MacOS: + Skip: false + Windows: + Skip: false + PSModule: + Skip: false + Linux: + Skip: false + MacOS: + Skip: false + Windows: + Skip: false + Module: + Skip: false + Linux: + Skip: false + MacOS: + Skip: false + Windows: + Skip: false + TestResults: + Skip: false + CodeCoverage: + Skip: false + PercentTarget: 0 + StepSummaryMode: 'Missed, Files' + +Publish: + Module: + Skip: false + AutoCleanup: true + AutoPatching: true + IncrementalPrerelease: true + DatePrereleaseFormat: '' + VersionPrefix: 'v' + MajorLabels: 'major, breaking' + MinorLabels: 'minor, feature' + PatchLabels: 'patch, fix' + IgnoreLabels: 'NoRelease' ```
+### Example 1 - Defaults with Code Coverage target + +This example runs all steps and will require that code coverage is 80% before passing. + +```yaml +Test: + CodeCoverage: + PercentTarget: 80 +``` + +### Example 2 - Rapid testing + +This example ends up running Get-Settings, Build-Module and Test-Module (tests from the module repo) on ubuntu-latest. + +```yaml +Test: + SourceCode: + Skip: true + PSModule: + Skip: true + Module: + MacOS: + Skip: true + Windows: + Skip: true + TestResults: + Skip: true + CodeCoverage: + Skip: true +Build: + Docs: + Skip: true +``` + ## Usage ### Inputs @@ -69,17 +250,12 @@ jobs: | 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 | -| `Path` | `string` | The path to the source code of the module. | `false` | `src` | -| `ModulesOutputPath` | `string` | The path to the output directory for the modules. | `false` | `outputs/modules` | -| `DocsOutputPath` | `string` | The path to the output directory for the documentation. | `false` | `outputs/docs` | -| `PublishDocs` | `boolean` | Whether to publish the documentation using MkDocs and GitHub Pages. | `false` | `true` | -| `SiteOutputPath` | `string` | The path to the output directory for the site. | `false` | `outputs/site` | -| `SkipTests` | `string` | Defines what types of tests to skip. Allowed values are 'All', 'SourceCode', 'Module', 'None', 'macOS', 'Windows', 'Linux'. | `false` | `None` | -| `TestProcess` | `boolean` | Whether to test the process. | `false` | `false` | -| `Version` | `string` | Specifies the version of the GitHub module to be installed. The value must be an exact version. | `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` | +| `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` | `.` | ### Secrets @@ -88,8 +264,7 @@ in the workflow file. | Name | Location | Description | Default | | ---- | -------- | ----------- | ------- | -| `GITHUB_TOKEN` | `github` context | The token used to authenticate with GitHub. | `${{ secrets.GITHUB_TOKEN }}` | -| `APIKey` | GitHub secrets | The API key for the PowerShell Gallery. | N/A | +| `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 | @@ -123,12 +298,11 @@ permissions: For more info see [Deploy GitHub Pages site](https://github.com/marketplace/actions/deploy-github-pages-site). -## Compatibility +## Specifications and practices -The action is compatible with the following configurations: +Process-PSModule follows: -| OS | Shell | -| --- | --- | -| windows-latest | pwsh | -| macos-latest | pwsh | -| ubuntu-latest | pwsh | +- [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) +- [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow) +- [SemVer 2.0.0 specifications](https://semver.org) +- [Continiuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery) diff --git a/media/.$Flow.drawio.bkp b/media/.$Flow.drawio.bkp new file mode 100644 index 00000000..95b111d1 --- /dev/null +++ b/media/.$Flow.drawio.bkp @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/media/Flow.drawio b/media/Flow.drawio new file mode 100644 index 00000000..b9fc274b --- /dev/null +++ b/media/Flow.drawio @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/media/Process-PSModule.png b/media/Process-PSModule.png index 9c15217e..fffc5736 100644 Binary files a/media/Process-PSModule.png and b/media/Process-PSModule.png differ diff --git a/media/pagesEnvironment.png b/media/pagesEnvironment.png new file mode 100644 index 00000000..59f411d0 Binary files /dev/null and b/media/pagesEnvironment.png differ diff --git a/tests/srcTestRepo/tests/Environment.Tests.ps1 b/tests/srcTestRepo/tests/Environment.Tests.ps1 new file mode 100644 index 00000000..211be946 --- /dev/null +++ b/tests/srcTestRepo/tests/Environment.Tests.ps1 @@ -0,0 +1,15 @@ +Describe 'Environment Variables are available' { + It 'Should be available [<_>]' -ForEach @( + 'TEST_APP_ENT_CLIENT_ID', + 'TEST_APP_ENT_PRIVATE_KEY', + 'TEST_APP_ORG_CLIENT_ID', + 'TEST_APP_ORG_PRIVATE_KEY', + 'TEST_USER_ORG_FG_PAT', + 'TEST_USER_USER_FG_PAT', + 'TEST_USER_PAT' + ) { + $name = $_ + Write-Verbose "Environment variable: [$name]" -Verbose + Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 b/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 index 2be65edb..9bf1bb64 100644 --- a/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 +++ b/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 @@ -6,22 +6,6 @@ Param( ) Write-Verbose "Path to the module: [$Path]" -Verbose -Describe 'Environment Variables are available' { - It 'Should be available [<_>]' -ForEach @( - 'TEST_APP_ENT_CLIENT_ID', - 'TEST_APP_ENT_PRIVATE_KEY', - 'TEST_APP_ORG_CLIENT_ID', - 'TEST_APP_ORG_PRIVATE_KEY', - 'TEST_USER_ORG_FG_PAT', - 'TEST_USER_USER_FG_PAT', - 'TEST_USER_PAT' - ) { - $name = $_ - Write-Verbose "Environment variable: [$name]" -Verbose - Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty - } -} - Describe 'PSModuleTest.Tests.ps1' { Context 'Function: Test-PSModuleTest' { It 'Should be able to call the function' { diff --git a/tests/srcWithManifestTestRepo/.github/PSModule.json b/tests/srcWithManifestTestRepo/.github/PSModule.json new file mode 100644 index 00000000..b175eeb5 --- /dev/null +++ b/tests/srcWithManifestTestRepo/.github/PSModule.json @@ -0,0 +1,22 @@ +{ + "Name": "PSModuleTest", + "Test": { + "SourceCode": { + "Skip": true + }, + "PSModule": { + "Linux": { + "Skip": true + } + }, + "Module": { + "Skip": false + }, + "CodeCoverage": { + "PercentTarget": 1 + } + }, + "Publish": { + "AutoCleanup": false + } +} diff --git a/tests/srcWithManifestTestRepo/.github/PSModule.psd1 b/tests/srcWithManifestTestRepo/.github/PSModule.psd1 new file mode 100644 index 00000000..e680e8fd --- /dev/null +++ b/tests/srcWithManifestTestRepo/.github/PSModule.psd1 @@ -0,0 +1,22 @@ +@{ + Name = 'PSModuleTest' + Test = @{ + SourceCode = @{ + Skip = $true + } + PSModule = @{ + Linux = @{ + Skip = $true + } + } + Module = @{ + Skip = $false + } + CodeCoverage = @{ + PercentTarget = 1 + } + } + Publish = @{ + AutoCleanup = $false + } +} diff --git a/tests/srcWithManifestTestRepo/.github/PSModule.yml b/tests/srcWithManifestTestRepo/.github/PSModule.yml new file mode 100644 index 00000000..d285c1ed --- /dev/null +++ b/tests/srcWithManifestTestRepo/.github/PSModule.yml @@ -0,0 +1,13 @@ +Name: PSModuleTest +Test: + SourceCode: + Skip: true + PSModule: + Linux: + Skip: true + Module: + Skip: false + CodeCoverage: + PercentTarget: 1 +Publish: + AutoCleanup: false diff --git a/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 b/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 new file mode 100644 index 00000000..211be946 --- /dev/null +++ b/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 @@ -0,0 +1,15 @@ +Describe 'Environment Variables are available' { + It 'Should be available [<_>]' -ForEach @( + 'TEST_APP_ENT_CLIENT_ID', + 'TEST_APP_ENT_PRIVATE_KEY', + 'TEST_APP_ORG_CLIENT_ID', + 'TEST_APP_ORG_PRIVATE_KEY', + 'TEST_USER_ORG_FG_PAT', + 'TEST_USER_USER_FG_PAT', + 'TEST_USER_PAT' + ) { + $name = $_ + Write-Verbose "Environment variable: [$name]" -Verbose + Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/srcWithManifestTestRepo/tests/PSModuleTest.Tests.ps1 b/tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 similarity index 74% rename from tests/srcWithManifestTestRepo/tests/PSModuleTest.Tests.ps1 rename to tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 index 2be65edb..9bf1bb64 100644 --- a/tests/srcWithManifestTestRepo/tests/PSModuleTest.Tests.ps1 +++ b/tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 @@ -6,22 +6,6 @@ Param( ) Write-Verbose "Path to the module: [$Path]" -Verbose -Describe 'Environment Variables are available' { - It 'Should be available [<_>]' -ForEach @( - 'TEST_APP_ENT_CLIENT_ID', - 'TEST_APP_ENT_PRIVATE_KEY', - 'TEST_APP_ORG_CLIENT_ID', - 'TEST_APP_ORG_PRIVATE_KEY', - 'TEST_USER_ORG_FG_PAT', - 'TEST_USER_USER_FG_PAT', - 'TEST_USER_PAT' - ) { - $name = $_ - Write-Verbose "Environment variable: [$name]" -Verbose - Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty - } -} - Describe 'PSModuleTest.Tests.ps1' { Context 'Function: Test-PSModuleTest' { It 'Should be able to call the function' { diff --git a/tests/srcWithManifestTestRepo/tools/1-build.ps1 b/tests/srcWithManifestTestRepo/tools/1-build.ps1 index f3a17f23..e762395b 100644 --- a/tests/srcWithManifestTestRepo/tools/1-build.ps1 +++ b/tests/srcWithManifestTestRepo/tools/1-build.ps1 @@ -1 +1 @@ -Write-Host "1 - Build script executed." +"1 - Build script executed." diff --git a/tests/srcWithManifestTestRepo/tools/2-build.ps1 b/tests/srcWithManifestTestRepo/tools/2-build.ps1 index d839b516..d2575a02 100644 --- a/tests/srcWithManifestTestRepo/tools/2-build.ps1 +++ b/tests/srcWithManifestTestRepo/tools/2-build.ps1 @@ -1 +1 @@ -Write-Host "2 - Build script executed." +"2 - Build script executed."