Skip to content
11 changes: 6 additions & 5 deletions .build/scripts/New-PSModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ function New-PSModule {
$moduleFileExists = Test-Path -Path $moduleFilePath
$action = if ($moduleFileExists) {
'Overwrite file'
} else {
}
else {
'Create file'
}

if ($moduleFileExists) {
if ($PSCmdlet.ShouldProcess($moduleFilePath, $action)) {
if ($ConfirmPreference -eq 'None' -or $PSCmdlet.ShouldContinue('Overwrite existing file?', $moduleFilePath)) {
Expand All @@ -141,17 +142,17 @@ function New-PSModule {
else {
'Create manifest'
}

if ($manifestExists) {
if ($PSCmdlet.ShouldProcess($manifestFilePath, $action)) {
if ($ConfirmPreference -eq 'None' -or $PSCmdlet.ShouldContinue('Overwrite existing manifest?', $manifestFilePath)) {
New-ModuleManifest -Path $manifestFilePath -ModuleVersion '0.1.0' -RootModule "$moduleName.psm1" -Confirm:$false -WhatIf:$false
New-ModuleManifest -Path $manifestFilePath -ModuleVersion '0.1.0' -RootModule "$moduleName.psm1" -FunctionsToExport @() -CmdletsToExport @() -VariablesToExport '' -AliasesToExport @() -Confirm:$false -WhatIf:$false
}
}
}
else {
if ($PSCmdlet.ShouldProcess($manifestFilePath, $action)) {
New-ModuleManifest -Path $manifestFilePath -ModuleVersion '0.1.0' -Description $moduleName -RootModule "$moduleName.psm1" -Confirm:$false -WhatIf:$false
New-ModuleManifest -Path $manifestFilePath -ModuleVersion '0.1.0' -Description $moduleName -RootModule "$moduleName.psm1" -FunctionsToExport @() -CmdletsToExport @() -VariablesToExport '' -AliasesToExport @() -Confirm:$false -WhatIf:$false
}
}
}
Expand Down
244 changes: 244 additions & 0 deletions .build/scripts/Write-PSModuleDocs.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<#
.SYNOPSIS
Generates markdown documentation for all public functions in the PowerShell module.
#>
[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$VerbosePreference = 'SilentlyContinue'

function Write-PSModuleDocs {
[CmdletBinding()]
<#
.SYNOPSIS
Generates markdown documentation for all public functions in the PowerShell module.

.DESCRIPTION
Imports the built module directly from the output manifest and generates markdown
documentation for each public function. Output files are written to the docs folder
in the repository root, with one file per function named <FunctionName>.md.

.EXAMPLE
Write-PSModuleDocs

Generates markdown documentation for all public functions and writes them to the docs folder.
#>
param()

$gitRoot = Resolve-Path -Path "$PSScriptRoot\..\.."
$moduleName = Split-Path -Path $gitRoot -Leaf
$outputModulePath = "$gitRoot\.output\$moduleName"
$docsPath = "$gitRoot\docs"

if (-not (Test-Path -Path $outputModulePath -PathType Container)) {
throw "Module directory not found at: '$outputModulePath', run 'make build' to build the module."
}

$versionFolder = Get-ChildItem -Path $outputModulePath -Directory | Sort-Object Name -Descending | Select-Object -First 1

if (-not $versionFolder) {
throw "No version folder found in '$outputModulePath', run 'make build' to build the module."
}

$manifestPath = "$($versionFolder.FullName)\$moduleName.psd1"

if (-not (Test-Path -Path $manifestPath -PathType Leaf)) {
throw "Module manifest not found at: '$manifestPath', run 'make build' to build the module."
}

try {
Import-Module $manifestPath -Force -ErrorAction Stop
}
catch {
throw "Module could not be imported from '$manifestPath': $_"
}

try {
$module = Get-Module -Name $moduleName
if (-not $module) {
throw "Module '$moduleName' could not be found after import."
}

$allAlias = Get-Alias
$docs = [Collections.Generic.List[PSCustomObject]]::new()

foreach ($command in $module.ExportedFunctions.Values) {
$help = Get-Help $command -Full

# If there's no real help documentation, skip
if (-not $help -or (
(-not $help.Description) -and
(-not $help.examples) -and
($help.Synopsis.Trim() -eq $command.Name -or -not $help.Synopsis)
)) {
continue
}

$parameters = $help.parameters.parameter
$examples = $help.examples.example
$alias = @(($allAlias | where ResolvedCommandName -eq $command.Name).Name)

$sections = [Collections.Generic.List[string]]::new()

# Header
$header = "# $($command.Name)"
if ($alias) {
$header += " ($($alias -join ', '))"
}
$sections.Add($header)

# Synopsis
if ($help.Synopsis -and $help.Synopsis.Trim() -notlike "$($command.Name)*") {
$sections.Add("## Synopsis`n`n$($help.Synopsis)")
}

# Description
if ($help.Description.Text) {
$sections.Add("## Description`n`n$($help.Description.Text)")
}

# Syntax
$syntaxLines = foreach ($paramSet in $command.ParameterSets) {
$paramStrings = foreach ($param in $paramSet.Parameters | where Name -notin [Management.Automation.Cmdlet]::CommonParameters) {
$typeName = if ($param.ParameterType -ne [switch]) {
" <$($param.ParameterType.Name)>"
}
else {
''
}
$token = if ($param.Position -ge 0) {
"[-$($param.Name)$typeName]" | foreach {
if ($param.IsMandatory) {
"[-$($param.Name)$typeName]"
}
else {
"[[-$($param.Name)$typeName]]"
}
}
}
else {
if ($param.IsMandatory) {
"-$($param.Name)$typeName"
}
else {
"[-$($param.Name)$typeName]"
}
}
$token
}
"$($command.Name) $($paramStrings -join ' ')"
}
$syntaxText = ($syntaxLines -join "`n").Trim()
if ($syntaxText) {
$sections.Add("## Syntax`n`n``````powershell`n$syntaxText`n``````")
}

# Parameters
if ($parameters) {
$paramLines = [Collections.Generic.List[string]]::new()
$paramLines.Add('## Parameters')

foreach ($param in $parameters) {
$paramSection = "### -$($param.Name)"

if ($param.Description.Text) {
$paramSection += "`n`n$($param.Description.Text)"
}

$bullets = [Collections.Generic.List[string]]::new()

if ($param.Type.Name) {
$bullets.Add("- **Type**: $($param.Type.Name)")
}
if ($param.Required) {
$bullets.Add("- **Required**: $($param.Required)")
}
if ($param.Position) {
$bullets.Add("- **Position**: $($param.Position)")
}

$bullets.Add("- **Default value**: $(if ($param.defaultValue) {
$param.defaultValue
}
else {
'None'
})")

if ($param.pipelineInput) {
$bullets.Add("- **Accepts pipeline input**: $($param.pipelineInput)")
}

$paramSection += "`n`n$($bullets -join "`n")"
$paramLines.Add($paramSection)
}

$sections.Add($paramLines -join "`n`n")
}

# Examples
if ($examples) {
$exampleLines = [Collections.Generic.List[string]]::new()
$exampleLines.Add('## Examples')

$i = 1
foreach ($example in $examples) {
$exampleSection = "### Example $i"

$remarksText = ($example.remarks.Text -join '').Trim()
if ($remarksText) {
$exampleSection += "`n`n$remarksText"
}

if ($example.code) {
$exampleSection += "`n`n``````powershell`n$($example.code)`n``````"
}

$exampleLines.Add($exampleSection)
$i++
}

$sections.Add($exampleLines -join "`n`n")
}

$docs.Add([PSCustomObject]@{
FileName = "$($command.Name).md"
Content = $sections -join "`n`n"
})
}

# Clean up docs folder
if (Test-Path -Path $docsPath -PathType Container) {
Get-ChildItem -Path $docsPath -File | Remove-Item -Force
}
else {
$null = New-Item -Path $docsPath -ItemType Directory -Force
}

if ($docs.Count -eq 0) {
$null = New-Item -Path "$docsPath\.gitkeep" -ItemType File -Force
Write-Verbose 'No documentation generated; created .gitkeep in docs folder.'
return
}

foreach ($doc in $docs) {
$filePath = "$docsPath\$($doc.FileName)"
Set-Content -Path $filePath -Value $doc.Content -Encoding utf8 -Force
Write-Verbose "Written: $filePath"
}

Write-Host "Documentation written to '$docsPath' ($($docs.Count) file(s))." -ForegroundColor Green
}
finally {
Get-Module -Name $moduleName -ErrorAction SilentlyContinue | Remove-Module -ErrorAction SilentlyContinue
}
}

try {
Write-PSModuleDocs @PSBoundParameters
}
catch {
Write-Host $_ -ForegroundColor Red
exit 1
}
24 changes: 0 additions & 24 deletions .build/template/Template.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,9 @@ if (Test-Path -Path $privatePath) {
$publicPath = "$PSScriptRoot\public"

if (Test-Path -Path $publicPath) {
# Snapshots
$currentAlias = Get-Alias | select Name, ReferencedCommand
$currentCommands = (Get-Command).Name

Get-ChildItem -Path $publicPath -Filter '*.ps1' | where PSIsContainer -eq $false | foreach {
. $_.FullName
}

# Find new aliases
$aliasToExport = Compare-Object $currentAlias (Get-Alias | select Name, ReferencedCommand) -Property Name, ReferencedCommand |
where SideIndicator -eq '=>' |
select -ExpandProperty InputObject

# Find new commands
$commandsToExport = Compare-Object $currentCommands (Get-Command).Name |
where SideIndicator -eq '=>' |
where InputObject -notin $aliasToExport.ReferencedCommand |
select -ExpandProperty InputObject

# Combine all functions and export
$allFunctions = @($commandsToExport) + @($aliasToExport.ReferencedCommand) |
where { $_ } |
select -Unique

if ($allFunctions -or $aliasToExport) {
Export-ModuleMember -Function $allFunctions -Alias $aliasToExport.Name
}
}

#endregion Public
1 change: 1 addition & 0 deletions .github/workflows/pr_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- '!LICENSE'
- '!Makefile'
- '!.github/workflows/release.yml'
- '!docs/**'
workflow_dispatch:

permissions:
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/update_docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Update Documentation

on:
pull_request:
paths:
- '**.psd1'
- '!**/classes.psd1'
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
update-docs:
runs-on: windows-latest

env:
GITHUB_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY: ${{ github.event.repository.name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }}

- name: Setup Profile
shell: pwsh
run: .\.build\scripts\Setup-RunnerProfile.ps1 -Verbose

- name: Build Module
run: .\.build\scripts\Build-PSModule.ps1

- name: Generate Documentation
shell: pwsh
run: .\.build\scripts\Write-PSModuleDocs.ps1

- name: Configure Git
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "DocsBot"

- name: Commit Documentation Changes
run: |
git add docs/
if git diff --staged --quiet; then
echo "No documentation changes to commit"
else
git commit -m "AUTO: Exported Command Documentation"
git push origin ${{ github.head_ref }}
fi
Loading