Skip to content

⚙️ [Maintenance]: Pinned PowerShell Gallery module versions kept current automatically - #156

Open
Marius Storhaug (MariusStorhaug) wants to merge 8 commits into
mainfrom
maintenance-136-gallery-pin-updates
Open

⚙️ [Maintenance]: Pinned PowerShell Gallery module versions kept current automatically#156
Marius Storhaug (MariusStorhaug) wants to merge 8 commits into
mainfrom
maintenance-136-gallery-pin-updates

Conversation

@MariusStorhaug

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

Copy link
Copy Markdown
Member

Pinned PowerShell Gallery module versions now move on their own. The Pester version this repository's test suites run against is pinned exactly and verified by module GUID, which is the right posture for a CI pipeline — but only while something keeps that pin current. Dependabot has no PowerShell Gallery ecosystem, so nothing ever did, and the pin could only drift further behind the fixes Pester ships.

New: pinned Gallery versions are raised by an automated pull request

A workflow checks the Gallery for a newer release inside the range the test suites declare, and opens a labelled pull request when it finds one. Nothing changes about how tightly the pin is set — exact plus GUID stays exactly as it was, and only its movement is new. The update is reviewed and gated like any other change: a bump that breaks the suite turns the Test check red before it can merge.

The check runs weekly, on demand, and on every push to main. The last of those is deliberate: GitHub silently disables a scheduled workflow after 60 days of inactivity in a public repository, and a workflow that has stopped running looks exactly like one that ran and found nothing to do.

New: the gap is written down for every other repository

The Dependency Updates capability now records that the PowerShell Gallery is an ecosystem the platform updater does not cover, the pattern that fills the gap, how to notice if the schedule lapses, and the steps another repository follows to adopt it. It says plainly that the gap is Dependabot's, not a preference of this organization — so if the platform ever ships that ecosystem, the right response is to delete the bespoke updater and add a package-ecosystem entry.


Technical details

Decision. Of the three candidates in #136, the first was chosen — a scheduled workflow here, with the logic in a parameterised script so promoting it later is a move rather than a rewrite. The decision and the evidence are recorded in the issue's Technical decisions section.

  • Declaring the dependency for an existing updater was ruled out by checking rather than assuming: dependabot/dependabot-core has no PowerShell ecosystem directory, and Renovate's lib/modules/datasource has no PowerShell Gallery datasource. Renovate could be coerced via a customManagers regex over the nuget datasource, but that is bespoke automation plus a second updater and an org-level app install.
  • A reusable action was judged premature: a code search across MSXOrg for RequiredVersion returns exactly one pin — this repository's. PSModule/Invoke-Pester uses NuGet ranges, not exact pins, so it is not a waiting consumer. GitHub Actions → "Start local; promote when it is reused" is explicit about not paying for a shared release surface before there is a second consumer.

Files.

  • .github/scripts/Update-GalleryModulePin.ps1 — parameterised by module name, target file, pin pattern (a regex with a version capture group), allowed range, and -GalleryUri. Only the captured group is rewritten; byte-order mark and line endings are preserved, and the GUID is never touched. A pattern matching nothing, a pattern matching several places, and an unreachable Gallery are all hard failures, so a broken lookup can never read as "already up to date".
  • .github/workflows/Update-ModulePin.ymlpermissions: {} floor, contents: write + pull-requests: write on the job, idempotent against an already-open pull request or an existing branch, and no auto-merge because identity-plus-exact is the one track Dependencies says is never auto-merged.
  • tests/Update-GalleryModulePin.Tests.ps1 — 10 cases against an in-process HttpListener on a loopback port.
  • src/docs/Capabilities/dependency-updates/design.md — extended rather than adding a page, so no src/zensical.toml nav entry was needed and the generated indexes stay valid.

The pin stays in Invoke-PesterSuite.ps1. A single-key data file would be a dependency manifest in all but name, which #136's non-goals forbid, and a parameter default is no harder to rewrite. -PinPattern is what makes the script indifferent to where a pin lives.

Verification.

What How Result
Opens a PR when a newer version exists Ran the workflow's own steps against the real Gallery with the pin set back to 6.0.0 Opened #157 — one file, one line, dependencies + powershell + update:patch + Maintenance, GUID untouched. Closed in the same session, branches deleted.
Does nothing when it does not Same commands with the pin at 6.0.1 updated=false, working tree clean, open pull-request count unchanged at 12
Does not open a duplicate Re-ran the step with the PR already open "already open. Nothing to do", still exactly one PR
A breaking bump fails Test Commit 71007fe pinned Pester to 5.7.1 Run 31266574992 — Test failed, every suite's #Requires rejected it. Reverted in c6ea08e.
Fires, stays quiet, ceiling, prerelease, paging, identity, hard failures tests/Update-GalleryModulePin.Tests.ps1 10 cases, offline, ~5s
Full suite .github/scripts/Invoke-PesterSuite.ps1 64 passed, 0 failed
PowerShell lint Invoke-ScriptAnalyzer with .github/linters/.powershell-psscriptanalyzer.psd1 Clean on both new files
Workflow security zizmor Clean across both workflows
Docs Update-DocumentationIndex.ps1 -Check, Test-DocumentationLink.ps1, markdownlint Pass; 114 files scanned, all links resolve

