Skip to content

Commit 98cf6fa

Browse files
docs: Update PowerShell module standards for clarity and completeness, including CI/CD pipeline details and linting rules
1 parent bc95a91 commit 98cf6fa

1 file changed

Lines changed: 185 additions & 4 deletions

File tree

src/docs/PowerShell/Modules/Standards.md

Lines changed: 185 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# PowerShell module standard
22

3-
Standards for implementing and reviewing PowerShell modules in the PSModule organization. These rules apply to modules built with the [PSModule framework](https://github.com/PSModule/PSModule).
3+
Standards for implementing and reviewing PowerShell modules in the PSModule organization. These rules apply to modules built with the [PSModule framework](https://github.com/PSModule/Process-PSModule).
44

55
For general PowerShell coding standards (naming, style, function structure, documentation, readability, error handling), see [PowerShell Standards](../Standard/index.md). This page covers only module-specific conventions.
66

7-
> **Repo-local config wins.** Repo-level `PSScriptAnalyzerSettings.psd1`, `.github/linters/.powershell-psscriptanalyzer.psd1`, and equivalent local rules override anything below. This standard fills the gap.
7+
> **Repo-local config wins.** Repo-level `.github/linters/.powershell-psscriptanalyzer.psd1` and `.github/PSModule.yml` override anything below. This standard fills the gap.
88
99
## Repository layout
1010

@@ -13,7 +13,7 @@ The framework treats `src/` as the source for the compiled module. Place code in
1313
| Folder or file | Purpose | Do not put here |
1414
| --------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------- |
1515
| `src/header.ps1` | Single comment block at the top of the compiled module | Runtime code |
16-
| `src/manifest.psd1` | Intentional manifest overrides | Generated values (functions, types, version, GUID, etc.) |
16+
| `src/manifest.psd1` | Intentional manifest overrides (e.g., `Author`) | Generated values (functions, types, version, GUID, etc.) |
1717
| `src/data/*.psd1` | Static read-only configuration | Mutable state, secrets, computed values |
1818
| `src/init/*.ps1` | Code that runs once at import (module-scope init, completer registration) | Per-call logic, network calls, slow work |
1919
| `src/classes/private/*.ps1` | Internal classes | Public pipeline output types |
@@ -34,6 +34,8 @@ The framework treats `src/` as the source for the compiled module. Place code in
3434
| `tests/` | Pester tests and test data | Generated test results or build output |
3535
| `tests/BeforeAll.ps1` | Shared setup script executed once before the test matrix | Per-test setup |
3636
| `tests/AfterAll.ps1` | Shared teardown script executed once after the test matrix | Per-test teardown |
37+
| `.github/PSModule.yml` | Workflow configuration (build, test, publish, linter settings) | Source code or runtime config |
38+
| `tools/*.ps1` | Build-time helper scripts (numbered for execution order) | Runtime module code |
3739

3840
Layout rules:
3941

@@ -110,12 +112,180 @@ The release process treats each merged PR as a release on a single linear ancest
110112

111113
For large work, open a release branch and target it from feature branches. Apply the `Prerelease` label on the release branch PR to publish preview versions before the final merge to `main`.
112114

115+
## CI/CD pipeline
116+
117+
The [Process-PSModule](https://github.com/PSModule/Process-PSModule) workflow orchestrates the full lifecycle. Every PR triggers a **Plan** job that resolves configuration and version, then conditionally runs build, test, lint, and publish stages.
118+
119+
### Pipeline stages
120+
121+
```mermaid
122+
graph LR
123+
Plan --> Lint-Repository
124+
Plan --> Build-Module
125+
Plan --> Test-SourceCode
126+
Plan --> Lint-SourceCode
127+
Build-Module --> Test-Module
128+
Build-Module --> BeforeAll-ModuleLocal
129+
BeforeAll-ModuleLocal --> Test-ModuleLocal
130+
Test-ModuleLocal --> AfterAll-ModuleLocal
131+
Test-SourceCode --> Get-TestResults
132+
Test-Module --> Get-TestResults
133+
Test-ModuleLocal --> Get-TestResults
134+
Test-Module --> Get-CodeCoverage
135+
Test-ModuleLocal --> Get-CodeCoverage
136+
Get-TestResults --> Publish-Module
137+
Get-CodeCoverage --> Publish-Module
138+
Build-Module --> Build-Docs
139+
Build-Docs --> Build-Site
140+
Build-Site --> Publish-Site
141+
```
142+
143+
| Stage | Runs on | Purpose |
144+
| ----- | ------- | ------- |
145+
| **Plan** | All events | Loads `.github/PSModule.yml`, resolves version from PR labels, produces the Settings JSON |
146+
| **Lint-Repository** | Open/Updated PR | Runs super-linter on the full repo (markdown, YAML, etc.) |
147+
| **Lint-SourceCode** | Open/Updated PR, Merged PR, Manual | Runs PSScriptAnalyzer against `src/` |
148+
| **Build-Module** | Open/Updated PR, Merged PR, Manual | Compiles source into a versioned module artifact |
149+
| **Test-SourceCode** | Open/Updated PR, Merged PR, Manual | Framework tests on raw source files |
150+
| **Test-Module** | Open/Updated PR, Merged PR, Manual | Pester tests against the built module artifact |
151+
| **BeforeAll-ModuleLocal** | Open/Updated PR, Merged PR, Manual | Runs `tests/BeforeAll.ps1` once before the local test matrix |
152+
| **Test-ModuleLocal** | Open/Updated PR, Merged PR, Manual | Pester tests with the module installed locally (cross-OS matrix) |
153+
| **AfterAll-ModuleLocal** | Always (if tests started) | Runs `tests/AfterAll.ps1` for cleanup |
154+
| **Get-TestResults** | Always (if Plan succeeded) | Aggregates and reports test results |
155+
| **Get-CodeCoverage** | Always (if Plan succeeded) | Calculates and reports code coverage |
156+
| **Publish-Module** | Merged PR (or Prerelease label) | Publishes to PowerShell Gallery and creates a GitHub Release |
157+
| **Build-Docs / Build-Site** | Open/Updated PR, Merged PR, Manual | Generates documentation site from source |
158+
| **Publish-Site** | Merged PR | Deploys documentation site to GitHub Pages |
159+
160+
### Important file patterns
161+
162+
The workflow only triggers build, test, and publish stages when changed files match the `ImportantFilePatterns` setting. The default patterns are:
163+
164+
```text
165+
^src/
166+
^README\.md$
167+
```
168+
169+
Changes that do not match any pattern result in `ReleaseType: None` — the pipeline skips build, test, and publish entirely. Override in `.github/PSModule.yml`:
170+
171+
```yaml
172+
ImportantFilePatterns:
173+
- '^src/'
174+
- '^README\.md$'
175+
- '^\.github/workflows/'
176+
```
177+
178+
### Version resolution
179+
180+
The **Plan** job resolves the next version before any build occurs. This means the tested artifact carries the exact version that will be published — no re-stamping happens at publish time.
181+
182+
**Flow:**
183+
184+
1. `Get-PSModuleSettings` loads `.github/PSModule.yml` and determines `ReleaseType` from PR labels
185+
2. `Resolve-PSModuleVersion` calculates the next semantic version from the latest Git tag
186+
3. `Build-PSModule` stamps the resolved version into the compiled manifest
187+
4. `Publish-PSModule` reads the version from the manifest (read-only) and publishes
188+
189+
**PR label to version bump mapping:**
190+
191+
| Labels (configurable) | Bump type | Default label values |
192+
| --------------------- | --------- | -------------------- |
193+
| Major | Major (`X.0.0`) | `major`, `breaking` |
194+
| Minor | Minor (`x.Y.0`) | `minor`, `feature` |
195+
| Patch | Patch (`x.y.Z`) | `patch`, `fix` |
196+
| Ignore | No release | `NoRelease` |
197+
| None of the above | Patch (when `AutoPatching: true`) | — |
198+
199+
**Prerelease versions:** Adding a `Prerelease` label to the PR produces a prerelease tag (e.g., `1.2.3-preview0001`). The format is controlled by `IncrementalPrerelease` (sequential numbering) or `DatePrereleaseFormat` (.NET DateTime format string).
200+
201+
**Tag format:** Releases are tagged with a configurable prefix (default `v`) — e.g., `v1.2.3`.
202+
203+
### Configuration (`.github/PSModule.yml`)
204+
205+
All settings have sensible defaults. An empty or missing file uses the defaults below:
206+
207+
```yaml
208+
Name: null # Defaults to the repository name
209+
210+
ImportantFilePatterns:
211+
- '^src/'
212+
- '^README\.md$'
213+
214+
Build:
215+
Skip: false
216+
Module:
217+
Skip: false
218+
Docs:
219+
Skip: false
220+
Site:
221+
Skip: false
222+
223+
Test:
224+
Skip: false
225+
Linux:
226+
Skip: false
227+
MacOS:
228+
Skip: false
229+
Windows:
230+
Skip: false
231+
SourceCode:
232+
Skip: false
233+
PSModule:
234+
Skip: false
235+
Module:
236+
Skip: false
237+
TestResults:
238+
Skip: false
239+
CodeCoverage:
240+
Skip: false
241+
PercentTarget: 0
242+
243+
Publish:
244+
Module:
245+
Skip: false
246+
AutoCleanup: true # Delete prerelease tags after stable release
247+
AutoPatching: true # Unlabeled PRs default to patch bump
248+
IncrementalPrerelease: true # Sequential prerelease numbering
249+
DatePrereleaseFormat: '' # Alternative: .NET DateTime format for prerelease
250+
VersionPrefix: 'v' # Git tag prefix
251+
MajorLabels: 'major, breaking'
252+
MinorLabels: 'minor, feature'
253+
PatchLabels: 'patch, fix'
254+
IgnoreLabels: 'NoRelease'
255+
UsePRTitleAsReleaseName: false
256+
UsePRBodyAsReleaseNotes: true
257+
UsePRTitleAsNotesHeading: true
258+
259+
Linter:
260+
Skip: false
261+
env: {} # Additional env vars passed to super-linter
262+
```
263+
264+
### Publishing
265+
266+
The `Publish-Module` stage:
267+
268+
1. Downloads the pre-built module artifact (identical to what was tested)
269+
2. Reads the version from the compiled manifest (no recalculation)
270+
3. Publishes to the PowerShell Gallery
271+
4. Creates a GitHub Release with the module attached as a zip artifact
272+
5. Comments on the PR with links to the Gallery package and GitHub Release
273+
6. Cleans up old prerelease tags when publishing a stable release (if `AutoCleanup: true`)
274+
275+
The publish step only runs when:
276+
277+
- All tests and code coverage pass (or are skipped)
278+
- The PR is merged to the default branch (stable release), or
279+
- The PR carries the `Prerelease` label (prerelease from the feature/release branch)
280+
281+
On abandoned (closed without merge) PRs, the pipeline cleans up any prerelease tags created for that branch.
282+
113283
## Tests
114284

115285
- Pester tests under `tests/`.
116286
- Filenames: `<ModuleName>.Tests.ps1` or `<Function>.Tests.ps1`.
117287
- One test file per public command for unit tests; integration tests grouped by scenario.
118-
- Tests run against the dot-sourced source files, not against an installed module — see [PSModule Test Specification](https://psmodule.io/docs/PowerShell-Modules/Test-Specification/).
288+
- Tests run against the built module artifact installed locally, across a multi-OS matrix (Linux, macOS, Windows).
119289

120290
### Shared test infrastructure
121291

@@ -156,3 +326,14 @@ To skip a specific rule for one file only, add a comment at the very top of that
156326
```
157327

158328
Use skip comments sparingly and always include a meaningful reason. Prefer refactoring to comply over skipping.
329+
330+
### PSScriptAnalyzer linting
331+
332+
Source code is linted with PSScriptAnalyzer using the repo-level settings at `.github/linters/.powershell-psscriptanalyzer.psd1`. Key enforced rules include:
333+
334+
- `PSAlignAssignmentStatement` — aligned assignment operators in hashtables
335+
- `PSAvoidLongLines` — maximum 150 characters per line
336+
- `PSAvoidSemicolonsAsLineTerminators` — no trailing semicolons
337+
- `PSPlaceOpenBrace` / `PSPlaceCloseBrace` — OTBS brace style
338+
- `PSUseConsistentIndentation` — 4-space indentation
339+
- `PSUseConsistentWhitespace` — consistent spacing around operators, pipes, and separators

0 commit comments

Comments
 (0)