Build, Test, and Publish PowerShell Module #65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build, Test, and Publish PowerShell Module | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry run (skip publishing, tagging, and releasing)?' | |
| required: false | |
| default: 'true' | |
| permissions: | |
| contents: write # Required for git tag and release | |
| jobs: | |
| ##################### | |
| # 1. Build Job | |
| ##################### | |
| build: | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Locate module manifest and prepare | |
| id: build_module | |
| shell: pwsh | |
| run: | | |
| $manifest = Get-ChildItem -Recurse -Include *.psd1 | Where-Object { $_.Name -notlike '*Tests*' } | Select-Object -First 1 | |
| if (-not $manifest) { | |
| Write-Error "❌ Module manifest not found!" | |
| exit 1 | |
| } | |
| $moduleName = [IO.Path]::GetFileNameWithoutExtension($manifest.Name) | |
| $version = (Import-PowerShellDataFile -Path $manifest.FullName).ModuleVersion.ToString() | |
| "MODULE_NAME=$moduleName" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| echo "moduleName=$moduleName" >> $GITHUB_OUTPUT | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| Write-Host "✅ Found module '$moduleName' version $version" | |
| ##################### | |
| # 2. Test Job | |
| ##################### | |
| test: | |
| needs: build | |
| runs-on: windows-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Run Pester Tests (if any) | |
| shell: pwsh | |
| run: | | |
| if (Test-Path -Path ./Tests -PathType Container) { | |
| $testFiles = Get-ChildItem -Path ./Tests -Recurse -Include '*.Tests.ps1' | |
| if ($testFiles.Count -gt 0) { | |
| # Install latest Pester v5 | |
| Install-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser | |
| # Run tests with Pester v5 | |
| $result = Invoke-Pester -Path ./Tests -Output Detailed -PassThru | |
| $result | Out-Default # ensures results show in logs | |
| if ($result.FailedCount -gt 0) { | |
| Write-Error "One or more Pester tests failed." | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-Host "No Pester test files found, skipping tests." | |
| } | |
| } | |
| else { | |
| Write-Host "Tests folder not found, skipping tests." | |
| } | |
| ##################### | |
| # 3. Publish Job | |
| ##################### | |
| publish: | |
| needs: test | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| if: success() | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Show dry run mode | |
| run: | | |
| echo "🧪 Dry run mode:" | |
| echo "${{ github.event.inputs.dry_run || 'true' }}" | |
| - name: Install PowerShellGet (if needed) | |
| shell: pwsh | |
| run: | | |
| if (-not (Get-Module -ListAvailable -Name PowerShellGet)) { | |
| Install-Module -Name PowerShellGet -Force -Scope CurrentUser -AllowClobber | |
| } | |
| - name: Prepare Module Files | |
| id: prepare_publish | |
| shell: pwsh | |
| run: | | |
| $manifest = Get-ChildItem -Recurse -Include *.psd1 | Where-Object { $_.Name -notlike '*Tests*' } | Select-Object -First 1 | |
| if (-not $manifest) { | |
| Write-Error "❌ Module manifest not found!" | |
| exit 1 | |
| } | |
| $moduleName = [IO.Path]::GetFileNameWithoutExtension($manifest.Name) | |
| $version = (Import-PowerShellDataFile -Path $manifest.FullName).ModuleVersion.ToString() | |
| $publishDir = Join-Path -Path $env:RUNNER_TEMP -ChildPath "publish" | |
| $publishModuleDir = Join-Path -Path $publishDir -ChildPath $moduleName | |
| if (Test-Path $publishDir) { Remove-Item -Recurse -Force $publishDir } | |
| New-Item -ItemType Directory -Path $publishModuleDir -Force | Out-Null | |
| $includeExtensions = @('*.psd1', '*.psm1', '*.ps1') | |
| foreach ($ext in $includeExtensions) { | |
| Copy-Item -Path "$($manifest.Directory.FullName)\$ext" -Destination $publishModuleDir -Recurse -ErrorAction SilentlyContinue | |
| } | |
| "MODULE_NAME=$moduleName" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "PUBLISH_DIR=$publishModuleDir" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| Write-Host "✅ Module prepared for publish: $publishModuleDir" | |
| - name: Publish PowerShell Module | |
| if: ${{ (github.event.inputs.dry_run || 'true') == 'false' }} | |
| shell: pwsh | |
| env: | |
| PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} | |
| run: | | |
| Publish-Module -Path "${{ env.PUBLISH_DIR }}" -NuGetApiKey $env:PSGALLERY_API_KEY | |
| Write-Host "✅ Module published." | |
| - name: Delete existing Git tag (if any) | |
| if: ${{ (github.event.inputs.dry_run || 'true') == 'false' }} | |
| run: | | |
| git fetch --tags | |
| git tag -d "v${{ env.VERSION }}" 2>$null || echo "No local tag" | |
| git push origin :refs/tags/v${{ env.VERSION }} 2>$null || echo "No remote tag" | |
| - name: Create Git tag | |
| if: ${{ (github.event.inputs.dry_run || 'true') == 'false' }} | |
| run: | | |
| git config user.name "github-actions" | |
| git config user.email "[email protected]" | |
| git tag "v${{ env.VERSION }}" | |
| git push origin "v${{ env.VERSION }}" | |
| - name: Create GitHub Release | |
| if: ${{ (github.event.inputs.dry_run || 'true') == 'false' }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ env.VERSION }} | |
| name: PS-NCentral-RESTAPI v${{ env.VERSION }} | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |