Skip to content

⚙️ [Maintenance]: Release workflow and test suite updated to Process-PSModule v8 and Pester 6 - #110

Draft
Marius Storhaug (MariusStorhaug) wants to merge 4 commits into
mainfrom
process-psmodule-v8-migration
Draft

⚙️ [Maintenance]: Release workflow and test suite updated to Process-PSModule v8 and Pester 6#110
Marius Storhaug (MariusStorhaug) wants to merge 4 commits into
mainfrom
process-psmodule-v8-migration

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented Aug 27, 2026

Copy link
Copy Markdown
Member

Releases, documentation builds, and test runs for this module now go through the Process-PSModule v8 workflow, and the test suite is written in Pester 6 assertion syntax. Stable releases publish from a push to main instead of a merged pull request, repository writes authenticate through a GitHub App, and failing tests now name the value that did not match instead of reporting a bare expectation.

Changed: Releases publish from the default branch

Publishing to the PowerShell Gallery, creating GitHub Releases, and building the documentation site run on the shared Process-PSModule reusable workflow, now tracking its v8 major tag.

Stable releases are evaluated from a push to main. A merged pull request supplies the version label and release notes, while a direct push or a manual dispatch falls back to a Patch bump with commit-based notes. Pull request runs continue to handle CI, prereleases, and prerelease cleanup, and concurrent release-capable runs queue rather than cancel each other so the Gallery, Releases, and tags are never mutated by two runs at once.

Maintainers of forks or of repositories copying this workflow need three organization secrets in place — PSGALLERY_API_KEY for the Gallery, plus SHELLY_CLIENT_ID and SHELLY_PRIVATE_KEY for the GitHub App that performs repository writes. The APIKey secret from the v6 contract no longer exists.

jobs:
  Process-PSModule:
    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 }}

Full details are in Calling the workflow.

Changed: Tests report which value failed

The suite requires Pester 6.1.0, matching the version the release workflow installs, and asserts through the Pester 6 Should-* commands.

A contributor who breaks the variant filter or the already-installed skip now sees the specific font files that failed the check, rather than a generic assertion message. Cases that must complete without error call the command directly, so an unexpected exception arrives with its own stack trace and line number. Running the suite locally needs Pester 6.1.0 or newer:

Install-Module Pester -MinimumVersion 6.1.0 -Scope CurrentUser
Import-Module ./src
Invoke-Pester

Changed: GitHub Actions security scanning enforced

Workflow files are audited by zizmor for GitHub Actions security issues. Tracking the shared Process-PSModule workflow by its v8 major tag conflicts with a blanket policy requiring every reference to be pinned to a commit hash.

