Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ BeforeDiscovery {

@{
DocumentationPath = $documentationPath
ExpectedLink = "https://psmodule.io/$ModuleName/Functions/$documentationPath/"
FilePath = $_.FullName
}
}
Expand Down Expand Up @@ -360,15 +359,25 @@ Describe 'PSModule - SourceCode tests' {
$tokens.count -ne 0
}
}
It 'Should put the canonical documentation link first for <DocumentationPath> (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases {
param($DocumentationPath, $ExpectedLink, $FilePath)
It 'Should require a canonical documentation link for <DocumentationPath> (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases {
param($DocumentationPath, $FilePath)

$content = Get-Content -Path $FilePath -Raw
$links = [regex]::Matches($content, '(?ms)^\s*\.LINK\s*\r?\n\s*(?<Uri>\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 = @('')
Expand Down
8 changes: 4 additions & 4 deletions docs/content/guides/skipping-framework-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function Get-ComplexData {
This file intentionally skips only the FunctionCount framework test.

.LINK
https://psmodule.io/<ModuleName>/Functions/Get-ComplexData
https://<DocumentationHost>/<ModuleName>/Functions/Get-ComplexData
#>
[OutputType([PSCustomObject])]
[CmdletBinding()]
Expand Down Expand Up @@ -104,7 +104,7 @@ function Get-RawData {
This function is a private helper for Get-ComplexData.

.LINK
https://psmodule.io/<ModuleName>/Functions/Get-ComplexData
https://<DocumentationHost>/<ModuleName>/Functions/Get-ComplexData
#>
[OutputType([string])]
[CmdletBinding()]
Expand Down Expand Up @@ -145,7 +145,7 @@ function Format-ComplexData {
This function is a private helper for Get-ComplexData.

.LINK
https://psmodule.io/<ModuleName>/Functions/Get-ComplexData
https://<DocumentationHost>/<ModuleName>/Functions/Get-ComplexData
#>
[OutputType([PSCustomObject])]
[CmdletBinding()]
Expand All @@ -163,7 +163,7 @@ function Format-ComplexData {
}
```

Replace `<ModuleName>` with the module's published name. If the public function belongs to a group, insert `<Group>/` between `Functions/` and `Get-ComplexData`.
Replace `<DocumentationHost>` with the HTTPS host that publishes the module documentation and `<ModuleName>` with the module's published name. If the public function belongs to a group, insert `<Group>/` 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.

Expand Down
4 changes: 2 additions & 2 deletions docs/content/guides/structuring-your-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ Key expectations:
- A group's overview page (`<Category>/<Category>.md` named after the folder, or `<Category>/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/<Group>/<Name>.ps1`, use `https://psmodule.io/<ModuleName>/Functions/<Group>/<Name>/`. `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/<Group>/<Name>.ps1`, use `https://<DocumentationHost>/<ModuleName>/Functions/<Group>/<Name>/` 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/<ModuleName>/Functions/<Name>/` to `https://psmodule.io/<ModuleName>/Functions/<Group>/<Name>/`.
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://<DocumentationHost>/<ModuleName>/Functions/<Name>/` to `https://<DocumentationHost>/<ModuleName>/Functions/<Group>/<Name>/`.

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.

Expand Down
1 change: 1 addition & 0 deletions docs/content/guides/validating-before-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/framework-test-ids.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/powershell-module-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<relative-command>/` |

To skip a specific rule for one file only, add a comment at the very top of that file:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading