Build, Test, and Publish PowerShell Module #62
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-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser | |
| Invoke-Pester -Path ./Tests -Output Detailed -EnableExit | |
| } | |
| 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 }} |