Two bugs were found by this work and fixed in it: a Gallery reporting exactly one version unrolled to a bare [version] and crashed under Set-StrictMode; and the new tests relied on member enumeration over the script's mixed output, which passed in isolation and failed under the full suite. zizmor's artipacked audit also flagged the updater's checkout persisting credentials — rather than suppress it, the checkout now sets persist-credentials: false and the single push that needs a token supplies it itself.

Labels. dependencies, powershell, update:major, update:minor and update:patch did not exist in this repository and were created once with gh label create; the workflow applies them but does not create them.

Branch name deviation. maintenance-136-gallery-pin-updates rather than maintenance/136-…. The app's rename_branch tool strips / and fires once per session, so the <type>/<issue>-<slug> form in Branching and Merging is unreachable from an app session. Nothing in CI keys off the branch name.

Standards and framework alignment

Changed surface Standards checked Framework docs checked Result
.github/scripts/** (PowerShell) PowerShell (Scripts, Functions, Naming, Documentation), Dependencies Dependency Updates design Aligned
.github/workflows/** GitHub Actions (least-privilege permissions, untrusted input, concurrency, pinned SHAs and runner) Dependency Updates design Aligned
tests/** Testing, PowerShell Testing Aligned
src/docs/** Markdown, Documentation Model Capability spec-and-design shape Aligned

Implementation plan progress. All seven items of #136's plan are complete and ticked.

Issue convergence sweep. Scope: open issues touching .github/ dependency automation and the Dependency Updates capability. #136 is the only one this diff satisfies; no additional closing keywords are used.

Related issues

Dependabot has no PowerShell Gallery ecosystem, so nothing on the platform
moves a pin like the Pester one in Invoke-PesterSuite.ps1. The script reads the
pinned version through a caller-supplied pattern, asks the Gallery which
versions exist, and rewrites the pin to the newest one inside the allowed range.

The pattern must match exactly once and an unreachable Gallery is an error, so
a broken lookup can never be mistaken for 'already up to date'.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug Marius Storhaug (MariusStorhaug) added the Maintenance Internal maintenance change label Aug 8, 2026
…et otherwise

The Gallery is served from an in-process HttpListener on a loopback port, so
both outcomes are demonstrated without waiting for Pester to release and
without network access.

Also fixes a bug the suite found: a feed reporting exactly one version unrolled
to a bare [version], which has no Count under Set-StrictMode, so a
single-version module would have crashed the check.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Runs on a schedule, on demand, and on every push to main. The push trigger is
there because GitHub silently disables a scheduled workflow after 60 days of
inactivity in a public repository: it does not cover the quiet window, but it
guarantees the first push after one re-checks the pin.

The pull request is labelled and left for a human, because identity-plus-exact
pins are the one track the Dependencies standard says is never auto-merged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Records that the gap is Dependabot having no PowerShell Gallery ecosystem
rather than a choice made here, so if the platform ever ships one the right
response is to delete this and add a package-ecosystem entry.

Names the risk that a scheduled workflow is disabled silently after 60 days of
inactivity in a public repository, and what distinguishes that from a run that
found nothing to do.

Also settles the analyzer findings on the new files and stops the new tests
depending on member enumeration, which broke them under the full suite.

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

Reverted in the next commit. 5.7.1 is outside the range the suites' #Requires
lines declare, which is what a bad bump would look like.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reverts the temporary 5.7.1 pin now that run 31266574992 has shown a breaking
bump fails the Test check.

The updater's checkout no longer persists the token into .git/config, which
zizmor's artipacked audit flagged. The one push that needs credentials supplies
them itself, so no later step in the job can read them.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The existing-pull-request guard looked only at open ones, so a version a
reviewer had closed to decline would be proposed again on the next run, every
week, once the branch was gone. Checking every state means closing is how you
say no, and the bot listens.

The guard stays per version because the branch name carries it: declining 6.1.0
says nothing about 6.1.1.

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

Maintenance Internal maintenance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep pinned PowerShell Gallery module versions current without Dependabot

1 participant