Skip to content

Commit b11b310

Browse files
⚙️ [Maintenance]: Public help links follow canonical documentation paths (#420)
Public module source validation requires each **public function-bearing script** under `src/functions/public` to put its canonical generated-documentation URL first in comment-based help. ## Changed: Canonical public help links are enforced for function-bearing files The source-code standards suite derives documentation paths from recursive public function paths and validates the first `.LINK` against `https://psmodule.io/<ModuleName>/Functions/<relative path>/<FunctionName>/`. ## Technical Details - `Test-SourceCode` forwards the configured module name into the shared test action instead of falling back to the repository name. - Public help-link validation applies only to public scripts that define at least one function/filter. - Covers ungrouped and nested public function scripts. - Requires at least one `.LINK`, exact URL casing/content, first-link ordering, and a trailing slash. - Fixture repos cover both grouped and ungrouped function paths while non-function public scripts are not forced to provide canonical links. ## Downstream dependency status (live) - `PSModule/Domeneshop` PR **#17** has already been merged into `main` at `2ca6f788c4b68a72c63d6472ae19720bc90cc8b9` while still pinned to `Process-PSModule` **v6.1.13** (`fb1bdb8fefd243292f779d2a856a38db6fe6daf4`). - That downstream state currently lacks the local `PublicHelpLinks` / `ModuleRequirements` / `TestLayout` checks intended by this framework update. - Merging this PR unblocks downstream alignment by making the canonical public help-link validation available in the framework version line. ## Validation - Focused PublicHelpLink runs pass for both dynamic module names (4 cases each after scoping). - Full source-code standards suites pass for both fixtures (13 tests each). - PSScriptAnalyzer reports no warnings or errors in changed files. - Draft PR CI passes, including linter, CodeQL, action analysis, and both workflow-test matrices. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 9bca3f5 commit b11b310

12 files changed

Lines changed: 78 additions & 2 deletions

File tree

.github/actions/Test-PSModule/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ runs:
283283
id: test
284284
env:
285285
LocalTestPath: ${{ steps.paths.outputs.LocalTestPath }}
286+
PSMODULE_TEST_PSMODULE_MODULE_NAME: ${{ steps.paths.outputs.ModuleName }}
286287
WorkingDirectory: ${{ inputs.WorkingDirectory }}
287288
with:
288289
Debug: ${{ inputs.Debug }}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
@{
22
Path = Get-ChildItem -Path $PSScriptRoot -Filter *.Tests.ps1 | Select-Object -ExpandProperty FullName
33
Data = @{
4-
Path = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_Path
5-
TestsPath = $env:LocalTestPath
4+
ModuleName = $env:PSMODULE_TEST_PSMODULE_MODULE_NAME
5+
Path = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_Path
6+
TestsPath = $env:LocalTestPath
67
}
78
}

.github/actions/Test-PSModule/src/tests/SourceCode/PSModule/PSModule.Tests.ps1

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
'PSUseDeclaredVarsMoreThanAssignments', 'functionBearingFiles',
1111
Justification = 'Variables are used in the test.'
1212
)]
13+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
14+
'PSUseDeclaredVarsMoreThanAssignments', 'publicHelpLinkTestCases',
15+
Justification = 'Variable is used during Pester test discovery.'
16+
)]
1317
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
1418
'PSAvoidUsingWriteHost', '',
1519
Justification = 'Logging to Github Actions.'
@@ -20,6 +24,10 @@
2024
)]
2125
[CmdLetBinding()]
2226
param(
27+
# The name of the module.
28+
[Parameter(Mandatory)]
29+
[string] $ModuleName,
30+
2331
# The path to the 'src' folder of the repo.
2432
[Parameter(Mandatory)]
2533
[string] $Path,
@@ -29,6 +37,37 @@ param(
2937
[string] $TestsPath
3038
)
3139

40+
BeforeDiscovery {
41+
$publicFunctionsPath = Join-Path -Path $Path -ChildPath 'functions/public'
42+
$publicHelpLinkTestCases = if (Test-Path -Path $publicFunctionsPath) {
43+
Get-ChildItem -Path $publicFunctionsPath -Filter '*.ps1' -Recurse -File |
44+
Sort-Object -Property FullName |
45+
ForEach-Object {
46+
$ast = [System.Management.Automation.Language.Parser]::ParseFile($_.FullName, [ref]$null, [ref]$null)
47+
$functionTokens = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
48+
if ($functionTokens.Count -eq 0) {
49+
return
50+
}
51+
52+
$relativePath = [IO.Path]::GetRelativePath($publicFunctionsPath, $_.FullName)
53+
$relativeDirectory = Split-Path -Path $relativePath -Parent
54+
$documentationPath = if ($relativeDirectory) {
55+
'{0}/{1}' -f ($relativeDirectory -replace '[\\/]', '/'), $_.BaseName
56+
} else {
57+
$_.BaseName
58+
}
59+
60+
@{
61+
DocumentationPath = $documentationPath
62+
ExpectedLink = "https://psmodule.io/$ModuleName/Functions/$documentationPath/"
63+
FilePath = $_.FullName
64+
}
65+
}
66+
} else {
67+
@()
68+
}
69+
}
70+
3271
BeforeAll {
3372
$scriptFiles = Get-ChildItem -Path $Path -Include *.psm1, *.ps1 -Recurse -File
3473
Write-Host "::group:: - Script files [$($scriptFiles.Count)]"
@@ -321,6 +360,16 @@ Describe 'PSModule - SourceCode tests' {
321360
$tokens.count -ne 0
322361
}
323362
}
363+
It 'Should put the canonical documentation link first for <DocumentationPath> (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases {
364+
param($DocumentationPath, $ExpectedLink, $FilePath)
365+
366+
$content = Get-Content -Path $FilePath -Raw
367+
$links = [regex]::Matches($content, '(?ms)^\s*\.LINK\s*\r?\n\s*(?<Uri>\S+)')
368+
369+
$links.Count | Should -BeGreaterThan 0 -Because "$DocumentationPath should have a documentation link"
370+
$links[0].Groups['Uri'].Value |
371+
Should -BeExactly $ExpectedLink -Because "$DocumentationPath should put its canonical documentation link first"
372+
}
324373
It 'All public functions/filters have tests (ID: FunctionTest)' {
325374
$issues = @('')
326375

.github/workflows/Test-SourceCode.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- name: Test-SourceCode
3838
uses: ./_wf/.github/actions/Test-PSModule
3939
with:
40+
Name: ${{ fromJson(inputs.Settings).Name }}
4041
Debug: ${{ fromJson(inputs.Settings).Debug }}
4142
Prerelease: ${{ fromJson(inputs.Settings).Prerelease }}
4243
Verbose: ${{ fromJson(inputs.Settings).Verbose }}

tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ function Get-PSModuleTest {
1515
Test-PSModule -Name 'World'
1616
1717
"Hello, World!"
18+
19+
.LINK
20+
https://psmodule.io/PSModuleTest2/Functions/PSModule/Get-PSModuleTest/
1821
#>
1922
[CmdletBinding()]
2023
param (

tests/srcTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ function New-PSModuleTest {
1313
1414
"Hello, World!"
1515
16+
.LINK
17+
https://psmodule.io/PSModuleTest2/Functions/PSModule/New-PSModuleTest/
18+
1619
.NOTES
1720
Testing if a module can have a [Markdown based link](https://example.com).
1821
!"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.,"

tests/srcTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
Test-PSModule -Name 'World'
1111
1212
"Hello, World!"
13+
14+
.LINK
15+
https://psmodule.io/PSModuleTest2/Functions/SomethingElse/Set-PSModuleTest/
1316
#>
1417
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
1518
'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',

tests/srcTestRepo/src/functions/public/Test-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
Test-PSModule -Name 'World'
1111
1212
"Hello, World!"
13+
14+
.LINK
15+
https://psmodule.io/PSModuleTest2/Functions/Test-PSModuleTest/
1316
#>
1417
[CmdletBinding()]
1518
param (

tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ function Get-PSModuleTest {
1515
Test-PSModule -Name 'World'
1616
1717
"Hello, World!"
18+
19+
.LINK
20+
https://psmodule.io/PSModuleTest/Functions/PSModule/Get-PSModuleTest/
1821
#>
1922
[CmdletBinding()]
2023
param (

tests/srcWithManifestTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ function New-PSModuleTest {
1313
1414
"Hello, World!"
1515
16+
.LINK
17+
https://psmodule.io/PSModuleTest/Functions/PSModule/New-PSModuleTest/
18+
1619
.NOTES
1720
Testing if a module can have a [Markdown based link](https://example.com).
1821
!"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.,"

0 commit comments

Comments
 (0)