A repository policy allows references to first-party PSModule/* workflows and actions to use a symbolic tag, while every other owner must be pinned to a full commit hash. Contributors adding a third-party action still need a hash pin, and the scan runs as part of the standard linter job.

rules:
  unpinned-uses:
    config:
      policies:
        PSModule/*: ref-pin
Policy Values Effect Default
PSModule/* ref-pin Accepts a symbolic tag or a commit hash for first-party PSModule references. Configured
Any other owner hash-pin Requires a full commit hash, applied through zizmor's implicit fallback rule. Implicit

Changed: Dependabot waits before proposing an update

Dependabot opens dependency update pull requests as soon as a new release appears, and an opportunistic package compromise is usually withdrawn within days of publication.

The GitHub Actions updater waits seven days after a release before proposing it, so an update that is pulled by the ecosystem never reaches a pull request here. Updates still arrive on the existing weekly schedule.

updates:
  - package-ecosystem: github-actions
    cooldown:
      default-days: 7

Technical details

.github/workflows/Process-PSModule.yml

  • Tracks the mutable @v8 tag, replacing the SHA pin on bf67cd9 (v6.1.19).
  • Adds the push trigger on main, which v8 requires to publish stable releases, and the unlabeled pull request type.
  • Keys concurrency on github.event.pull_request.number || github.ref with cancel-in-progress: false, keeping a pull request distinct from a default-branch push.
  • Narrows permissions to contents: read, pages: write, id-token: write. From v7 onward a GitHub App installation token performs repository writes, so contents: write, pull-requests: write, and statuses: write are no longer needed on github.token.
  • Both v7.0.0 and v8.0.0 changed the caller contract; the file matches the canonical caller in the v8.0.3 docs verbatim.

tests/NerdFonts.Tests.ps1

  • #Requires moves from Pester 6.0.0 to 6.1.0, matching the [6.1.0,7.0.0) range that v8's Test-ModuleLocal.yml passes to PSModule/Invoke-Pester.
  • Assertions used: Should-NotBeNull, Should-Be, Should-All, Should-BeCollection, Should-Throw, Should-Invoke, Should-NotInvoke.
  • Pester 6 exports no Should-NotThrow, so Should -Not -Throw becomes a bare call — the idiom Pester's own tst/functions/assert/ suite uses. Should -Invoke -Times 0 -Exactly becomes Should-NotInvoke.
  • The three duplicated -Variant tests collapse into one -ForEach case, and Use-TestFontData, Get-TestFont, and Get-TestCacheRoot replace five repeated try/finally blocks. Net 40 lines removed, same 12 tests.
  • Should-NotBeNull is not equivalent to Should -Not -BeNullOrEmpty — it passes on an empty string — so each assertion was converted individually rather than mechanically substituted.

.github/linters/zizmor.yaml and .github/PSModule.yml

  • zizmor's default unpinned-uses policy requires hash-pinning on every action; since v1.20.0 that applies to first-party namespaces too. The @v8 caller therefore reports as a high-severity finding.
  • The config sets rules.unpinned-uses.config.policies with a single PSModule/*: ref-pin entry. Unmatched clauses fall through to zizmor's implicit '*': hash-pin rule, so no third-party action is exempted.
  • The filename must be zizmor.yaml: super-linter builds the command as zizmor --config "${GITHUB_ACTIONS_ZIZMOR_LINTER_RULES}", resolved from LINTER_RULES_PATH (default .github/linters) with a default filename of zizmor.yaml. Zizmor's own discovery paths (.github/zizmor.yml, zizmor.yml) are not consulted when --config is passed.
  • VALIDATE_GITHUB_ACTIONS_ZIZMOR: false is dropped from the linter env, enabling the audit now that the workflows pass it.

.github/dependabot.yml

  • Enabling the validator surfaced a pre-existing dependabot-cooldown finding (medium): the updater declared no cooldown, so Dependabot applied its implicit three-day default while zizmor's rules.dependabot-cooldown.config.days threshold is seven. The audit had never run before because the validator was disabled.
  • cooldown.default-days: 7 resolves it. The finding was pre-existing rather than introduced here; it became visible only once zizmor ran.
  • zizmor.yaml also carries a --- document start and wrapped comments, so it satisfies the repository's yamllint document-start and 80-character line-length rules.

Verification

The full linter job passes in CI, with super-linter confirming it loads the user-provided config and reporting Successfully linted GITHUB_ACTIONS_ZIZMOR. Locally, zizmor reports no findings on the workflows and Dependabot config, and still errors on actions/checkout@v5 and a third-party @v1 reference in a scratch workflow, confirming the policy is scoped rather than blanket. Ran against a locally built module with Pester 6.1.0 and the Fonts and Admin dependencies: 12 of 12 tests pass, PSScriptAnalyzer reports nothing using this repository's .github/linters/.powershell-psscriptanalyzer.psd1. Two mutations confirm the suite still catches regressions: removing the already-installed continue fails both skip tests, and dropping the NerdFontMono exclusion from the Standard variant filter fails only the Standard case, naming the four unexpected files. Should.DisableV5 is set nowhere in the PSModule toolchain, so the classic syntax was not failing — the rewrite is a deliberate style move, not a forced fix.

.github/workflows/Update-FontsData.yml was reviewed and left unchanged: it authenticates with the repository's own NERDFONTS_UPDATER_BOT_CLIENT_ID and NERDFONTS_UPDATER_BOT_PRIVATE_KEY through PSModule/GitHub-Script and is independent of the Process-PSModule contract. src/ carries no changes, keeping clear of the parallel Install-NerdFont -Force defect work in #109.

End-to-end v8 pipeline validation

ImportantFilePatterns (^src/, ^README\.md$) means a change confined to .github/** and tests/** skips the build, test, and publish stages, so pull request CI alone does not exercise them. A manual workflow_dispatch run on this branch validated the full path: every stage green, Pester v6.1.0 with 12 of 12 tests passing on Linux, macOS, and Windows, code coverage 94.78% against the 85% target, and Build-Site reporting Build Config Type: zensical.toml. Publish-Module and Publish-Site correctly stayed skipped, since the branch is not the default branch.

Contract alignment was checked against the v8.0.3 sources rather than the docs alone: all three workflow_call secrets are declared required: true and all three are supplied; Get-PSModuleSettings resolves releases through IsPushToDefaultBranch, which the push trigger provides; unlabeled is among the accepted pull request actions; the reusable workflow declares the same three permissions; and Structure-Site accepts only zensical.toml, which this repository provides. Invoke-Pester receives [6.1.0,7.0.0), matching the test file's requirement exactly.

Standards and framework alignment

Changed surface Standards checked Framework docs checked Result
.github/workflows/** GitHub Actions Process-PSModule v8 caller contract (repository setup, calling the workflow, GitHub App authentication) Aligned
tests/** (PowerShell) PowerShell Testing Process-PSModule v8 writing module tests Aligned
.github/linters/** (zizmor) GitHub Actions zizmor configuration and unpinned-uses audit Aligned
.github/dependabot.yml Dependencies zizmor dependabot-cooldown audit Fixed in this PR

Issue convergence sweep

Swept the four open issues in this repository. #109 (-Force defect), #78 (Install-NerdFont follow-ups), #62 (#Requires -PSEdition Core), and #35 (DSC integration) all require src/ changes and are not satisfied by this diff, which touches only the workflow and the test file. No additional issues are closed.

Relevant issues (or links)

Related work

Move from the SHA pin on v6.1.19 to the mutable @v8 major tag and adopt
the canonical v8 caller contract:

- Add the push trigger on main, which v8 uses to publish stable releases.
- Add the unlabeled pull_request type.
- Key concurrency on the PR number or ref with cancel-in-progress: false.
- Narrow permissions to contents: read, pages: write, id-token: write.
- Replace the removed APIKey secret with PSGALLERY_API_KEY and the
  GitHubAppClientId/GitHubAppPrivateKey pair.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@psmodule-s-shelly

Copy link
Copy Markdown

No Significant Changes Detected

This PR does not contain changes to files that would trigger a new release:

Pattern Description
^src/ Matches files where path matches this pattern
^README\.md$ Matches files where path matches this pattern

Build, test, and publish stages will be skipped for this PR.

If you believe this is incorrect, please verify that your changes are in the correct locations.

Comment thread .github/workflows/Process-PSModule.yml Dismissed
Rewrite tests/NerdFonts.Tests.ps1 in the Pester 6 Should-* syntax and
align the requirement with the version Process-PSModule v8 installs.

- Require Pester 6.1.0 instead of 6.0.0. The v8 Test-ModuleLocal workflow
  pins Invoke-Pester to '[6.1.0,7.0.0)', so 6.0.0 understated the floor.
- Replace classic assertions with the v6 family: Should-NotBeNull,
  Should-Be, Should-All, Should-BeCollection, Should-Throw, Should-Invoke
  and Should-NotInvoke.
- Drop 'Should -Not -Throw'. Pester 6 has no Should-NotThrow; the
  documented idiom is to call the code directly and let an unexpected
  exception fail the test with a real stack trace.
- Use Should-NotInvoke instead of 'Should -Invoke -Times 0 -Exactly'.
- Collapse the three duplicated -Variant tests into one -ForEach case.
- Extract Use-TestFontData, Get-TestFont and Get-TestCacheRoot helpers so
  the module catalog is always restored via a single try/finally.

Verified against Pester 6.1.0: 12/12 pass, PSScriptAnalyzer clean, and
mutation checks confirm the suite still fails when the install-skip and
variant-filter logic are broken.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title Migrate Process-PSModule caller workflow to v8 Migrate to Process-PSModule v8 and Pester 6 test syntax Aug 27, 2026
@psmodule-s-shelly

Copy link
Copy Markdown

No Significant Changes Detected

This PR does not contain changes to files that would trigger a new release:

Pattern Description
^src/ Matches files where path matches this pattern
^README\.md$ Matches files where path matches this pattern

Build, test, and publish stages will be skipped for this PR.

If you believe this is incorrect, please verify that your changes are in the correct locations.

@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title Migrate to Process-PSModule v8 and Pester 6 test syntax ⚙️ [Maintenance]: Release workflow and test suite updated to Process-PSModule v8 and Pester 6 Aug 28, 2026
@psmodule-s-shelly

Copy link
Copy Markdown

No Significant Changes Detected

This PR does not contain changes to files that would trigger a new release:

Pattern Description
^src/ Matches files where path matches this pattern
^README\.md$ Matches files where path matches this pattern

Build, test, and publish stages will be skipped for this PR.

If you believe this is incorrect, please verify that your changes are in the correct locations.

The Process-PSModule caller tracks the mutable @v8 major tag, which
zizmor's default blanket hash-pin policy reports as a high-severity
unpinned-uses finding.

Add .github/linters/zizmor.yaml with a 'PSModule/*: ref-pin' policy so
first-party PSModule references may use a symbolic ref. Any other owner
still falls through to the implicit '*': hash-pin rule, so third-party
actions must be hash-pinned.

Super-linter invokes zizmor with '--config' resolved from
LINTER_RULES_PATH, so the file must be named zizmor.yaml under
.github/linters. Enable the validator by dropping the
VALIDATE_GITHUB_ACTIONS_ZIZMOR override, now that the workflows pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@psmodule-s-shelly

Copy link
Copy Markdown

No Significant Changes Detected

This PR does not contain changes to files that would trigger a new release:

Pattern Description
^src/ Matches files where path matches this pattern
^README\.md$ Matches files where path matches this pattern

Build, test, and publish stages will be skipped for this PR.

If you believe this is incorrect, please verify that your changes are in the correct locations.

Enabling the zizmor validator surfaced a pre-existing dependabot-cooldown
finding: the github-actions updater had no cooldown, so Dependabot used
its implicit three-day default while zizmor requires seven.

Set 'cooldown.default-days: 7' on the updater, which reduces the risk of
pulling a compromised release before the ecosystem takes it down.

Also add the document start marker and wrap the comment lines in
zizmor.yaml so it passes the repository's yamllint rules.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@psmodule-s-shelly

Copy link
Copy Markdown

No Significant Changes Detected

This PR does not contain changes to files that would trigger a new release:

Pattern Description
^src/ Matches files where path matches this pattern
^README\.md$ Matches files where path matches this pattern

Build, test, and publish stages will be skipped for this PR.

If you believe this is incorrect, please verify that your changes are in the correct locations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants