Skip to content

🌟 [Major]: Publish stable releases from default-branch pushes - #512

Merged
Marius Storhaug (MariusStorhaug) merged 14 commits into
mainfrom
push-main-stable-release
Aug 15, 2026
Merged

🌟 [Major]: Publish stable releases from default-branch pushes#512
Marius Storhaug (MariusStorhaug) merged 14 commits into
mainfrom
push-main-stable-release

Conversation

@MariusStorhaug

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

Copy link
Copy Markdown
Member

Process-PSModule now authorizes stable module publication from an important push to the default branch rather than from a merged pull_request event. An associated merged pull request supplies labels and release notes only when its merge commit exactly matches the pushed SHA; direct pushes and default-branch manual dispatches release a Patch version with commit-based notes.

Breaking Changes

Caller workflows must add a push trigger for their default branch to receive stable releases. The canonical caller templates now use non-cancelling per-pull-request-or-ref concurrency so pull-request cleanup and the resulting default-branch release remain independent while all release mutations queue safely. The consumer-repository rollout remains tracked in #438.

Changed: Stable release lifecycle

  • Open labelled pull requests can publish prereleases; closed pull requests run prerelease cleanup only.
  • Important default-branch pushes run the full pipeline and publish a stable release only after PowerShell Gallery publication succeeds.
  • GitHub releases and tags target the exact tested pushed commit.
  • Direct default-branch pushes and manual dispatches default to Patch regardless of AutoPatching; pull-request prereleases retain their configured AutoPatching behavior.
  • First pushes are evaluated from the complete Git tree, and truncated trees fail explicitly rather than producing an unsafe release decision.
  • Only a real pull_request.closed event can enter cleanup; a label change on an already closed PR runs no build, prerelease, or cleanup path.
  • Release workflow concurrency uses cancel-in-progress: false to preserve serialized artifact and tag mutation.

Dogfood and documentation

  • The default and manifest dogfood callers now receive the complete PR event contract, including label changes and closure, and do not cancel release-capable runs.
  • Fixture roots are important, fixture sites build, and Publish.Site.Skip prevents deployment to the Process-PSModule Pages environment.
  • Caller, first-release, scenario, stage, settings, specification, and module-standard documentation now define default-branch push authority, cleanup-only closed PRs, direct-push behavior, and non-cancelling concurrency.

Scope boundary

Release-GHRepository@v2.0.3, which releases Process-PSModule itself, only supports pull-request event payloads. This PR therefore does not install a push trigger that would succeed without producing that repository release. Push-capable repository releases and moving workflow-consumer tags are tracked separately in PSModule/Release-GHRepository#108.

Technical Details

  • The Plan settings action normalizes PR, push, and manual-dispatch state into a shared context.
  • Push-to-PR association uses GET /repos/{owner}/{repo}/commits/{sha}/pulls and requires an exact merged commit match on the default branch.
  • Version-label conflicts remain blocking for release decisions but cannot block closed-PR cleanup.
  • Publishing, GitHub release creation, and cleanup receive the normalized PR context explicitly, so direct releases never attempt PR comments.
  • Publish.Site.Skip separates site build from deployment; dogfood callers mark their fixture roots as important, build site artifacts, and skip real Pages publication.
Related issues

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

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown

Super-linter summary

Language Validation result
CHECKOV Pass ✅
CSS Pass ✅
CSS_PRETTIER Pass ✅
GITHUB_ACTIONS Pass ✅
GITHUB_ACTIONS_ZIZMOR Pass ✅
GITLEAKS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
HTML Pass ✅
JAVASCRIPT_ES Pass ✅
JAVASCRIPT_PRETTIER Pass ✅
MARKDOWN Pass ✅
NATURAL_LANGUAGE Pass ✅
POWERSHELL Pass ✅
PRE_COMMIT Pass ✅
SPELL_CODESPELL Pass ✅
TRIVY Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the GitHub Actions workflow run

