Skip to content

Commit e674f9e

Browse files
tablackburnclaude
andcommitted
feat: Initial PowerShell module template
Complete template for creating PowerShell modules with: - Build system (psake + PowerShellBuild + PSDepend) - Module scaffolding (Public/Private function structure) - Test infrastructure (Pester 5.x with code coverage) - CI/CD (GitHub Actions multi-OS matrix + auto-publish) - VS Code configuration (settings, tasks, extensions) - DevContainer (PowerShell LTS + Claude Code CLI) - AI instructions (AIM modules for agent guidance) - Initialize-Template.ps1 for easy setup Template variables: - {{ModuleName}} - Module name - {{Prefix}} - Function prefix - {{Author}}, {{Description}}, {{ProjectUri}} - {{GUID}}, {{Date}}, {{Year}} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0 parents  commit e674f9e

45 files changed

Lines changed: 4210 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# DevContainer for {{ModuleName}} PowerShell module development
2+
# Supports Claude Code and Claude Code for Web
3+
FROM mcr.microsoft.com/powershell:lts-ubuntu-22.04
4+
5+
ARG NODE_VERSION=20
6+
ARG USERNAME=vscode
7+
ARG USER_UID=1000
8+
ARG USER_GID=$USER_UID
9+
10+
# Install system dependencies
11+
RUN apt-get update && apt-get install -y --no-install-recommends \
12+
git \
13+
curl \
14+
jq \
15+
sudo \
16+
ca-certificates \
17+
gnupg \
18+
less \
19+
&& rm -rf /var/lib/apt/lists/* \
20+
&& git config --system --add safe.directory '*'
21+
22+
# Install GitHub CLI
23+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
24+
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
25+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
26+
&& apt-get update \
27+
&& apt-get install -y gh \
28+
&& rm -rf /var/lib/apt/lists/*
29+
30+
# Install Node.js (required for Claude Code CLI)
31+
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
32+
&& apt-get install -y nodejs \
33+
&& rm -rf /var/lib/apt/lists/*
34+
35+
# Install Claude Code CLI globally
36+
RUN npm install -g @anthropic-ai/claude-code
37+
38+
# Create non-root user with sudo access
39+
RUN groupadd --gid $USER_GID $USERNAME \
40+
&& useradd --uid $USER_UID --gid $USER_GID -m -s /bin/bash $USERNAME \
41+
&& echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/$USERNAME \
42+
&& chmod 0440 /etc/sudoers.d/$USERNAME
43+
44+
# Create directories for PowerShell modules and history persistence
45+
RUN mkdir -p /home/$USERNAME/.local/share/powershell/Modules \
46+
&& mkdir -p /home/$USERNAME/.local/share/powershell/PSReadLine \
47+
&& chown -R $USERNAME:$USERNAME /home/$USERNAME/.local
48+
49+
# Switch to non-root user
50+
USER $USERNAME
51+
WORKDIR /workspace
52+
53+
# Pre-configure PowerShell: trust PSGallery for faster module installation
54+
RUN pwsh -NoProfile -Command "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted"
55+
56+
# Set environment variables
57+
ENV DEVCONTAINER=true
58+
ENV SHELL=/usr/bin/pwsh

.devcontainer/devcontainer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "{{ModuleName}} DevContainer",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"context": ".."
6+
},
7+
"remoteUser": "vscode",
8+
"containerEnv": {
9+
"DEVCONTAINER": "true"
10+
},
11+
"mounts": [
12+
"source={{ModuleNameLower}}-pwsh-modules,target=/home/vscode/.local/share/powershell/Modules,type=volume",
13+
"source={{ModuleNameLower}}-pwsh-history,target=/home/vscode/.local/share/powershell/PSReadLine,type=volume"
14+
],
15+
"postCreateCommand": "pwsh ./build.ps1 -Task Init -Bootstrap",
16+
"customizations": {
17+
"vscode": {
18+
"extensions": [
19+
"ms-vscode.powershell",
20+
"anthropic.claude-code"
21+
],
22+
"settings": {
23+
"powershell.codeFormatting.preset": "OTBS",
24+
"powershell.scriptAnalysis.settingsPath": "./PSScriptAnalyzerSettings.psd1",
25+
"terminal.integrated.defaultProfile.linux": "pwsh",
26+
"terminal.integrated.profiles.linux": {
27+
"pwsh": {
28+
"path": "/usr/bin/pwsh",
29+
"icon": "terminal-powershell"
30+
},
31+
"bash": {
32+
"path": "/bin/bash",
33+
"icon": "terminal-bash"
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}

.github/FUNDING.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Sponsorship configuration
2+
# Uncomment and configure the platforms you want to use
3+
# github: your-username
4+
# patreon: your-username
5+
# ko_fi: your-username
6+
# buy_me_a_coffee: your-username

.github/dependabot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
commit-message:
8+
prefix: "ci"
9+
labels:
10+
- "dependencies"
11+
- "github-actions"

.github/workflows/CI.yaml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
lint:
15+
name: PSScriptAnalyzer Lint
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Cache PowerShell modules
21+
uses: actions/cache@v4
22+
with:
23+
path: ~/.local/share/powershell/Modules
24+
key: ${{ runner.os }}-psmodules-lint-${{ hashFiles('build.depend.psd1') }}
25+
restore-keys: |
26+
${{ runner.os }}-psmodules-lint-
27+
28+
- name: Install PSScriptAnalyzer
29+
shell: pwsh
30+
run: |
31+
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
32+
Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
33+
34+
- name: Run PSScriptAnalyzer
35+
shell: pwsh
36+
run: |
37+
$results = Invoke-ScriptAnalyzer -Path ./{{ModuleName}} -Recurse -Settings PSGallery -ReportSummary
38+
$errors = $results | Where-Object { $_.Severity -eq 'Error' }
39+
40+
if ($results) {
41+
Write-Host "::group::PSScriptAnalyzer Results"
42+
$results | Format-Table -AutoSize
43+
Write-Host "::endgroup::"
44+
}
45+
46+
if ($errors) {
47+
Write-Host "::error::PSScriptAnalyzer found $($errors.Count) error(s)"
48+
exit 1
49+
}
50+
51+
Write-Host "PSScriptAnalyzer passed with no errors"
52+
53+
unit-tests:
54+
name: Unit Tests (${{ matrix.os }})
55+
runs-on: ${{ matrix.os }}
56+
strategy:
57+
fail-fast: false
58+
matrix:
59+
os: [ubuntu-latest, windows-latest, macOS-latest]
60+
steps:
61+
- uses: actions/checkout@v4
62+
63+
- name: Cache PowerShell modules
64+
uses: actions/cache@v4
65+
with:
66+
path: |
67+
~/Documents/PowerShell/Modules
68+
~/.local/share/powershell/Modules
69+
key: ${{ runner.os }}-psmodules-${{ hashFiles('build.depend.psd1') }}
70+
restore-keys: |
71+
${{ runner.os }}-psmodules-
72+
73+
- name: Build and Test
74+
shell: pwsh
75+
run: |
76+
New-Item -Path out -ItemType Directory -Force | Out-Null
77+
./build.ps1 -Task Build,Test -Bootstrap
78+
79+
- name: Upload Coverage to Codecov
80+
uses: codecov/codecov-action@v5
81+
if: success()
82+
with:
83+
token: ${{ secrets.CODECOV_TOKEN }}
84+
files: out/codeCoverage.xml
85+
flags: ${{ matrix.os }}
86+
fail_ci_if_error: false
87+
88+
- name: Upload Test Results
89+
uses: actions/upload-artifact@v4
90+
if: always()
91+
with:
92+
name: test-results-${{ matrix.os }}
93+
path: out/
94+
retention-days: 30
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Publish Module to PowerShell Gallery
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- '{{ModuleName}}/{{ModuleName}}.psd1'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
publish:
16+
name: Publish Module
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Get Module Version
25+
id: version
26+
shell: pwsh
27+
run: |
28+
$manifest = Import-PowerShellDataFile -Path ./{{ModuleName}}/{{ModuleName}}.psd1
29+
$version = $manifest.ModuleVersion
30+
Write-Host "Module version: $version"
31+
"version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
32+
33+
- name: Check if Release Exists
34+
id: check_release
35+
shell: bash
36+
env:
37+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
run: |
39+
if gh release view "v${{ steps.version.outputs.version }}" > /dev/null 2>&1; then
40+
echo "exists=true" >> $GITHUB_OUTPUT
41+
echo "GitHub release v${{ steps.version.outputs.version }} already exists"
42+
else
43+
echo "exists=false" >> $GITHUB_OUTPUT
44+
echo "GitHub release v${{ steps.version.outputs.version }} does not exist"
45+
fi
46+
47+
- name: Check if PSGallery Version Exists
48+
id: check_psgallery
49+
if: steps.check_release.outputs.exists == 'false'
50+
shell: pwsh
51+
run: |
52+
$version = "${{ steps.version.outputs.version }}"
53+
$published = Find-Module -Name {{ModuleName}} -RequiredVersion $version -Repository PSGallery -ErrorAction SilentlyContinue
54+
if ($published) {
55+
Write-Host "PSGallery version $version already exists"
56+
"exists=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
57+
} else {
58+
Write-Host "PSGallery version $version not found - will publish"
59+
"exists=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
60+
}
61+
62+
- name: Bootstrap
63+
if: steps.check_release.outputs.exists == 'false'
64+
shell: pwsh
65+
run: ./build.ps1 -Task Init -Bootstrap
66+
67+
- name: Create GitHub Release
68+
if: steps.check_release.outputs.exists == 'false'
69+
shell: bash
70+
env:
71+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72+
run: |
73+
gh release create "v${{ steps.version.outputs.version }}" \
74+
--title "v${{ steps.version.outputs.version }}" \
75+
--generate-notes
76+
77+
- name: Publish to PSGallery
78+
if: steps.check_release.outputs.exists == 'false' && steps.check_psgallery.outputs.exists == 'false'
79+
shell: pwsh
80+
env:
81+
PSGALLERY_API_KEY: ${{ secrets.PS_GALLERY_KEY }}
82+
run: ./build.ps1 -Task Publish -Bootstrap

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Build output
2+
Output/
3+
4+
# Test results
5+
testResults.xml
6+
codeCoverage.xml
7+
coverage.xml
8+
out/
9+
10+
# Scratch/temporary files
11+
scratch/
12+
13+
# Local integration test settings (contains tokens/secrets)
14+
tests/local.settings.ps1
15+
16+
# Secret vault password
17+
local.secrets.json
18+
19+
# Claude Code local settings
20+
.claude/
21+
22+
# IDE specific
23+
.idea/
24+
*.suo
25+
*.user
26+
27+
# OS specific
28+
.DS_Store
29+
Thumbs.db

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"ms-vscode.powershell",
4+
"editorconfig.editorconfig",
5+
"eamodio.gitlens"
6+
]
7+
}

.vscode/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"files.trimTrailingWhitespace": true,
3+
"files.insertFinalNewline": true,
4+
"editor.insertSpaces": true,
5+
"editor.tabSize": 4,
6+
"powershell.codeFormatting.autoCorrectAliases": true,
7+
"powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true,
8+
"powershell.codeFormatting.trimWhitespaceAroundPipe": true,
9+
"powershell.codeFormatting.useConstantStrings": true,
10+
"powershell.codeFormatting.useCorrectCasing": true,
11+
"powershell.codeFormatting.whitespaceBetweenParameters": true
12+
}

0 commit comments

Comments
 (0)