Calling the workflow#
To use the workflow, create a new file in the .github/workflows directory of the module repository and add the following content.
For documentation site generation, use zensical.toml as the active site contract.
For the exact inputs, secrets, and permissions the reusable workflow declares, see Workflow inputs.
Workflow suggestion
name: Process-PSModule
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
pull_request:
branches:
- main
types:
- closed
- opened
- reopened
- synchronize
- labeled
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
statuses: write
pages: write
id-token: write
jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5
secrets:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
Passing test data#
The reusable workflow at .github/workflows/workflow.yml declares four workflow-call secrets,
which keeps the calling workflow in full control of the credentials that are exposed.
secrets: inherit is intentionally not required. PSGALLERY_API_KEY publishes to the PowerShell Gallery,
GitHubAppClientId and GitHubAppPrivateKey authenticate GitHub API operations, and TestData
carries everything the module's own tests need.
Breaking change: fixed test secrets use TestData#
The reusable workflow accepts test data through TestData and no longer declares or accepts the old fixed test-secret inputs:
TEST_APP_ENT_CLIENT_IDTEST_APP_ENT_PRIVATE_KEYTEST_APP_ORG_CLIENT_IDTEST_APP_ORG_PRIVATE_KEYTEST_USER_ORG_FG_PATTEST_USER_USER_FG_PATTEST_USER_PAT
If a caller passed any of these secrets directly, place them in the secrets map inside TestData.
The environment variable names used by the tests can stay the same; only the workflow-call interface
changes:
jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5
secrets:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
TestData: >-
{ "secrets": { "TEST_USER_PAT": "${{ secrets.TEST_USER_PAT }}",
"TEST_APP_ORG_CLIENT_ID": "${{ secrets.TEST_APP_ORG_CLIENT_ID }}" } }
Passing test phase data (secrets and variables)#
A single TestData secret lets a module expose any number of caller-defined values to its test jobs
(BeforeAll-ModuleLocal, Test-ModuleLocal and AfterAll-ModuleLocal) without changing the shared
workflow. It is one JSON object with two maps, so everything the tests need is visible in one place:
{ "secrets": { "NAME": "value" }, "variables": { "NAME": "value" } }
Values under secrets are masked in the logs; values under variables are not. Build it in the
calling workflow and pass it through the secrets: block (so the whole blob is masked). Reference each
secret directly as "${{ secrets.<name> }}" and each variable as ${{ toJSON(vars.<name>) }}. A
folded >- scalar keeps the source readable while producing a single-line value, as long as the JSON
content lines stay at the same indentation level:
jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5
secrets:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
TestData: >-
{ "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" },
"variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }},
"CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }},
"CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } }
Each entry becomes an environment variable in the test jobs, so the module's Pester tests read the values directly:
$env:CONFLUENCE_API_TOKEN # from the "secrets" map (masked in logs)
$env:CONFLUENCE_SITE # from the "variables" map (not masked)
The same TestData keys are exported before every module-local phase runs:
BeforeAll-ModuleLocalruns roottests/BeforeAll.ps1before the module-local test matrix.Test-ModuleLocaldiscovers and runs module-local Pester tests recursively.AfterAll-ModuleLocalruns roottests/AfterAll.ps1after the module-local test matrix, including cleanup paths.
Setup and teardown detection is not recursive. These root scripts and the discovered tests should use the same environment variable names.
If $env:<name> is available in one phase but missing in another, treat that as a Process-PSModule
propagation bug rather than a caller contract difference.
Notes:
- The names are caller-defined; no secret or variable names are hard-coded in the shared workflow.
Names must match
^[A-Za-z_][A-Za-z0-9_]*$and must not override reserved variables such asPATH,CI,GITHUB_*,RUNNER_*orACTIONS_*. - The
TestDatavalidation, masking and environment export logic is shared by the ModuleLocal workflows through thePSModule/Install-PSModuleHelpersaction, which installs theImport-TestDatacommand each workflow runs to expose the values. - Reference secrets as
"${{ secrets.<name> }}"(quoted, directly) rather thantoJSON(secrets.<name>). The direct form keeps CodeQL's excessive secrets exposure check happy and works for single-line secret values. It cannot carry values that contain",\or newlines, so base64-encode a multi-line or special-character secret and decode it in the test (for example[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:MY_KEY_B64))). - Variables use
toJSON(vars.<name>)so any characters are JSON-encoded safely; they are never masked. You can use the same quoted direct form as secrets ("${{ vars.<name> }}") only for simple values that do not contain",\or newlines. - Provide
TestDataas a single-line value (the folded>-block above does this). Avoid a literal|block: GitHub registers every line of a multi-line secret as its own mask, which over-masks unrelated log output. - Do not pretty-print
TestDatawith nested indentation. YAML preserves more-indented lines inside a folded scalar, so a fully formatted JSON object can still become a multi-line secret. That makes GitHub register each line as its own mask, including brace-only lines such as{,}or},, which can turn unrelated log output into***. Keep the compact form above, or keep every JSON content line at the same indentation level. - Omit
TestDataentirely when the module needs no secrets or variables. Include only the map you need (justsecrets, justvariables, or both). - Because
secrets: inheritis not used, only the values you list are ever exposed. - If using
secrets: inheritin a caller workflow, remember that GitHub only forwards secrets that already exist by name. It does not assemble aTestDataJSON payload from individual secrets such asTEST_USER_PAT; the caller must still create and pass theTestDatavalue explicitly. - Organization, repository and GitHub Environment secrets and variables are supported when they are
visible to the calling job. For environment-scoped values, set
environment:on the calling job and explicitly include those values inTestData; they are not exposed automatically.
Important file change detection#
The workflow automatically detects whether a pull request contains changes to "important" files that should enter the build, test, and publish path. This prevents unnecessary work and releases when only files outside the configured patterns are modified.
Files that trigger the important-change path#
By default, the following regular expression patterns identify important files:
| Pattern | Description |
|---|---|
^src/ |
Module source code |
^README\.md$ |
Module documentation |
Customizing important file patterns#
To override the default patterns, set ImportantFilePatterns in your settings file (.github/PSModule.yml):
ImportantFilePatterns:
- '^src/'
- '^README\.md$'
- '^tests/'
- '^\.github/PSModule\.yml$'
- '^\.github/workflows/'
When configured, the provided list fully replaces the defaults. Include the default patterns in your list if you still want them to trigger the build, test, and publish path.
Recursive module-local test discovery does not change this trigger.
With the defaults, a test-only change does not run the important-change build, test, and publish stages because
^tests/ is not matched. Add ^tests/ when those changes must exercise the path, plus each settings, workflow, or
other automation path whose changes need the same validation. Include only paths that should trigger all three stages.
To disable file-change triggering entirely (so that no file changes ever trigger a release), set an empty list in the settings file:
ImportantFilePatterns: []
You can also pass patterns via the workflow input:
jobs:
Process:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5
with:
ImportantFilePatterns: |
^src/
^README\.md$
^examples/
To disable triggering via the workflow input, pass an explicit empty string:
jobs:
process:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v5
with:
ImportantFilePatterns: ''
Note that omitting the ImportantFilePatterns key entirely causes the workflow's default patterns (^src/ and
^README\.md$) to be used. The settings file takes priority over the workflow input, so set
ImportantFilePatterns: [] in .github/PSModule.yml to disable triggering regardless of the workflow input.
Resolution order: settings file → workflow input → workflow input default values.
Behavior when no important files are changed#
When a pull request does not contain changes to important files:
- A comment is automatically added to the PR listing the configured patterns and explaining why build/test stages are skipped
Settings.Publish.Module.Resolution.ReleaseTypeisNone(andSettings.Publish.Module.Resolution.CreateReleaseisfalse)- Build, test, and publish stages are skipped
- The PR can still be merged for non-release changes (documentation updates, CI improvements, etc.)
This behavior ensures that maintenance PRs (such as updating GitHub Actions versions or fixing typos in comments) don't create unnecessary releases in the PowerShell Gallery.