Powered by Super-linter

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves stable publication authority from merged-PR events to default-branch pushes.

Changes:

  • Normalizes push, PR, and manual-dispatch release context.
  • Updates publication, cleanup, concurrency, and site-deployment routing.
  • Aligns dogfood workflows, tests, and documentation.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/srcWithManifestTestRepo/.github/PSModule.yml Skips fixture site deployment.
tests/srcTestRepo/.github/PSModule.yml Skips fixture site deployment.
docs/content/specification/spec.md Defines push-authorized releases.
docs/content/specification/design.md Updates release architecture.
docs/content/reference/settings.md Documents site and release settings.
docs/content/reference/scenario-matrix.md Revises event/job routing.
docs/content/reference/powershell-module-standard.md Updates pipeline standards.
docs/content/reference/pipeline-stages.md Documents publication ordering.
docs/content/index.md Updates lifecycle overview.
docs/content/guides/versioning-and-releases.md Revises versioning guidance.
docs/content/guides/calling-the-workflow.md Adds push and concurrency guidance.
docs/content/get-started/your-first-release.md Explains push-based releases.
docs/content/get-started/repository-setup.md Updates caller template.
docs/content/get-started/module-bootstrap.md Updates first-release wording.
docs/content/get-started/index.md Updates onboarding flow.
.github/workflows/workflow.yml Revises orchestration and concurrency.
.github/workflows/Workflow-Test-WithManifest.yml Adds push lifecycle dogfooding.
.github/workflows/Workflow-Test-Default.yml Adds push lifecycle dogfooding.
.github/workflows/Release.yml Makes release runs non-cancelling.
.github/workflows/Publish-Module.yml Orders Gallery and GitHub publication.
.github/actions/Resolve-PSModuleVersion/tests/Resolve-PSModuleVersion.Helpers.Tests.ps1 Tests normalized release decisions.
.github/actions/Resolve-PSModuleVersion/src/Resolve-PSModuleVersion.Helpers.psm1 Resolves normalized event context.
.github/actions/Resolve-PSModuleVersion/src/main.ps1 Passes settings into resolution.
.github/actions/Release-PSModule/tests/Release-PSModule.WhatIf.Tests.ps1 Tests direct-push releases.
.github/actions/Release-PSModule/src/release.ps1 Supports push-based release creation.
.github/actions/Release-PSModule/action.yml Adds PR and commit inputs.
.github/actions/Publish-PSModule/src/publish.ps1 Makes PR comments conditional.
.github/actions/Publish-PSModule/action.yml Adds normalized PR input.
.github/actions/Get-PSModuleSettings/tests/Get-PSModuleSettings.Helpers.Tests.ps1 Tests routing and association helpers.
.github/actions/Get-PSModuleSettings/src/Settings.schema.json Adds site publication settings.
.github/actions/Get-PSModuleSettings/src/main.ps1 Normalizes events and release routing.
.github/actions/Get-PSModuleSettings/src/Get-PSModuleSettings.Helpers.psm1 Adds routing and Git helpers.
.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 Uses normalized PR context.
.github/actions/Cleanup-PSModulePrereleases/action.yml Adds normalized PR input.
Suppressed comments (3)

