Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/commands/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Run the full build pipeline (lint -> test -> build -> docs):

```powershell
Invoke-Build
```

Or run individual tasks:

```powershell
Invoke-Build Lint # PSScriptAnalyzer only
Invoke-Build Test # Pester tests with JaCoCo coverage
Invoke-Build Build # Stage module to .build/
Invoke-Build Docs # Generate markdown help via platyPS
Invoke-Build Clean # Remove .build/
```
19 changes: 19 additions & 0 deletions .claude/commands/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Generate markdown documentation from comment-based help:

```powershell
# Build the module first, then generate docs
Invoke-Build Build, Docs
```

Or after an initial build, regenerate docs only:

```powershell
Invoke-Build Docs
```

Docs are written to `docs/` as platyPS-formatted markdown.

When adding a new public function:
1. Add the function to `src/MyModule/Public/`
2. Add its name to `FunctionsToExport` in `src/MyModule/MyModule.psd1`
3. Run `Invoke-Build Docs` to generate its help page
11 changes: 11 additions & 0 deletions .claude/commands/lint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Run PSScriptAnalyzer against the source:

```powershell
Invoke-Build Lint
```

Or directly:

```powershell
Invoke-ScriptAnalyzer -Path ./src -Settings ./PSScriptAnalyzerSettings.psd1 -Recurse -ReportSummary
```
23 changes: 23 additions & 0 deletions .claude/commands/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Run Pester 5 tests with code coverage:

```powershell
Invoke-Build Test
```

Or directly:

```powershell
$config = New-PesterConfiguration
$config.Run.Path = './tests'
$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = './src'
$config.CodeCoverage.UseBreakpoints = $false
$config.CodeCoverage.OutputFormat = 'JaCoCo'
$config.CodeCoverage.OutputPath = '.build/coverage.xml'
Invoke-Pester -Configuration $config
```

Coverage report is written to `.build/coverage.xml` (JaCoCo format, compatible with Codecov).
Test results are written to `.build/testresults.xml` (JUnit XML).
33 changes: 33 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "PowerShell Module",
"image": "mcr.microsoft.com/devcontainers/powershell:7.4",
"postCreateCommand": [
"pwsh", "-Command",
"Set-PSRepository PSGallery -InstallationPolicy Trusted; Install-Module InvokeBuild, Pester, PSScriptAnalyzer, platyPS, Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force"
],
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.powershell",
"editorconfig.editorconfig",
"github.vscode-github-actions",
"GitHub.copilot",
"GitHub.copilot-chat"
],
"settings": {
"powershell.scriptAnalysis.enable": true,
"powershell.scriptAnalysis.settingsPath": "${workspaceFolder}/PSScriptAnalyzerSettings.psd1",
"powershell.pester.useLegacyCodeLens": false,
"powershell.pester.outputVerbosity": "Diagnostic",
"[powershell]": {
"editor.defaultFormatter": "ms-vscode.powershell",
"editor.formatOnSave": true,
"editor.rulers": [120]
},
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}
}
},
"remoteUser": "vscode"
}
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

[*.{yml,yaml}]
indent_size = 2

[*.json]
indent_size = 2

[*.md]
trim_trailing_whitespace = false
125 changes: 125 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install tools
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module InvokeBuild -Scope CurrentUser -Force
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force

- name: Lint
shell: pwsh
run: Invoke-Build Lint

test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4

- name: Install tools
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module InvokeBuild -Scope CurrentUser -Force
Install-Module Pester -Scope CurrentUser -Force -MinimumVersion '5.0'

- name: Test
shell: pwsh
run: Invoke-Build Test

- name: Upload coverage
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v5
with:
files: .build/coverage.xml

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}
path: .build/testresults.xml

security:
name: Security
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install tools
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force

- name: Security scan
shell: pwsh
run: |
$rules = @(
'PSAvoidUsingInvokeExpression',
'PSAvoidUsingConvertToSecureStringWithPlainText',
'PSAvoidUsingPlainTextForPassword',
'PSAvoidUsingUsernameAndPasswordParams',
'PSAvoidUsingComputerNameHardcoded',
'PSAvoidUsingWMICmdlet'
)
$results = Invoke-ScriptAnalyzer -Path ./src -Recurse -IncludeRule $rules
if ($results) {
$results | Format-Table RuleName, Severity, ScriptName, Line, Message -AutoSize
exit 1
}

