From 8f35ce956235100c2bde6e67303f27e49da83054 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Aug 2026 11:49:51 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Document=20grouped?= =?UTF-8?q?=20public=20command=20help=20URLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/content/guides/structuring-your-module.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/content/guides/structuring-your-module.md b/docs/content/guides/structuring-your-module.md index b70100b2..ae339d7c 100644 --- a/docs/content/guides/structuring-your-module.md +++ b/docs/content/guides/structuring-your-module.md @@ -67,6 +67,14 @@ 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. +- 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 + +`Document-PSModule` generates command help and then moves 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///`. + +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. `Document-PSModule` does not create redirects for the old path; arrange a redirect separately in the publishing layer when existing links must continue to work. ## Module source code structure From aa16e99eb0d2bad44ea35b166f03eced6051af74 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 4 Sep 2026 19:05:53 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Use=20Process-PSMo?= =?UTF-8?q?dule=20in=20help=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/content/guides/structuring-your-module.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/guides/structuring-your-module.md b/docs/content/guides/structuring-your-module.md index ae339d7c..1a9a8def 100644 --- a/docs/content/guides/structuring-your-module.md +++ b/docs/content/guides/structuring-your-module.md @@ -40,7 +40,7 @@ The goal is a stable repository anatomy so both humans and automation know exact │ ├── PSModule.yml # Settings parsed to drive matrices │ └── release.yml # Release automation template invoked on publish ├── examples/ # Samples referenced in generated documentation -│ └── General.ps1 # Example script ingested by Document-PSModule +│ └── General.ps1 # Example script processed by Process-PSModule ├── icon/ # Icon assets linked from manifest and documentation │ └── icon.png # Default module icon (PNG format) ├── src/ # Module source, see "Module source code structure" below @@ -72,9 +72,9 @@ Key expectations: ### Grouping and published help URLs -`Document-PSModule` generates command help and then moves 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://psmodule.io//Functions//` to `https://psmodule.io//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. `Document-PSModule` does not create redirects for the old path; arrange a redirect separately in the publishing layer when existing links must continue to work. +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. ## Module source code structure @@ -125,7 +125,7 @@ How the module is built. │ ├── finally.ps1 # Cleanup script appended to the root module │ ├── header.ps1 # Optional header injected at the top of the module │ ├── manifest.psd1 (optional) # Source manifest reused when present -│ └── README.md # Module-level docs ingested by Document-PSModule +│ └── README.md # Module-level docs processed by Process-PSModule ``` ### Declaring module dependencies From 97fb31559a704f7593089d2146412f2f8beb1e83 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 4 Sep 2026 19:22:17 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Make?= =?UTF-8?q?=20public=20help-link=20validation=20reusable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../SourceCode/PSModule/PSModule.Tests.ps1 | 19 ++++++++++++++----- .../public/PSModule/Get-PSModuleTest.ps1 | 2 +- .../public/PSModule/Get-PSModuleTest.ps1 | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) 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..93455446 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,8 +59,7 @@ BeforeDiscovery { @{ DocumentationPath = $documentationPath - ExpectedLink = "https://psmodule.io/$ModuleName/Functions/$documentationPath/" - FilePath = $_.FullName + FilePath = $_.FullName } } } else { @@ -361,14 +360,24 @@ Describe 'PSModule - SourceCode tests' { } } It 'Should put the canonical documentation link first for (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases { - param($DocumentationPath, $ExpectedLink, $FilePath) + 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/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 ( From 28ae16fd29f66e296063d2d4dba388a3657515a3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 4 Sep 2026 19:23:18 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Clari?= =?UTF-8?q?fy=20required=20public=20help=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/tests/SourceCode/PSModule/PSModule.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 93455446..d0aea663 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 @@ -359,7 +359,7 @@ Describe 'PSModule - SourceCode tests' { $tokens.count -ne 0 } } - It 'Should put the canonical documentation link first for (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases { + It 'Should require a canonical documentation link for (ID: PublicHelpLink)' -ForEach $publicHelpLinkTestCases { param($DocumentationPath, $FilePath) $content = Get-Content -Path $FilePath -Raw From 823267dd3eea10d87e3ca53e15408ead0a4e6461 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 4 Sep 2026 19:25:50 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Document=20the=20p?= =?UTF-8?q?ublic=20help-link=20requirement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/content/guides/skipping-framework-tests.md | 8 ++++---- docs/content/guides/structuring-your-module.md | 4 ++-- docs/content/guides/validating-before-review.md | 1 + docs/content/reference/framework-test-ids.md | 1 + docs/content/reference/powershell-module-standard.md | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) 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: From 0bad0e47f23836f3ff1b328d4d01d471daae7ce6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 4 Sep 2026 19:52:20 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A7=B9=20[Maintenance]:=20Align=20sou?= =?UTF-8?q?rce=20test=20assignments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/tests/SourceCode/PSModule/PSModule.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d0aea663..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,7 @@ BeforeDiscovery { @{ DocumentationPath = $documentationPath - FilePath = $_.FullName + FilePath = $_.FullName } } } else {