From 4d4e138c5c5aaf6a45a7e88743a46d933055f77c Mon Sep 17 00:00:00 2001 From: fadwen <110697945+fadwen@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:04:00 -0700 Subject: [PATCH] ci(badge): sum the Gallery's per-version downloads into a README badge The Gallery's per-package total is broken (PowerShell/PowerShellGallery #347 and #350): 0 for this module, stuck or wrong for others, and it is what the page and the shields.io powershellgallery/dt badge read. The per-version counts still move. A daily workflow reads every version from the v2 feed, sums VersionDownloadCount and publishes a shields.io endpoint file on the badges branch when the number changes; the README badge reads that file. Other module repositories can call the workflow with their package name. --- .github/workflows/gallery-downloads.yml | 102 ++++++++++++++++++++++++ CHANGELOG.md | 8 ++ README.md | 1 + 3 files changed, 111 insertions(+) create mode 100644 .github/workflows/gallery-downloads.yml diff --git a/.github/workflows/gallery-downloads.yml b/.github/workflows/gallery-downloads.yml new file mode 100644 index 0000000..d668959 --- /dev/null +++ b/.github/workflows/gallery-downloads.yml @@ -0,0 +1,102 @@ +name: Gallery downloads badge + +# The PowerShell Gallery's per-package total (DownloadCount, which the page and the shields.io +# powershellgallery/dt badge read) is broken: 0 for this module, and stuck or wrong for others +# (PowerShell/PowerShellGallery#347, #350). The per-version counts still move, so this sums them +# and publishes a shields.io endpoint badge on the badges branch. The README reads it through +# https://img.shields.io/endpoint?url=https://raw.githubusercontent.com///badges/downloads.json +# +# Reusable: another module's repository calls this workflow with its package name and gets the +# same badge on its own badges branch. The schedule below is this repository's; a caller sets +# its own. + +on: + schedule: + - cron: '17 6 * * *' + workflow_dispatch: + workflow_call: + inputs: + package: + description: The Gallery package id. Defaults to the repository name. + type: string + required: false + label: + description: The badge's left-hand text. + type: string + required: false + default: Gallery downloads + +permissions: + contents: write + +jobs: + badge: + name: Sum the version downloads + runs-on: ubuntu-latest + steps: + - name: Sum VersionDownloadCount over every version + id: sum + shell: pwsh + env: + PACKAGE: ${{ inputs.package || github.event.repository.name }} + LABEL: ${{ inputs.label || 'Gallery downloads' }} + run: | + $ErrorActionPreference = 'Stop' + # The v2 feed is OData Atom. Invoke-RestMethod hands back the entries but not the paging + # link, so the pages are read as XML; a package with many versions spans several. + $uri = "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='$env:PACKAGE'" + $versions = 0 + $total = 0L + $brokenTotal = $null + while ($uri) { + $xml = [xml](Invoke-WebRequest -Uri $uri -UseBasicParsing).Content + foreach ($entry in @($xml.feed.entry)) { + $versions++ + $total += [long]$entry.properties.VersionDownloadCount.'#text' + $brokenTotal = $entry.properties.DownloadCount.'#text' + } + $uri = @($xml.feed.link | Where-Object rel -eq 'next' | Select-Object -First 1).href + } + if ($versions -eq 0) { throw "The Gallery returned no versions for '$env:PACKAGE'." } + Write-Host "$env:PACKAGE : $versions version(s), $total downloads summed (the Gallery's own total says $brokenTotal)" + + $badge = [ordered]@{ + schemaVersion = 1 + label = $env:LABEL + message = $total.ToString('N0', [cultureinfo]::InvariantCulture) + color = 'blue' + cacheSeconds = 3600 + } | ConvertTo-Json -Compress + Set-Content -Path downloads.json -Value $badge -NoNewline + "total=$total" >> $env:GITHUB_OUTPUT + "versions=$versions" >> $env:GITHUB_OUTPUT + + # The badges branch holds nothing but the badge files, with no history in common with main, + # and gains a commit only when a number changed. + - name: Publish to the badges branch + shell: bash + env: + TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + TOTAL: ${{ steps.sum.outputs.total }} + VERSIONS: ${{ steps.sum.outputs.versions }} + run: | + set -euo pipefail + mkdir badges && cd badges + git init -q -b badges + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + remote="https://x-access-token:${TOKEN}@github.com/${REPO}.git" + git remote add origin "$remote" + if git fetch -q origin badges 2>/dev/null; then + git reset -q --hard origin/badges + fi + if [ -f downloads.json ] && cmp -s downloads.json ../downloads.json; then + echo "Badge unchanged at ${TOTAL} downloads; nothing to publish." + exit 0 + fi + cp ../downloads.json downloads.json + git add downloads.json + git commit -q -m "badge: ${TOTAL} downloads across ${VERSIONS} versions" + git push -q origin badges + echo "Published ${TOTAL} downloads to the badges branch." diff --git a/CHANGELOG.md b/CHANGELOG.md index e20c54c..41e9b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ release notes. ## [Unreleased] +### Added + +- A Gallery downloads badge in the README that sums the per-version download counts, because the + Gallery's own total is broken (PowerShell/PowerShellGallery#347 and #350: 0 for this module, stuck + or wrong for others). `.github/workflows/gallery-downloads.yml` reads every version from the v2 + feed daily and publishes a shields.io endpoint file on the `badges` branch when the number moves; + it is reusable from another module's repository through `workflow_call` with the package name. + ### Changed - The `help` job in `quality-gates.yml`, the one that runs on Linux, also analyzes a folder of two diff --git a/README.md b/README.md index c272610..658aee6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # IntuneScriptLab [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/IntuneScriptLab?style=flat-square&logo=powershell&label=Gallery)](https://www.powershellgallery.com/packages/IntuneScriptLab) +[![Gallery downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffadwen%2FIntuneScriptLab%2Fbadges%2Fdownloads.json&style=flat-square)](https://www.powershellgallery.com/packages/IntuneScriptLab) [![Quality Gates](https://img.shields.io/github/actions/workflow/status/fadwen/IntuneScriptLab/quality-gates.yml?branch=main&style=flat-square&label=quality%20gates)](https://github.com/fadwen/IntuneScriptLab/actions/workflows/quality-gates.yml) [![PowerShell 5.1](https://img.shields.io/badge/PowerShell-5.1+-blue?style=flat-square&logo=powershell)](https://github.com/PowerShell/PowerShell) [![Pester](https://img.shields.io/badge/Tested_with-Pester_6-green?style=flat-square)](https://pester.dev)