build:
name: Build & Docs
runs-on: ubuntu-latest
needs: [lint, test, security]
steps:
- uses: actions/checkout@v4

- name: Install tools
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module InvokeBuild -Scope CurrentUser -Force
Install-Module platyPS -Scope CurrentUser -Force

- name: Build
shell: pwsh
run: Invoke-Build Build

- name: Docs
shell: pwsh
run: Invoke-Build Docs

- name: Upload module artifact
uses: actions/upload-artifact@v4
with:
name: module-package
path: .build/MyModule/

- name: Upload docs artifact
uses: actions/upload-artifact@v4
with:
name: docs
path: docs/
129 changes: 129 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Publish

on:
push:
tags:
- 'v*.*.*'

permissions:
contents: read
packages: write

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install tools
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module InvokeBuild -Scope CurrentUser -Force
Install-Module platyPS -Scope CurrentUser -Force

- name: Build
shell: pwsh
run: Invoke-Build Build

- name: Set ModuleVersion from tag
shell: pwsh
run: |
$version = '${{ github.ref_name }}' -replace '^v', ''
$manifestPath = '.build/MyModule/MyModule.psd1'
$content = Get-Content -Path $manifestPath -Raw
$content = [regex]::Replace($content, "ModuleVersion\s*=\s*'[^']*'", "ModuleVersion = '$version'")
Set-Content -Path $manifestPath -Value $content.TrimEnd() -Encoding utf8NoBOM
Write-Host "ModuleVersion set to $version"

- name: Upload module artifact
uses: actions/upload-artifact@v4
with:
name: module-package-${{ github.ref_name }}
path: .build/MyModule/
retention-days: 1

publish-gallery:
name: Publish to PowerShell Gallery
runs-on: ubuntu-latest
needs: build
environment: release
steps:
- uses: actions/download-artifact@v4
with:
name: module-package-${{ github.ref_name }}
path: .build/MyModule/

- name: Install PSResourceGet
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force

- name: Publish
shell: pwsh
env:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
run: Publish-PSResource -Path .build/MyModule -Repository PSGallery -ApiKey $env:PSGALLERY_API_KEY

publish-github:
name: Publish to GitHub Packages
runs-on: ubuntu-latest
needs: build
environment: release
steps:
- uses: actions/download-artifact@v4
with:
name: module-package-${{ github.ref_name }}
path: .build/MyModule/

- name: Install PSResourceGet
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force

- name: Publish
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_OWNER: ${{ github.repository_owner }}
run: |
$uri = "https://nuget.pkg.github.com/$env:GITHUB_OWNER/index.json"
$cred = [pscredential]::new(
$env:GITHUB_OWNER,
(ConvertTo-SecureString $env:GITHUB_TOKEN -AsPlainText -Force)
)
Register-PSResourceRepository -Name 'GitHubPackages' -Uri $uri -Trusted -Credential $cred -ErrorAction SilentlyContinue
Publish-PSResource -Path .build/MyModule -Repository 'GitHubPackages' -ApiKey $env:GITHUB_TOKEN

publish-ado:
name: Publish to Azure DevOps Artifacts
runs-on: ubuntu-latest
needs: build
environment: release
if: vars.ADO_ORG != ''
steps:
- uses: actions/download-artifact@v4
with:
name: module-package-${{ github.ref_name }}
path: .build/MyModule/

- name: Install PSResourceGet
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force

- name: Publish
shell: pwsh
env:
ADO_PAT: ${{ secrets.ADO_PAT }}
ADO_ORG: ${{ vars.ADO_ORG }}
ADO_FEED: ${{ vars.ADO_FEED }}
run: |
$uri = "https://pkgs.dev.azure.com/$env:ADO_ORG/_packaging/$env:ADO_FEED/nuget/v3/index.json"
$cred = [pscredential]::new('PAT', (ConvertTo-SecureString $env:ADO_PAT -AsPlainText -Force))
Register-PSResourceRepository -Name 'ADOFeed' -Uri $uri -Trusted -Credential $cred -ErrorAction SilentlyContinue
Publish-PSResource -Path .build/MyModule -Repository 'ADOFeed' -Credential $cred
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Build output
.build/

# Compiled module artifacts
*.nupkg

# OS
.DS_Store
Thumbs.db

# Editor
.vscode/*.code-workspace

# Secrets
.env
*.env.local
Loading
Loading