diff --git a/.github/actions/Test-PSModule/src/tests/SourceCode/PSModule/PSModule.Tests.ps1 b/.github/actions/Test-PSModule/src/tests/SourceCode/PSModule/PSModule.Tests.ps1 index f7df56ea..aceef32c 100644 --- a/.github/actions/Test-PSModule/src/tests/SourceCode/PSModule/PSModule.Tests.ps1 +++ b/.github/actions/Test-PSModule/src/tests/SourceCode/PSModule/PSModule.Tests.ps1 @@ -59,7 +59,6 @@ BeforeDiscovery { @{ DocumentationPath = $documentationPath - ExpectedLink = "https://psmodule.io/$ModuleName/Functions/$documentationPath/" FilePath = $_.FullName } } @@ -360,15 +359,25 @@ Describe 'PSModule - SourceCode tests' { $tokens.count -ne 0 } } - It 'Should put the canonical documentation link first for (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases { - param($DocumentationPath, $ExpectedLink, $FilePath) + It 'Should require a canonical documentation link for (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases { + param($DocumentationPath, $FilePath) $content = Get-Content -Path $FilePath -Raw $links = [regex]::Matches($content, '(?ms)^\s*\.LINK\s*\r?\n\s*(?\S+)') $links.Count | Should -BeGreaterThan 0 -Because "$DocumentationPath should have a documentation link" - $links[0].Groups['Uri'].Value | - Should -BeExactly $ExpectedLink -Because "$DocumentationPath should put its canonical documentation link first" + $link = $links[0].Groups['Uri'].Value + $parsedLink = $null + [Uri]::TryCreate($link, [UriKind]::Absolute, [ref]$parsedLink) | + Should -BeTrue -Because "$DocumentationPath should use an absolute documentation link" + $parsedLink.Scheme | + Should -BeExactly 'https' -Because "$DocumentationPath should use HTTPS for its documentation link" + $parsedLink.Host | + Should -Not -BeNullOrEmpty -Because "$DocumentationPath should specify a documentation host" + $parsedLink.AbsolutePath | + Should -BeExactly "/$ModuleName/Functions/$DocumentationPath/" -Because "$DocumentationPath should use the canonical documentation path" + $parsedLink.Query | Should -BeNullOrEmpty -Because "$DocumentationPath should not add a query to its documentation link" + $parsedLink.Fragment | Should -BeNullOrEmpty -Because "$DocumentationPath should not add a fragment to its documentation link" } It 'All public functions/filters have tests (ID: FunctionTest)' { $issues = @('') diff --git a/docs/content/guides/skipping-framework-tests.md b/docs/content/guides/skipping-framework-tests.md index 61ecbb9b..13a6cc03 100644 --- a/docs/content/guides/skipping-framework-tests.md +++ b/docs/content/guides/skipping-framework-tests.md @@ -62,7 +62,7 @@ function Get-ComplexData { This file intentionally skips only the FunctionCount framework test. .LINK - https://psmodule.io//Functions/Get-ComplexData + https:////Functions/Get-ComplexData #> [OutputType([PSCustomObject])] [CmdletBinding()] @@ -104,7 +104,7 @@ function Get-RawData { This function is a private helper for Get-ComplexData. .LINK - https://psmodule.io//Functions/Get-ComplexData + https:////Functions/Get-ComplexData #> [OutputType([string])] [CmdletBinding()] @@ -145,7 +145,7 @@ function Format-ComplexData { This function is a private helper for Get-ComplexData. .LINK - https://psmodule.io//Functions/Get-ComplexData + https:////Functions/Get-ComplexData #> [OutputType([PSCustomObject])] [CmdletBinding()] @@ -163,7 +163,7 @@ function Format-ComplexData { } ``` -Replace `` with the module's published name. If the public function belongs to a group, insert `/` between `Functions/` and `Get-ComplexData`. +Replace `` with the HTTPS host that publishes the module documentation and `` with the module's published name. If the public function belongs to a group, insert `/` between `Functions/` and `Get-ComplexData`. The skip exempts only `FunctionCount`. Every function in the file must still follow the [PowerShell function standard](https://msx.no/docs/Coding-Standards/PowerShell/Functions/), including complete comment-based help, matching `[OutputType()]` and `.OUTPUTS` metadata, typed parameters, and implicit output. diff --git a/docs/content/guides/structuring-your-module.md b/docs/content/guides/structuring-your-module.md index 1a9a8def..c4f36cf3 100644 --- a/docs/content/guides/structuring-your-module.md +++ b/docs/content/guides/structuring-your-module.md @@ -67,12 +67,12 @@ Key expectations: - A group's overview page (`/.md` named after the folder, or `/index.md`) becomes that group's section landing page in the docs navigation. - The build step compiles `src/` into a root module file and removes the original project layout from the artifact. - Documentation generation mirrors the `src/functions/public` hierarchy so help content always aligns with source. -- Put the canonical public help URL first in each public command's comment-based help. For a command at `src/functions/public//.ps1`, use `https://psmodule.io//Functions///`. `Test-PSModule` enforces this as `PublicHelpLink`; additional `.LINK` entries may follow. +- Put the canonical public help URL first in each public command's comment-based help. For a command at `src/functions/public//.ps1`, use `https:////Functions///` with the HTTPS host that publishes the module documentation. `Test-PSModule` enforces this as `PublicHelpLink`; additional `.LINK` entries may follow. - Point each private helper's `.LINK` entry to the public command it supports, using that command's canonical grouped URL. ### Grouping and published help URLs -Process-PSModule generates command help and publishes each page to mirror the relative path under `src/functions/public/`. Moving an existing command into a group therefore changes its published URL from `https://psmodule.io//Functions//` to `https://psmodule.io//Functions///`. +Process-PSModule generates command help and publishes each page to mirror the relative path under `src/functions/public/`. Moving an existing command into a group therefore changes its published URL from `https:////Functions//` to `https:////Functions///`. When regrouping a command, update its first public `.LINK`, every private-helper `.LINK` that points to it, and any other references to the old URL in the same change. Process-PSModule does not create redirects for the old path; arrange a redirect separately in the publishing layer when existing links must continue to work. diff --git a/docs/content/guides/validating-before-review.md b/docs/content/guides/validating-before-review.md index ac6454c6..def8c02f 100644 --- a/docs/content/guides/validating-before-review.md +++ b/docs/content/guides/validating-before-review.md @@ -55,6 +55,7 @@ Do not repeat the shared workflow here. Follow the shared branch → draft PR - comment-based help is present for every changed function, including private helpers - help sections, examples, `.INPUTS`, `.OUTPUTS`, and parameter documentation match the function contract from [MSX PowerShell Functions](https://msx.no/docs/Coding-Standards/PowerShell/Functions/) + - every public function has a first `.LINK` entry using an absolute HTTPS URL whose path matches the generated command documentation path - public-function links and usage examples are current enough that generated documentation will stay accurate Do not treat help as optional cleanup. In PSModule repositories, the function help is part of the delivered behavior. diff --git a/docs/content/reference/framework-test-ids.md b/docs/content/reference/framework-test-ids.md index 4a3cc3d9..f87f29da 100644 --- a/docs/content/reference/framework-test-ids.md +++ b/docs/content/reference/framework-test-ids.md @@ -26,6 +26,7 @@ Run by the [Test source code](pipeline-stages.md#test-source-code) job against f | `CmdletBinding` | Functions (Generic) | Functions should include the `[CmdletBinding()]` attribute. | `#SkipTest:CmdletBinding:Simple helper function` | | `ParamBlock` | Functions (Generic) | Functions should have a parameter block (`param()`). | `#SkipTest:ParamBlock:No parameters needed` | | `FunctionTest` | Functions (Public) | All public functions and filters should have corresponding tests. | `#SkipTest:FunctionTest:Test in development` | +| `PublicHelpLink` | Functions (Public) | Every public function and filter should have a first `.LINK` entry with an absolute HTTPS URL whose path matches its generated command documentation path. | `#SkipTest:PublicHelpLink:Legacy documentation link` | ## Module tests diff --git a/docs/content/reference/powershell-module-standard.md b/docs/content/reference/powershell-module-standard.md index 31d6ee32..e5eb0821 100644 --- a/docs/content/reference/powershell-module-standard.md +++ b/docs/content/reference/powershell-module-standard.md @@ -358,6 +358,7 @@ The CI pipeline automatically tests every source file against the following rule | `CmdletBinding` | Every function must have `[CmdletBinding()]` | | `ParamBlock` | Every function must have a `param()` block | | `FunctionTest` | Every public function must be referenced by the tests; its behavior must be covered whether the suite is per-command or grouped | +| `PublicHelpLink` | Every public function must have a first `.LINK` entry with an absolute HTTPS URL whose path is `/Module/Functions//` | To skip a specific rule for one file only, add a comment at the very top of that file: diff --git a/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 index d3c7ca04..d6cc4138 100644 --- a/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 +++ b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -17,7 +17,7 @@ function Get-PSModuleTest { "Hello, World!" .LINK - https://psmodule.io/PSModuleTest2/Functions/PSModule/Get-PSModuleTest/ + https://docs.example.com/PSModuleTest2/Functions/PSModule/Get-PSModuleTest/ #> [CmdletBinding()] param ( diff --git a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 index 4353eed8..0eb9f3cb 100644 --- a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 +++ b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -17,7 +17,7 @@ function Get-PSModuleTest { "Hello, World!" .LINK - https://psmodule.io/PSModuleTest/Functions/PSModule/Get-PSModuleTest/ + https://docs.example.com/PSModuleTest/Functions/PSModule/Get-PSModuleTest/ #> [CmdletBinding()] param (