diff --git a/.github/plugin/README.md b/.github/plugin/README.md new file mode 100644 index 00000000..5b0f9e43 --- /dev/null +++ b/.github/plugin/README.md @@ -0,0 +1,42 @@ +# PSModule process plugin + +This repository hosts the `psmodule` GitHub Copilot CLI marketplace and its +`psmodule` plugin. The marketplace and plugin are owned and released +with the PSModule process. + +## Install through the marketplace + +Register this repository's marketplace and install the plugin: + +```console +copilot plugin marketplace add https://github.com/PSModule/Process-PSModule.git +copilot plugin install psmodule +``` + +## Install directly + +Install the plugin directly from this repository: + +```console +copilot plugin install PSModule/Process-PSModule:.github/plugin/psmodule +``` + +The plugin provides the +[`psmodule-pester-migration`](./psmodule/skills/psmodule-pester-migration/SKILL.md) +skill for migrating every Pester test set in a PSModule repository to Pester +6.1.0, and the +[`psmodule-v8-upgrade`](./psmodule/skills/psmodule-v8-upgrade/SKILL.md) +skill for upgrading Process-PSModule consumer repositories to framework v8. +It also provides +[`psmodule-zensical-migration`](./psmodule/skills/psmodule-zensical-migration/SKILL.md) +for migrating legacy MkDocs sites to the Process-PSModule Zensical design. +Verify the installation with: + +```console +copilot plugin list +/skills list +``` + +The marketplace catalog is in [`marketplace.json`](./marketplace.json), and +plugin metadata is in +[`plugin.json`](./psmodule/plugin.json). diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json new file mode 100644 index 00000000..32ff5943 --- /dev/null +++ b/.github/plugin/marketplace.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/marketplace.schema.json", + "name": "psmodule", + "owner": { + "name": "PSModule" + }, + "metadata": { + "description": "Agent plugins for the PSModule repository process.", + "version": "1.0.0" + }, + "plugins": [ + { + "name": "psmodule", + "description": "PSModule process guidance and reusable skills for PowerShell module repositories.", + "version": "1.0.0", + "source": ".github/plugin/psmodule" + } + ] +} diff --git a/.github/plugin/psmodule/plugin.json b/.github/plugin/psmodule/plugin.json new file mode 100644 index 00000000..7b3cce75 --- /dev/null +++ b/.github/plugin/psmodule/plugin.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "psmodule", + "description": "PSModule process guidance and reusable skills for PowerShell module repositories.", + "version": "1.0.0", + "author": { + "name": "PSModule", + "url": "https://github.com/PSModule" + }, + "homepage": "https://psmodule.io/docs/", + "repository": "https://github.com/PSModule/Process-PSModule", + "license": "MIT", + "keywords": [ + "powershell", + "psmodule", + "pester", + "testing", + "github-actions" + ], + "skills": "skills/" +} diff --git a/.github/plugin/psmodule/skills/psmodule-pester-migration/SKILL.md b/.github/plugin/psmodule/skills/psmodule-pester-migration/SKILL.md new file mode 100644 index 00000000..cd11f56f --- /dev/null +++ b/.github/plugin/psmodule/skills/psmodule-pester-migration/SKILL.md @@ -0,0 +1,496 @@ +--- +name: psmodule-pester-migration +description: Migrate every Pester test set in a PSModule repository to Pester 6.1.0, preserving test intent while checking runtime, discovery, setup, mocks, data-driven tests, coverage, CI, and reporting. Use when upgrading a PSModule repository from Pester 5 or earlier, or when validating a repository-wide Pester migration. +--- + +# Migrate PSModule tests to Pester 6.1.0 + +Use this skill to migrate a PSModule repository one test set at a time. Do not +change consumer repositories while developing or validating this skill. + +The primary source is the official +[Pester v5-to-v6 migration guide](https://pester.dev/docs/migrations/v5-to-v6). +The two [Awesome Copilot skills](https://github.com/github/awesome-copilot/tree/main/skills) +are secondary practical guidance only. If this skill and the official guide +differ, follow the official guide and record the decision. + +## Compatibility boundary + +The target is **Pester 6.1.0** on **Windows PowerShell 5.1** or **PowerShell +7.4 or later**. Upgrade one major version at a time when the source is older: +v3 to v4, v4 to v5, then v5 to v6. Do not combine unrelated test refactoring +with compatibility fixes. + +Required compatibility work: + +- Install and run Pester 6.1.0 on every supported runtime and CI image. +- Make every test file self-contained under v6's per-file discovery-and-run + model. Put discovery-only data loading in `BeforeDiscovery`. +- Review hidden files and directories, including dot-prefixed paths and + Windows Hidden items. Exclude intentional non-tests with `Run.ExcludePath`. +- Fix empty or `$null` `-ForEach` and `-TestCases`; use + `-AllowNullOrEmptyForEach` only when empty data is intentional. +- Combine duplicate `BeforeAll`, `BeforeEach`, `AfterAll`, or `AfterEach` + blocks in the same scope. +- Replace removed `Assert-MockCalled` and `Assert-VerifiableMock`. +- Add a default mock when a parameter-filtered mock must handle other calls. +- Review `<...>` name templates because their contents are now expressions. +- Replace `Set-ItResult -Pending` with `-Inconclusive`, `-Skipped`, or + `It -Skip`. +- Review coverage tracer and output format settings. +- Replace removed v4-style `Invoke-Pester` parameters with + `New-PesterConfiguration`. +- Rename a real `None` tag; in v6 it is reserved for untagged tests. + +Optional v6 adoption after compatibility is green: + +- Convert classic `Should -Be` assertions to `Should-Be` commands + incrementally. Classic syntax remains supported. +- Enable `Run.Shuffle` only after proving order independence. +- Enable experimental `Run.Parallel` only after proving file isolation and + parallel-safe resources. +- Enable `Debug.ShowStartMarkers` for diagnostic runs. + +Never present optional adoption as required migration work. + +## Step 1: Inventory the repository and test sets + +Before editing, record the repository, branch, supported PowerShell versions, +CI workflow files, Pester installation source, and every test entry point. +Search the whole repository, not just `tests/`; framework tests, action tests, +source tests, generated test fixtures, and nested suites can use different +configuration. + +```powershell +Get-ChildItem -Force +Get-ChildItem -Force -Recurse -File -Include *.Tests.ps1,*.Configuration.ps1,*.Container.ps1 +Get-ChildItem -Force -Recurse -File -Include *.yml,*.yaml,*.ps1,*.psd1,*.psm1 | + Select-String -Pattern 'Pester|Invoke-Pester|Should|BeforeDiscovery|BeforeAll|Mock' +``` + +For each test set, add an inventory row to the migration report: + +| Field | Record | +| --- | --- | +| Test set | Relative path and purpose | +| Entry point | Workflow, action, script, or local command | +| Discovery paths | Explicit paths, recursive paths, containers, configurations | +| Pester source | Required module version, install step, lock or floating policy | +| Runtime matrix | Windows PowerShell 5.1 and/or PowerShell 7.4+ | +| Setup | Root `BeforeAll.ps1`, `Pester.BeforeContainer.ps1`, file setup | +| Data | `-ForEach`, `-TestCases`, external files, generated cases | +| Mocks | Filtered mocks, mock assertions, shared state | +| Coverage/results | Paths, formats, thresholds, uploaded artifacts | +| Baseline | Run command, result counts, known failures | + +Use the inventory to find test sets that are not reachable from the default +workflow. A green workflow is not evidence that every test set was migrated. + +## Test-state and data contract + +Treat the test runner's prepared state as a contract, not an implementation +detail. A module-local Pester test must assume that the target, built module is +already loaded by the framework. It must not silently call `Import-Module`, +`Install-Module`, `Install-PSModule`, or dot-source the target module as a +fallback when the framework did not prepare it. A hidden import can make local +tests pass while the real workflow is misconfigured and can change the module +version or process state being tested. + +This does not prohibit a test whose explicit subject is module import, +manifest validity, or module removal. Such a framework or contract test should +say so in its name and use a deliberate, isolated import as the behavior under +test. It must not become setup for unrelated tests. + +### Build, load, and interface boundary + +The normal module-local execution sequence is: + +1. Process-PSModule builds the module from `src/`. +2. The framework prepares and loads that built module for the test job. +3. Pester runs the module-local test files against the prepared module. +4. Results and coverage are collected for the configured suite. + +Module-local tests validate the module's **public interface**: exported +functions, exported aliases, public classes, public variables, documented +formats, and observable behavior. They must not call private functions, +private variables, internal classes, or implementation scripts directly. +Private implementation is covered indirectly through public behavior. This +keeps tests stable when internals are refactored and proves that the shipped +module artifact works for consumers. + +Source-code or framework suites may inspect internal files when that is their +explicit purpose, but they are a separate test surface. Do not use source-code +access as setup for module-local tests, and do not interpret source-suite +coverage as proof that the public module artifact is usable. + +Every additional fixture is also part of the test-state contract. A test set +must explicitly load its own JSON, CSV, XML, PSD1, script, or generated data, +or document which setup phase provides it. In particular, a PSD1 dataset is +not automatically loaded because it is in the repository: + +```powershell +BeforeDiscovery { + $cases = Import-PowerShellDataFile -Path (Join-Path $PSScriptRoot 'Data\Cases.psd1') +} +``` + +Record fixture ownership and availability in the inventory: + +| Fixture or state | Owner | Loaded in | Required environment | +| --- | --- | --- | --- | +| Target module | Process-PSModule or explicit contract test | Framework pre-run | Built module path and version | +| Public interface | Module-local test set | Pester after framework module load | Exported commands/classes and observable behavior | +| Private implementation | Source/framework suite only when explicitly required | Separate source-test setup | Never a module-local test dependency | +| PSD1/JSON/CSV data | Test set or documented setup | `BeforeDiscovery`, `BeforeAll`, or setup job | Relative path and encoding | +| Secrets/variables | Calling workflow | `Expose-TestData` and environment | `TestData` JSON contract | +| Shared service | `tests/BeforeAll.ps1` | Before module-local matrix | Deterministic run-scoped name | +| Cleanup state | `tests/AfterAll.ps1` | Always-run teardown | Same `TestData` and run identity | + +Fail loudly when required data or state is absent. Do not use a broad +`try/catch`, an empty default, or an implicit import to turn a missing fixture +into a passing or skipped test. + +## Process-PSModule test-adjacent surfaces + +For every consumer repository, trace and record these surfaces before editing: + +| Surface | Framework contract to verify | Migration check | +| --- | --- | --- | +| `PSModule/Invoke-Pester` | Installs/runs Pester and emits per-suite JSON results and coverage artifacts | Pin/verify Pester 6.1.0, map all inputs to v6 configuration, preserve suite names | +| `Test-PSModule` action | Selects `tests/Module` or `tests/SourceCode`, resolves `outputs/module` or `src`, and passes paths to `Invoke-Pester` | Confirm path selection, exclusions, module state, and test extension | +| Module-local workflow | Downloads the built module, exposes `TestData`, imports the module, then runs module tests | Tests consume the prepared module; no hidden fallback import | +| Source-code workflow | Runs source tests against the checked-out `src` path | Record how source functions/classes are loaded and which fixtures are explicit | +| `BeforeAll-ModuleLocal` | Runs exact root `tests/BeforeAll.ps1` once before module-local jobs | Put shared services/data here only when every matrix job needs them | +| `AfterAll-ModuleLocal` | Runs exact root `tests/AfterAll.ps1` with `always()` | Make cleanup safe after setup/test failure | +| `Expose-TestData` | Converts caller `TestData` JSON into environment variables | Inventory every required variable; do not confuse it with repository fixtures | +| `Get-PesterTestResults` | Downloads `*-TestResults`, expects every configured suite, and fails missing/unexecuted/failed/inconclusive results | Preserve `TestSuiteName`, matrix names, and result counts | +| `Get-PesterCodeCoverage` | Aggregates `*-CodeCoverage` JSON and writes missed-path reports and summaries | Verify JaCoCo/Cobertura format, target, paths, and v6 tracer behavior | +| `Invoke-ScriptAnalyzer` integration | Publishes `PSModuleLint-*` results alongside test results | Treat lint suites as required result artifacts, not Pester test files | +| Repository linter | Checks repository/workflow/Markdown files independently of Pester | Include skill and documentation paths in the repository lint inventory | + +The current Process-PSModule flow specifically downloads the built module and +imports it before module-local tests, while `Test-PSModule` resolves source or +module paths for framework suites. The framework's own importability and +manifest tests may import/remove the module because import is their subject; +ordinary consumer tests must not copy that pattern as setup. + +Record the `PSModule/Invoke-Pester` action revision separately from the Pester +module version. The current Process-PSModule workflows reference that action at +`v5.1.0`; its revision and its Pester installation policy must both be checked +when adopting Pester 6.1.0. Updating one does not prove that the other changed. + +Use this per-repository checklist: + +- [ ] All `*.Tests.ps1`, `*.Configuration.ps1`, and `*.Container.ps1` files, + including action and hidden paths, are listed. +- [ ] Each test set is mapped to `Module`, `SourceCode`, action, or external + invocation and its `Run.Path`/`Run.ExcludePath` is recorded. +- [ ] The built target module is loaded by the framework before module-local + tests; tests do not silently load it themselves. +- [ ] Module-local tests exercise only the built module's public interface; + private implementation tests, if required, are separate source/framework + suites. +- [ ] Source-code test loading is explicit and documented separately from the + module-local contract. +- [ ] Every PSD1, JSON, CSV, XML, script, generated fixture, secret, variable, + and service dependency has an owner and loading phase. +- [ ] `BeforeAll.ps1`, `AfterAll.ps1`, and `Pester.BeforeContainer.ps1` paths + and scope are recorded. +- [ ] `Invoke-Pester` action inputs, Pester version, suite names, result paths, + coverage paths, filters, and output formats are recorded. +- [ ] `Get-PesterTestResults` expected artifact names include source, framework, + module, and linter suites. +- [ ] Coverage aggregation and its missed-path report are validated. +- [ ] Local, CI, and linter runs use the same intended fixture and module state. + +## Step 2: Check versions, runtime, and CI + +Run the baseline on the current version before changing files. Save the command, +Pester version, runtime, OS, result counts, and known failures. + +```powershell +Get-Module Pester -ListAvailable | Sort-Object Version -Descending | + Select-Object Name, Version, Path +(Get-Module Pester).Version +$PSVersionTable | Select-Object PSVersion, PSEdition, OS +Invoke-Pester -Path ./tests -Output Detailed +``` + +Pin the target during migration so local and CI results are comparable: + +```powershell +Install-Module Pester -RequiredVersion 6.1.0 -Force +Import-Module Pester -RequiredVersion 6.1.0 -Force +``` + +On Windows PowerShell 5.1, use `-SkipPublisherCheck` only when required to +install a newer Pester beside the inbox Pester 3. Do not hide an installation +failure. Verify the imported module is 6.1.0, not merely that a 6.x package is +available. + +Update each CI installation and invocation. Prefer an explicit requirement in +test files when that is the repository convention: + +```powershell +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.1.0'; MaximumVersion = '6.1.0' } +``` + +If the repository intentionally floats patch releases within the major, record +that policy and use `ModuleVersion = '6.0.0'; MaximumVersion = '6.*'` instead. +The migration acceptance run must still use 6.1.0. + +## Step 3: Migrate discovery, setup, and isolation + +Pester 6 discovers and runs one file before moving to the next. A file cannot +depend on top-level state created while another file was discovered. Import +modules and define file-local discovery data in the file that uses them: + +```powershell +BeforeDiscovery { + $cases = Get-Content -Raw -Path (Join-Path $PSScriptRoot 'cases.json') | + ConvertFrom-Json +} + +BeforeAll { + # Consume the module loaded by the framework. + $command = Get-Command -Name Get-MyThing -ErrorAction Stop +} + +Describe 'My command' { + It 'handles ' -ForEach $cases { + Get-MyThing -Name $Name | Should -Be 'ok' + } +} +``` + +Use `$PSScriptRoot` for fixture paths. Do not rely on +`$MyInvocation.MyCommand.Path`, the current directory, another test file's +variables, or discovery order. If the command check fails, fix the workflow's +module preparation rather than adding an import to this test. +Repository-wide bootstrap that every worker needs belongs in +`Pester.BeforeContainer.ps1` at the repository root. Keep it deterministic and +idempotent. + +Check the complete recursive path set. Pester 6 includes hidden files and +directories by default, while `.git`, `.svn`, and `.hg` remain excluded. A +newly discovered hidden test is an intended compatibility change: fix it or +exclude it explicitly, rather than assuming it should remain invisible. + +For every test set, run one file directly and then the complete set. This +isolates discovery failures from cross-file assumptions: + +```powershell +Invoke-Pester -Path ./tests/Example.Tests.ps1 -Output Detailed +Invoke-Pester -Path ./tests -Output Detailed +``` + +## Step 4: Fix v6 breaking changes + +### Data-driven tests + +Pester 6 throws for `$null` or an empty `-ForEach`/`-TestCases`. Prefer fixing +the data-loading path. If empty data is a valid result, state that intent: + +```powershell +Describe 'Optional cases' -ForEach $cases -AllowNullOrEmptyForEach { + It 'runs when a case exists' {} +} +``` + +Use the local opt-in before the run-wide +`$config.Run.FailOnNullOrEmptyForEach = $false`; the latter can hide a broken +data source. + +### Setup and teardown blocks + +Each block can contain only one `BeforeAll`, `BeforeEach`, `AfterAll`, and +`AfterEach` at its scope. Combine duplicate blocks and preserve their required +ordering explicitly. Do not move discovery-time test generation into +`BeforeAll`; use `BeforeDiscovery` for that. + +### Mocks + +Replace removed assertions: + +```powershell +# Old +Assert-MockCalled Get-Thing -Times 1 -Exactly +Assert-VerifiableMock + +# v6 +Should -Invoke Get-Thing -Times 1 -Exactly +Should -InvokeVerifiable +``` + +In v6, a call that matches no `-ParameterFilter` no longer falls through to +the real command. Add an unfiltered default mock when other calls are valid: + +```powershell +Mock Get-Thing { 'default' } +Mock Get-Thing -ParameterFilter { $Name -eq 'a' } -MockWith { 'a' } +``` + +Keep mocks and their assertions in the scope where the code under test invokes +them. A migration must not accidentally allow a real external call. + +### Pending tests + +`Set-ItResult -Pending` is removed. Use the result that expresses the intent: + +```powershell +It 'is not implemented yet' { + Set-ItResult -Inconclusive -Because 'not implemented yet' +} +``` + +Use `-Skip` when the test must not run, and `-Inconclusive` when the result +should be reported for follow-up. + +### Name templates + +In v6, every `<...>` token in a `Describe`, `Context`, or `It` name is +evaluated as a PowerShell expression in the test scope. Review arithmetic, +method calls, and expressions that were literal in v5. Escape a leading `<` +when literal text is intended: + +```powershell +It 'adds up to `<($a + $b)`>' -ForEach @{ a = 1; b = 2 } {} +``` + +Keep names stable and useful for CI result reporting. + +### Tags + +`None` is reserved, case-insensitively, to select tests with no inherited or +local tags. Rename a tag that relied on the old literal meaning and update +filters and reports. + +### Coverage + +Pester 6 uses the Profiler tracer by default and `CodeCoverage.UseBreakpoints` +defaults to `$false`. Keep the new default unless compatibility with historical +coverage numbers requires: + +```powershell +$config.CodeCoverage.UseBreakpoints = $true +``` + +`CoverageGutters` is removed. Use `JaCoCo` or `Cobertura`; coverage paths are +already relative to `Run.RepoRoot`. + +## Step 5: Standardize Invoke-Pester configuration + +Legacy v4 parameters such as `-Script`, `-OutputFile`, `-OutputFormat`, +`-EnableExit`, and direct coverage switches are removed. Use one configuration +object for scripted and CI runs: + +```powershell +$config = New-PesterConfiguration +$config.Run.Path = @('./tests') +$config.Run.Exit = $true +$config.Output.Verbosity = 'Detailed' + +$config.TestResult.Enabled = $true +$config.TestResult.OutputPath = './artifacts/pester-results.xml' +$config.TestResult.OutputFormat = 'NUnitXml' + +$config.CodeCoverage.Enabled = $true +$config.CodeCoverage.Path = @('./src') +$config.CodeCoverage.OutputFormat = 'JaCoCo' + +Invoke-Pester -Configuration $config +``` + +Set `Run.ExcludePath` for intentional exclusions, and keep paths relative to +the repository root where CI and local runs share the same layout. Do not +disable failures globally to make a migration green. + +## Step 6: Optional v6 features + +Only after the serial suite matches the baseline may a repository evaluate: + +```powershell +$config.Run.Shuffle = $true +$config.Run.Parallel = $true +``` + +`Run.Shuffle` detects order dependence. Fix order dependence rather than +permanently suppressing it. `Run.Parallel` is experimental and runs files in +separate runspaces. Prove that setup, mocks, environment variables, ports, +temporary paths, external resources, and cleanup are isolated. Use +`Pester.BeforeContainer.ps1` for shared bootstrap and `#pester:no-parallel` +only for a documented exception. Compare serial and parallel result counts and +artifacts. + +The optional `Should-*` commands are a separate modernization. Classic +`Should -Be` remains valid in v6: + +```powershell +$value | Should -Be 1 # compatible and may remain +$value | Should-Be 1 # optional v6 style +``` + +If converting, review behavior rather than applying a blind rename: + +- `Should -Not -Be` becomes `Should-NotBe`. +- Truthy/falsy expectations may need `Should-BeTruthy` or `Should-BeFalsy`; + strict boolean assertions are different. +- `Should -BeNullOrEmpty` has different intents: null, empty string, empty + collection, or falsy. +- Collection comparisons use collection assertions and may need `-Actual` + because pipeline input is unwrapped. +- `Should -Exist` and file-content assertions can remain classic. + +Adopt this style incrementally and report intentionally unchanged assertions. + +## Step 7: Validate and report + +Run the migrated test set in this order: + +1. Every discovered file directly, including hidden paths and files selected + by configuration or container scripts. +2. Each test set serially with Pester 6.1.0. +3. Each supported PowerShell runtime and CI operating system. +4. Coverage and test-result generation with the same configuration CI uses. +5. Optional shuffle and parallel runs, only if the repository opted in. + +For every run, record the command, runtime, Pester version, path, passed, +failed, skipped, inconclusive, not-run, coverage summary, and artifact paths. +Compare against the baseline and investigate changed counts. A report should +include: + +```text +Repository: +Target: Pester 6.1.0 +Test set: +Runtime / OS: +Baseline: +Serial result: +Coverage / result artifacts: +Required compatibility fixes: +Optional v6 features enabled: +Known limitations or deferred work: +``` + +Review the diff for test-intent changes, accidental real calls, path assumptions, +and generated artifacts before committing. Make small commits by file or +concern so a failed migration is easy to bisect. + +## References + +- [PSModule module test guidance](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/writing-module-tests.md) +- [PSModule workflow and test data guidance](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/calling-the-workflow.md) +- [PSModule pipeline configuration](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/configuring-the-pipeline.md) +- [PSModule test specification](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/test-specification.md) +- [PSModule workflow inputs](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/workflow-inputs.md) +- [Pester v5-to-v6 migration](https://pester.dev/docs/migrations/v5-to-v6) +- [Pester installation and compatibility](https://pester.dev/docs/introduction/installation) +- [Pester configuration](https://pester.dev/docs/usage/configuration) +- [Pester data-driven tests](https://pester.dev/docs/usage/data-driven-tests) +- [Pester mocking](https://pester.dev/docs/usage/mocking) +- [Pester parallel execution](https://pester.dev/docs/usage/parallel) +- [Pester code coverage](https://pester.dev/docs/usage/code-coverage) +- [Pester test results](https://pester.dev/docs/usage/test-results) +- [Pester Should assertions](https://pester.dev/docs/assertions/should-command) +- [Awesome Copilot pester-migration](https://github.com/github/awesome-copilot/tree/main/skills/pester-migration) +- [Awesome Copilot pester-should-migration](https://github.com/github/awesome-copilot/tree/main/skills/pester-should-migration) diff --git a/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md b/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md new file mode 100644 index 00000000..1d4201c3 --- /dev/null +++ b/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md @@ -0,0 +1,324 @@ +--- +name: psmodule-v8-upgrade +description: Upgrade a Process-PSModule consumer repository to framework version 8 while preserving repository intent, enforcing the caller workflow contract, migrating Pester tests to v6, and retaining the repository's Zensical documentation design. +--- + +# Upgrade a Process-PSModule consumer to v8 + +Use this skill when a consumer repository needs to move its +`Process-PSModule.yml` caller to `PSModule/Process-PSModule/.github/workflows/workflow.yml@v8`. +Keep the change limited to the requested framework upgrade. Do not migrate +consumer repositories while developing or validating this skill. + +## Operating contract + +1. Inspect the consumer repository before editing. Read its local guidance, + workflow, documentation configuration and content, tests, settings, + dependencies, and existing validation commands. +2. Work on a dedicated branch, open a draft pull request early, and use small + commits. Include the required Copilot co-author trailer in every commit. +3. Preserve test intent, fixtures, secrets, variables, documentation content, + custom theme assets, and repository-owned automation unless the upgrade + requires a direct change. +4. Report preserved TestData, Pester migration details, Zensical/theme changes, + validation results, and blockers in the pull request. + +Use the repository's applicable MSX workflow and PR format guidance. Do not +silently broaden the scope when the consumer has unrelated failures. + +## Consumer layout variance + +Do not assume that a consumer already resembles Process-PSModule. Inventory +what is present before deciding what to migrate: + +- Many consumers have a legacy `.github/mkdocs.yml` and no `docs/` directory. +- Some consumers already have `docs/zensical.toml`, custom overrides, and + assets that must be preserved. +- A consumer may already declare Pester 6; do not repeat a dependency migration + that is already complete. +- Existing repository-owned workflows, scripts, and settings are not caller + workflow extensions. Keep them in separate files and validate them in place. + +When documentation is absent, do not invent a site as part of the caller +upgrade unless the requested scope explicitly includes documentation migration. +When a legacy MkDocs configuration exists and documentation migration is in +scope, migrate its content and design deliberately to Zensical, then remove +the obsolete configuration only after the generated site validates. + +## Template-PSModule baseline + +Use [`PSModule/Template-PSModule`](https://github.com/PSModule/Template-PSModule) +and the [PSModule Repository Standard](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/repository-standard.md) +as the structural baseline for module repositories. Compare the consumer with +the template's default files before adding, removing, or relocating anything. +The template is a starting point, not a reason to overwrite module-specific +content. + +The current template's default repository anatomy is: + +```text +/ +├── .github/ +│ ├── CODEOWNERS +│ ├── dependabot.yml +│ ├── linters/ +│ │ ├── .codespellrc +│ │ ├── .markdown-lint.yml +│ │ ├── .powershell-psscriptanalyzer.psd1 +│ │ └── .textlintrc +│ ├── PSModule.yml +│ ├── pull_request_template.md +│ ├── release.yml +│ ├── workflows/ +│ └── Process-PSModule.yml +│ └── zensical.toml +├── examples/ +├── icon/ +├── src/ +│ ├── classes/ +│ ├── data/ +│ ├── formats/ +│ ├── functions/ +│ │ ├── private/ +│ │ └── public/ +│ ├── init/ +│ ├── modules/ +│ ├── scripts/ +│ ├── types/ +│ └── variables/ +├── tests/ +│ ├── AfterAll.ps1 +│ ├── BeforeAll.ps1 +│ └── .Tests.ps1 +├── .gitattributes +├── .gitignore +├── AGENTS.md +├── CONTRIBUTING.md +├── LICENSE +└── README.md +``` + +Treat files and directories as follows: + +- Required baseline files should be present in the consumer and remain + repository-local; do not rely on organization-level fallback files. +- `src/`, especially `src/functions/public/` and `src/functions/private/`, + along with `tests/`, `examples/`, and `icon/`, is module-owned content. + Preserve its intent and only migrate paths when the framework contract + requires it. +- `.github/PSModule.yml`, linters, Dependabot, CODEOWNERS, release metadata, + pull-request templates, and repository guidance are configuration surfaces. + Inspect and preserve them independently of the caller workflow. +- `tests/BeforeAll.ps1` and `tests/AfterAll.ps1` are optional root-level + module-local phases, not recursively discovered test files. +- Optional source folders such as `assemblies`, `formats`, `types`, `variables`, + `data`, `modules`, and `scripts` are added when the module needs them; do not + create empty placeholders solely to match the tree. +- The template's starter test declares Pester 6 with `#Requires` and uses the + native `Describe`, `It`, and `Should-Be` syntax. Preserve that requirement + when the consumer already has the Pester 6 baseline. +- The template's `.github/PSModule.yml` sets a zero code-coverage target and + carries explicit linter environment defaults; compare these settings before + replacing or deleting a consumer settings file. +- The template's `AGENTS.md` points to Template-PSModule quickstart, repository + defaults, module anatomy, build/test/pack/publish, and standards guidance. + Preserve the consumer's local onboarding contract while updating stale links. + +If the template revision and the consumer's existing layout disagree, record +the difference and migrate only the requested integration surface. In +particular, the current template uses `.github/zensical.toml`, while this v8 +upgrade request uses `docs/zensical.toml`, `docs/content/`, and +`docs/overrides/` (including `docs/overrides/assets/`) as its documentation +contract; a separate `docs/assets/` directory is optional. When that requested +documentation migration is in scope, move the template's Zensical settings +and custom assets into the `docs/` contract rather than maintaining both +configurations. When it is not in scope, preserve the consumer's existing +working configuration and report the difference. + +For a documentation-only MkDocs migration, use the dedicated +[`psmodule-zensical-migration`](../psmodule-zensical-migration/SKILL.md) skill +so content, theme, assets, and link validation are handled independently from +the caller workflow upgrade. + +## Caller workflow contract + +Replace `.github/workflows/Process-PSModule.yml` with exactly this template: + +```yaml +name: Process-PSModule + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + push: + branches: + - main + pull_request: + branches: + - main + types: + - closed + - opened + - reopened + - synchronize + - labeled + - unlabeled + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: {} + +jobs: + Process-PSModule: + permissions: + contents: read + pages: write + id-token: write + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 + secrets: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }} + GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} +``` + +The only permitted variation is an optional `TestData` entry under +`jobs.Process-PSModule.secrets`. Do not add `with:` inputs, extra jobs, +conditions, schedule changes, `run-name`, permission changes, trigger changes, +concurrency changes, debug options, or version overrides. Repository-owned +automation belongs in separate workflow files. + +### TestData preservation + +First determine whether the current caller passes secrets or variables to the +framework. Preserve a required payload by translating it to this compact JSON +shape: + +```yaml + TestData: >- + {"secrets":{"SERVICE_TOKEN":"SERVICE_TOKEN"},"variables":{"ENVIRONMENT":"production"}} +``` + +Include only the `secrets` and/or `variables` maps that the consumer uses. Keep +the existing names and semantics. Omit `TestData` entirely when the repository +does not use it. Never move secrets into source files, workflow `env`, or +committed settings. + +For example, preserve a secrets-only caller payload such as PSModule/GitHub's: + +```yaml + TestData: >- + {"secrets":{"TEST_USER_USER_FG_PAT":"${{ secrets.TEST_USER_USER_FG_PAT }}","TEST_USER_ORG_FG_PAT":"${{ secrets.TEST_USER_ORG_FG_PAT }}","TEST_USER_PAT":"${{ secrets.TEST_USER_PAT }}","TEST_APP_ORG_CLIENT_ID":"${{ secrets.TEST_APP_ORG_CLIENT_ID }}","TEST_APP_ORG_PRIVATE_KEY":"${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}","TEST_APP_ENT_CLIENT_ID":"${{ secrets.TEST_APP_ENT_CLIENT_ID }}","TEST_APP_ENT_PRIVATE_KEY":"${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}"}} +``` + +Preserve a mixed secrets-and-variables payload such as PSModule/Confluence's: + +```yaml + TestData: >- + {"secrets":{"CONFLUENCE_API_TOKEN":"${{ secrets.CONFLUENCE_API_TOKEN }}"},"variables":{"CONFLUENCE_SITE":"${{ vars.CONFLUENCE_SITE }}","CONFLUENCE_USERNAME":"${{ vars.CONFLUENCE_USERNAME }}","CONFLUENCE_SPACE_KEY":"${{ vars.CONFLUENCE_SPACE_KEY }}"}} +``` + +These examples are contracts, not values to copy into an unrelated consumer. +Read the original workflow and preserve only the maps and keys it actually +uses. + +## Documentation integration + +Treat `docs/zensical.toml` as authoritative. Do not create or maintain +`mkdocs.yml`, introduce MkDocs configuration, or replace the consumer's +documentation design with a parallel theme. + +Compare the consumer's configuration with the Process-PSModule template and +preserve or migrate these surfaces deliberately: + +- `docs/zensical.toml`, including `docs_dir` and navigation. +- `docs/content/` as the documentation source directory. +- `docs/overrides/` and custom templates. +- `docs/overrides/assets/stylesheets/navigation.css`. +- `docs/overrides/assets/`, any optional `docs/assets/`, logo, favicon, palette, + fonts, and custom JavaScript/CSS. +- Existing navigation labels, page paths, and custom theme behavior. + +Do not delete existing custom CSS/assets merely because the default theme also +provides an equivalent feature. Resolve duplicate configuration in favor of +the existing consumer source of truth, then validate the generated site. + +## Pester v6 migration + +The framework upgrade does not permit leaving tests on an undeclared or +unsupported Pester version. Inspect module requirements, dependency manifests, +local setup, CI installation, and every test entry point. Declare and run the +repository's supported Pester 6 dependency. If Pester 6 is already declared, +retain the declaration and focus on configuration, discovery, assertions, +setup/teardown, and output compatibility. + +Rewrite tests using native Pester v6 terminology and APIs: + +- Prefer `New-PesterConfiguration` and + `Invoke-Pester -Configuration`. +- Update discovery, run configuration, output, result, and coverage handling + without changing test intent. +- Make each test file safe under Pester v6 per-file discovery and execution. +- Preserve explicit fixture ownership and loading; do not silently import the + target module as a fallback for a broken framework setup. +- Keep setup and teardown deterministic and scoped. +- Preserve data-driven cases, mocks, pending behavior, names, tags, coverage, + and result reporting while applying the v6 compatibility rules. + +Do not merely rename commands. Run the migrated tests with the declared Pester +6 dependency and investigate failures as migration or repository issues. + +## Repository integration inventory + +Before editing, record the current state and the intended v8 result for: + +| Surface | Inspect | Required result | +| --- | --- | --- | +| Caller workflow | Triggers, permissions, concurrency, secrets, `with:` inputs | Exact v8 contract; only documented `TestData` may vary | +| TestData | Secret and variable names and consumers | Explicit compact JSON maps, or omitted when unused | +| Pester dependency | `#Requires`, manifests, install steps, lockfiles | Pester 6 is declared and installed consistently | +| Pester configuration | `Invoke-Pester`, output, result, coverage | Native configuration object and v6-compatible output | +| Test setup | Before/After blocks, module load, fixtures, services | Explicit ownership and deterministic per-file behavior | +| Documentation | Zensical config, content, overrides, assets | Existing Zensical design remains authoritative | +| Repository automation | Other workflows and scripts | Unrelated automation remains separate and unchanged | +| Validation | Existing tests, lint, site build, workflow checks | Existing repository-native validation is rerun | + +## Validation + +Run the smallest existing checks that cover the changed surfaces, then escalate +when a targeted check reveals a broader dependency: + +1. Validate YAML syntax and confirm the caller has no forbidden variation. +2. Run the existing Process-PSModule workflow or its repository-native + equivalent when available. +3. Run the Pester v6 test suites with the repository's declared configuration. +4. Run the repository's existing lint and test commands. +5. When documentation exists, run: + + ```powershell + Push-Location docs + zensical build --clean + Pop-Location + ``` + +6. Review the diff for accidental workflow permissions, trigger changes, + secret exposure, fixture removal, generated files, or unrelated refactoring. + +Report commands and outcomes, including blocked checks and why they were +blocked. A green documentation build does not substitute for Pester or +workflow validation. + +## References + +- [PSModule repository standard](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/repository-standard.md) +- [PSModule workflow inputs](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/workflow-inputs.md) +- [PSModule pipeline stages](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/pipeline-stages.md) +- [PSModule module test guidance](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/writing-module-tests.md) +- [PSModule workflow and test data guidance](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/calling-the-workflow.md) +- [Zensical setup basics](https://zensical.org/docs/setup/basics/) +- [Pester v6 quick start](https://pester.dev/docs/v6/quick-start) +- [Pester `New-PesterConfiguration` command](https://pester.dev/docs/commands/New-PesterConfiguration) +- [Pester `Invoke-Pester` command](https://pester.dev/docs/commands/Invoke-Pester) +- [MSX PR format](https://msxorg.github.io/docs/Ways-of-Working/PR-Format/) diff --git a/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md b/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md new file mode 100644 index 00000000..b6e19fef --- /dev/null +++ b/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md @@ -0,0 +1,159 @@ +--- +name: psmodule-zensical-migration +description: Migrate a PSModule repository from MkDocs to Zensical using the Process-PSModule documentation design as the default while preserving content, navigation, assets, and repository-specific behavior. +--- + +# Migrate PSModule documentation from MkDocs to Zensical + +Use this skill when a PSModule repository has a legacy MkDocs configuration, +usually `.github/mkdocs.yml`, or needs its documentation site aligned with the +Process-PSModule Zensical design. Keep the migration limited to documentation +integration. Do not rewrite module code or tests unless a documentation build +requires a directly related fix. + +## Source-of-truth design + +Use the current +[`Process-PSModule` `docs/zensical.toml`](https://github.com/PSModule/Process-PSModule/blob/main/docs/zensical.toml) +as the default style and configuration baseline. Reuse its behavior rather than +inventing a second theme: + +- `docs_dir = "content"` with content under `docs/content/`. +- `docs/overrides/` as the custom theme directory. +- `docs/overrides/assets/stylesheets/navigation.css` for navigation styling. +- `docs/overrides/assets/` and any repository-owned `docs/assets/` directory + when present as the asset source of truth. +- Mona Sans text and Source Code Pro code fonts. +- Material-style GitHub/link icons, light/dark/system palette toggles, + black/slate/light-blue colors, and the established navigation features. +- Instant navigation, prefetch/preview/progress, tabs, tracking, top navigation, + search, code copy, tooltips, table of contents, and footer behavior. +- TOC, attribute lists, abbreviations, admonitions, definition lists, + footnotes, tables, HTML-in-Markdown, details, superfences, task lists, and + snippets extensions. +- Mermaid fenced blocks and the shared abbreviations snippet. +- Existing social links, consent configuration, site metadata, edit URI, and + custom tablesort JavaScript where those features apply. + +Customize only repository identity and content-specific values such as +`site_name`, `site_url`, `repo_name`, `repo_url`, `edit_uri`, copyright, social +links, and `nav`. Do not remove a default feature merely because the old +MkDocs site did not use it. + +## Inspect before changing + +Inventory the existing repository and record: + +1. Local guidance, branch state, and documentation build commands. +2. `.github/mkdocs.yml`, any `mkdocs.yml`, and any existing Zensical config. +3. The Markdown content root, includes/snippets, templates, media, and assets. +4. `theme`, `theme.custom_dir`, `extra_css`, `extra_javascript`, `plugins`, + `markdown_extensions`, `nav`, `extra`, and `watch` settings. +5. Links, anchors, generated API/help pages, redirects, and CI publishing steps. +6. Existing custom CSS, JavaScript, templates, logos, favicons, and fonts. + +Do not assume every consumer has a `docs/` tree. If documentation is absent, +create it only when the requested scope includes documentation migration. If +the repository already uses Zensical, compare it with the Process-PSModule +baseline and make only the required alignment changes. + +## Target layout + +For the Process-PSModule documentation contract, use: + +```text +docs/ +├── content/ +├── overrides/ +│ └── assets/ +│ ├── javascripts/ +│ └── stylesheets/ +└── zensical.toml +``` + +Keep existing content under `docs/content/`, custom templates under +`docs/overrides/`, and theme assets under `docs/overrides/assets/`. A separate +`docs/assets/` directory is optional for static content assets. Do not create a +parallel MkDocs theme or leave two active site configurations. + +The current `PSModule/Template-PSModule` repository historically stores a +starter `.github/zensical.toml` and may not contain a `docs/` tree. When the +consumer upgrade explicitly requires the Process-PSModule `docs/` contract, +move the template settings and custom assets into `docs/` and remove the +obsolete active configuration only after the site builds. When that contract +is not in scope, preserve a working template layout and report the difference +instead of moving files speculatively. + +## MkDocs-to-Zensical mapping + +Translate behavior, not just filenames: + +| MkDocs | Zensical | +| --- | --- | +| `site_name`, `site_url` | `[project]` metadata | +| `docs_dir` | `[project].docs_dir` | +| `repo_name`, `repo_url`, `edit_uri` | `[project]` metadata | +| `nav` | `nav = [...]` TOML entries | +| `theme.name` | `[project.theme]` settings | +| `theme.custom_dir` | `[project.theme].custom_dir` | +| `theme.logo`, `theme.favicon` | `[project.theme]` paths | +| `theme.features` | `[project.theme].features` | +| `theme.palette` | `[[project.theme.palette]]` tables | +| `extra_css`, `extra_javascript` | `[project]` arrays | +| `markdown_extensions` | `[project.markdown_extensions.*]` tables | +| `plugins: search` | `[project.plugins.search]` | +| `extra.social` | `[[project.extra.social]]` | +| `watch` | `[project].watch` | + +Preserve Markdown semantics while checking extensions that affect formatting: +admonitions, fenced code, tables, task lists, definition lists, attributes, +snippets, Mermaid, and anchor/permalink behavior. Fix only documented +Zensical incompatibilities; do not silently change headings or links to hide +build failures. + +## Content and link migration + +- Preserve page paths and navigation labels where possible. +- Keep explicit `nav` entries for important landing pages and references. +- Resolve relative links from the new `docs/content/` root. +- Recheck fragment anchors because heading and permalink behavior can differ. +- Move includes/snippets to the configured Zensical location and update every + reference. +- Keep generated API/help inputs in their framework-owned locations. +- Preserve images and downloads; update paths rather than deleting assets. +- Keep custom templates only when they are still required by the migrated site. + +## Validation + +Run repository-native checks and the smallest targeted site checks first: + +1. Parse the new `docs/zensical.toml` and verify all configured paths exist. +2. Confirm no active `mkdocs.yml` or MkDocs workflow remains. +3. Check every navigation target, image, download, include, and fragment link. +4. Run the existing documentation lint and link checks. +5. Build the site: + + ```powershell + Push-Location docs + zensical build --clean + Pop-Location + ``` + +6. Review the generated site for navigation, search, palette toggles, fonts, + logo/favicon, custom navigation behavior, code blocks, Mermaid, and social + links. +7. Run the repository's existing Process-PSModule workflow validation when the + documentation is part of that pipeline. + +Do not claim success if the site builds while links, assets, navigation, or +publishing behavior are broken. Report intentionally deferred pages, +unsupported extensions, and unrelated pre-existing failures. + +## References + +- [Process-PSModule Zensical configuration](https://github.com/PSModule/Process-PSModule/blob/main/docs/zensical.toml) +- [Process-PSModule documentation overrides](https://github.com/PSModule/Process-PSModule/tree/main/docs/overrides) +- [Process-PSModule override assets](https://github.com/PSModule/Process-PSModule/tree/main/docs/overrides/assets) +- [PSModule repository standard](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/repository-standard.md) +- [PSModule documentation model](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/structuring-your-module.md) +- [Zensical setup basics](https://zensical.org/docs/setup/basics/) diff --git a/docs/content/index.md b/docs/content/index.md index 57fe5412..d7f2abc8 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -40,6 +40,7 @@ Task-oriented deep dives into the pipeline's functionality. | [Configuring the pipeline](guides/configuring-the-pipeline.md) | Worked examples for coverage targets, rapid testing, linting, and PR-based release notes. | | [Structuring your module](guides/structuring-your-module.md) | The repository and module source layout the workflow expects, and how to declare dependencies. | | [Writing module tests](guides/writing-module-tests.md) | Test discovery, setup and teardown phases, and shared test infrastructure. | +| [PSModule process marketplace](https://github.com/PSModule/Process-PSModule/tree/main/.github/plugin) | Register the initiative marketplace and install the PSModule process plugin. | | [Skipping framework tests](guides/skipping-framework-tests.md) | Skip individual framework tests on a per-file basis. | | [Versioning and releases](guides/versioning-and-releases.md) | Label-driven versioning, prereleases, and what a release produces. | | [Validating before review](guides/validating-before-review.md) | The PSModule validation pass before a draft pull request is marked ready. |