.github/actions/Get-PSModuleSettings/src/main.ps1:325

  • This condition also selects the PR-files path for a default-branch push that was associated with a merged PR. If one push contains earlier commits plus that merge commit, important-file detection examines only the final PR and can incorrectly skip a release for important changes earlier in the push. Restrict this branch to actual PR events so every push uses the full before...after comparison.
    if ($pullRequestContext.Number) {

.github/actions/Get-PSModuleSettings/src/main.ps1:419

  • GitHub's compare response exposes at most 300 changed files. A larger default-branch push can therefore hide an important file after the cap and be classified as non-important, silently suppressing its release. Use a complete local diff or fail explicitly when the API cap is reached, as the initial-tree path already does for truncation.
                $changedFiles = Invoke-GitHubAPI -ApiEndpoint "/repos/$owner/$repo/compare/$beforeCommitSha...$commitSha" -Method GET |
                    Select-Object -ExpandProperty Response |
                    Select-Object -ExpandProperty files |
                    Select-Object -ExpandProperty filename

.github/actions/Get-PSModuleSettings/src/main.ps1:633

  • This now enables AutoCleanup only for the closed-PR run. Consequently the stable-push path passes AutoCleanup: false to the cleanup action even when the user configured it true, so a failed or missed close-event cleanup is never retried after the stable release. Include stable releases in this gate, matching the documented “after a stable default-branch release” behavior.
    $shouldAutoCleanup = $routing.ShouldCleanupEvent -and ($settings.Publish.Module.AutoCleanup -eq $true)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/actions/Get-PSModuleSettings/src/main.ps1
Comment thread .github/actions/Release-PSModule/src/release.ps1
Comment thread .github/workflows/Publish-Module.yml
Comment thread docs/content/guides/versioning-and-releases.md Outdated
Comment thread .github/workflows/workflow.yml Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug

Copy link
Copy Markdown
Member Author

Addressed the three suppressed default-branch push edge cases in 70fcb5a:

  • associated pushes now use the full before...after comparison rather than the final PR file list;
  • compare responses at GitHub's 300-file limit now fail explicitly rather than silently omit files;
  • successful stable releases retry prerelease cleanup when Publish.Module.AutoCleanup is enabled.

Added routing and compare-limit coverage, plus aligned the scenario matrix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug
Marius Storhaug (MariusStorhaug) merged commit 5a11e8e into main Aug 15, 2026
79 checks passed
@MariusStorhaug
Marius Storhaug (MariusStorhaug) deleted the push-main-stable-release branch August 15, 2026 10:32
Marius Storhaug (MariusStorhaug) added a commit that referenced this pull request Sep 2, 2026
A module version that has never been published to the PowerShell Gallery
can now be published. Any first release, and every subsequent new
version, previously failed the Publish-Module stage before the upload
was attempted.

## Fixed: New module versions publish to the PowerShell Gallery

Publishing a version that is not yet on the PowerShell Gallery now
succeeds. The stage checks whether the version already exists so an
interrupted run can resume, and treats an absent version as the expected
result for a new release rather than an error.

Before this change the Publish-Module job failed with the following, and
no module was ever uploaded:

```text
Find-PSResource: Package with name 'MyModule', version '1.3.1' could not be found in repository 'PSGallery'.
Error: Process completed with exit code 1.
```

No repository configuration changes are needed. A workflow run that
previously failed at this point succeeds on re-run.

Resuming an interrupted publication is unchanged: when the version is
already on the Gallery, the run skips the upload and continues to GitHub
release creation.

---
<details>
<summary>Technical details</summary>

- `.github/actions/Publish-PSModule/src/publish.ps1` — the Gallery
existence probe in the `Publish to PSGallery` region ran
`Find-PSResource` with `-ErrorAction Stop`.
`Microsoft.PowerShell.PSResourceGet` raises
`PackageNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource`
when the requested version does not exist, which `-ErrorAction Stop`
turns into a throw, so the probe made a missing version fatal instead of
returning `$null`. The probe now keeps `-ErrorAction Stop` and catches
only `PackageNotFound`, treating that one error as 'not yet published'
and letting the `if ($publishedPackage)` branch decide the outcome.
Every other error stays fatal, so a transient Gallery failure cannot be
misread as 'version absent' and cause a re-upload of a version that
already exists.
- The probe was introduced in #512 to make
Gallery publication idempotent for the default-branch push release path.
That path replaced a `try`/`catch` around `Publish-PSResource`, which is
why the regression reached `main` without an existing test catching it.
-
`.github/actions/Publish-PSModule/tests/Publish-PSModule.Recovery.Tests.ps1`
— the harness could not observe whether publication happened, so its
assertions were vacuous: `Publish-PSResource` was shimmed to set
`$script:publishInvoked`, but `publish.ps1` runs in its own scope via
`&`, so the flag never propagated and stayed `$false` regardless.
Replaced with a hashtable captured by `GetNewClosure()`, which is shared
by reference. A second variant wrote a marker file under
`$env:GITHUB_WORKSPACE`; that is process-wide and races between parallel
Pester runspaces, so the marker could land in another test file's
`TestDrive`. The not-found shim also used
`$PSCmdlet.ThrowTerminatingError(...)`, which ignores `-ErrorAction` and
therefore threw under both `Stop` and `SilentlyContinue` — unable to
distinguish the fix from the defect. It now uses `Write-Error` with the
real `PackageNotFound` error ID, matching how the cmdlet actually
behaves. Added a case asserting a non-`PackageNotFound` lookup failure
stays fatal and does not publish. Each test was verified to fail against
the specific defect it guards.
-
`.github/actions/Release-PSModule/tests/Release-PSModule.WhatIf.Tests.ps1`
and `Publish-PSModule.Recovery.Tests.ps1` — shim teardown used
`Remove-Item -Path function:global:X`. `Set-Item` accepts that path and
creates `X` in the global scope, but `Remove-Item` and `Get-Item` do not
resolve it back, and fail silently rather than erroring, so the cleanup
was a no-op. The shims survived `AfterAll` and shadowed the real
commands for later test files, which is what made `Test-Actions` fail
with `A parameter cannot be found that matches parameter name
'Prerelease'` in `Get-NextPrereleaseNumber`. Teardown now removes by
name.
- Validated end to end in `MariusStorhaug/MariusTestModule` ([PR
#63](MariusStorhaug/MariusTestModule#63)) with
the caller pointed at this branch. A new version published successfully
([run
33597824748](https://github.com/MariusStorhaug/MariusTestModule/actions/runs/33597824748/job/100145585604)),
and re-running the same job with the version present skipped the upload
via the resume path
([re-run](https://github.com/MariusStorhaug/MariusTestModule/actions/runs/33597824748/job/100146802704)).
Both branches of the probe are confirmed against the live Gallery.
- Out of scope, found while reproducing: a `workflow_dispatch` on the
default branch resolves no associated pull request, because pull request
association in `.github/actions/Get-PSModuleSettings/src/main.ps1` is
gated on `$isPush`. A manual recovery run therefore discards the merged
pull request's version label and silently resolves a Patch bump. This is
a separate defect in version resolution and is recorded in the analysis
on #528; it is not addressed here.
- Also out of scope: `.github/workflows/Test-Actions.yml` builds a
Pester configuration with `Run.Parallel` and `Run.Shuffle`, asserts the
options applied, then discards it and creates a fresh
`New-PesterConfiguration` for the actual run. Parallel and shuffle are
validated but never used, which is why the `GITHUB_WORKSPACE` race above
could not surface in CI. The suite now passes both sequentially and
under the intended parallel configuration, so enabling it should be
safe.

| Changed surface | Standards checked | Framework docs checked | Result
|
| --- | --- | --- | --- |
| `.github/actions/Publish-PSModule/src/**` (PowerShell) | Coding
standards, error handling | Publish stage contract | Aligned |
| `.github/actions/Publish-PSModule/tests/**` (Pester) | Pester test
standards | Action test layout | Aligned |
| `.github/actions/Release-PSModule/tests/**` (Pester) | Pester test
standards | Action test layout | Aligned |

</details>

<details>
<summary>Relevant issues (or links)</summary>

- Resolves #528

### Related work

- References #512

</details>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com>
Co-authored-by: Marius Storhaug <Marius.Storhaug@dnb.no>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support running the workflow on push to main, with prerelease-only-during-open-PR model

2 participants