From f00dad02babec59c93a24095751e4ca44ea83721 Mon Sep 17 00:00:00 2001 From: Gauvain Date: Mon, 15 Jun 2026 00:24:44 +0200 Subject: [PATCH] ci(issue-form): auto-populate version dropdown from GitHub releases (#1641) --- .github/ISSUE_TEMPLATE/issue_report.yml | 5 +- .github/workflows/update-issue-form.yml | 121 ++++++++++++++--------- scripts/update-issue-form.mjs | 122 ++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 scripts/update-issue-form.mjs diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index af86644d..365afca2 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -75,10 +75,13 @@ body: id: version attributes: label: Streamyfin Version - description: What version of Streamyfin are you using? + description: What version of Streamyfin are you running? On a TestFlight or development build, choose "TestFlight/Development build" and include the exact version string shown in the app's Settings. options: - 0.54.1 - 0.51.0 + - 0.47.1 + - 0.30.2 + - 0.28.0 - Older - TestFlight/Development build validations: diff --git a/.github/workflows/update-issue-form.yml b/.github/workflows/update-issue-form.yml index a23ecdf2..8b5af9c8 100644 --- a/.github/workflows/update-issue-form.yml +++ b/.github/workflows/update-issue-form.yml @@ -1,67 +1,102 @@ -name: ๐Ÿ› Update Bug Report Template +name: ๐Ÿ› Update Issue Form Versions on: release: - types: [published] # Run on every published release on any branch + # Only full releases populate the dropdown (no drafts/prereleases). + types: [released] + schedule: + - cron: "0 3 * * 1" # Weekly safety net (Mondays 03:00 UTC) in case a release event was missed + workflow_dispatch: +# Fixed group so a release event and the weekly cron can't race on the same +# ci/update-issue-form branch โ€” runs queue instead of force-pushing over each other. concurrency: - group: update-issue-form-${{ github.event.release.tag_name || github.run_id }} - cancel-in-progress: true + group: update-issue-form + cancel-in-progress: false + +permissions: + contents: read jobs: - update-bug-report: + update-issue-form: + name: ๐Ÿ”ข Populate version dropdown + runs-on: ubuntu-24.04 permissions: contents: write pull-requests: write - issues: write - runs-on: ubuntu-24.04 - steps: - name: ๐Ÿ“ฅ Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - name: "๐ŸŸข Setup Node.js" - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '24.x' - cache: 'npm' + # On `release` events GITHUB_SHA is the tagged commit โ€” without this the + # script would regenerate the form from the tag's (stale) copy and the bot + # PR would revert any form edits made on develop since that release. + ref: develop - - name: ๐Ÿ” Extract minor version from app.json - id: minor - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # main + - name: ๐Ÿž Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: - result-encoding: string - script: | - const fs = require('fs-extra'); - const semver = require('semver'); - const content = fs.readJsonSync('./app.json'); - const version = content.expo.version; - const minorVersion = semver.minor(version); - return minorVersion.toString(); + bun-version: latest - - name: ๐Ÿ“ Update bug report version - uses: ShaMan123/gha-populate-form-version@be012141ca560dbb92156e3fe098c46035f6260d #v2.0.5 - with: - semver: '^0.${{ steps.minor.outputs.result }}.0' - dry_run: no-push + - name: ๐Ÿ”ข Populate version dropdown from GitHub releases + id: populate + run: bun scripts/update-issue-form.mjs + env: + GH_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} - - name: โš™๏ธ Update bug report node version dropdown - uses: ShaMan123/gha-populate-form-version@be012141ca560dbb92156e3fe098c46035f6260d #v2.0.5 - with: - dropdown: _node_version - package: node - semver: '>=24.0.0' - dry_run: no-push - - - name: ๐Ÿ“ฌ Commit and create pull request + - name: ๐Ÿ“ฌ Create pull request + id: cpr uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: - add-paths: .github/ISSUE_TEMPLATE/bug_report.yml - branch: ci-update-bug-report + add-paths: .github/ISSUE_TEMPLATE/issue_report.yml + branch: ci/update-issue-form base: develop delete-branch: true labels: โš™๏ธ ci, ๐Ÿค– github-actions - title: 'chore(): Update bug report template to match release version' + commit-message: "chore: update issue form version dropdown" + title: "chore: update issue form version dropdown" + # Follows .github/pull_request_template.md so the bot PR isn't flagged by PR validation. body: | - Automated update to `.github/ISSUE_TEMPLATE/bug_report.yml` - Triggered by workflow run [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + # ๐Ÿ“ฆ Pull Request + + ## ๐Ÿ“ Description + + Automated update of the **Streamyfin Version** dropdown in `.github/ISSUE_TEMPLATE/issue_report.yml`, populated from the latest published GitHub releases by `scripts/update-issue-form.mjs`. + + **Version dropdown now lists:** ${{ steps.populate.outputs.versions }} + + Triggered by `${{ github.event_name }}`${{ github.event.release.tag_name && format(' โ€” release {0}', github.event.release.tag_name) || '' }} ยท [run ${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}). + + ## ๐Ÿท๏ธ Ticket / Issue + + N/A โ€” automated maintenance. + + ### ๐Ÿ–ผ๏ธ Screenshots / GIFs (if UI) + + N/A โ€” issue-template metadata only, no app UI. + + ## โœ… Checklist + + - [x] Iโ€™ve read the [contribution guidelines](CONTRIBUTING.md) + - [x] Verified that changes behave as expected for all platforms + - [x] Code passes lint/formatting and type checks (`tsc`/`biome`) + - [x] No secrets, hardcoded credentials, or private config files are included + - [x] I've declared if AI was used to assist with this PR (by uncommenting the line at the bottom, or not) + + ## ๐Ÿ” Testing Instructions + + N/A โ€” generated by CI from published releases; review the dropdown diff in `issue_report.yml`. + + - name: ๐Ÿ”€ Enable auto-merge + if: steps.cpr.outputs.pull-request-operation == 'created' + env: + GH_TOKEN: ${{ github.token }} + # Known limitation: PRs created with GITHUB_TOKEN don't trigger CI workflows + # (GitHub anti-recursion), so the required checks stay "Expected" until a + # maintainer kicks them (close/reopen the PR, or push an empty commit). + # Auto-merge is still worth enabling: once checks run and reviews land, + # the PR merges itself. + run: | + gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}" \ + || echo "::warning::Could not enable auto-merge โ€” enable 'Allow auto-merge' in repo settings (and branch protection); merge the PR manually for now." diff --git a/scripts/update-issue-form.mjs b/scripts/update-issue-form.mjs new file mode 100644 index 00000000..d7bc8192 --- /dev/null +++ b/scripts/update-issue-form.mjs @@ -0,0 +1,122 @@ +#!/usr/bin/env bun +/** + * Populates the "Streamyfin Version" dropdown in the issue report form with the + * latest GitHub releases. Run by the "Update Issue Form Versions" workflow on + * release events + a weekly cron (and manually via workflow_dispatch). + * + * Source: published, non-draft, non-prerelease GitHub releases, newest first. + * Non-version sentinels (e.g. "older", "TestFlight/Development build") are + * preserved at the end of the list. + * + * Usage: + * bun scripts/update-issue-form.mjs # rewrite the form in place + * ISSUE_FORM_LIMIT=8 bun scripts/update-issue-form.mjs + * bun scripts/update-issue-form.mjs --dry-run # print the new options, don't write + * + * Env: GITHUB_REPOSITORY (owner/repo), GH_TOKEN/GITHUB_TOKEN (for gh, provided in CI). + */ + +import { execFileSync } from "node:child_process"; +import { + appendFileSync, + readFileSync as read, + writeFileSync as write, +} from "node:fs"; + +const FORM = ".github/ISSUE_TEMPLATE/issue_report.yml"; +const DROPDOWN_ID = "version"; // the `id:` of the dropdown to populate +const parsedLimit = Number.parseInt(process.env.ISSUE_FORM_LIMIT ?? "", 10); +const LIMIT = + Number.isInteger(parsedLimit) && parsedLimit > 0 ? parsedLimit : 5; +const REPO = process.env.GITHUB_REPOSITORY || "streamyfin/streamyfin"; +const DRY = process.argv.includes("--dry-run"); + +// Matches "0.54.1" and prerelease/beta tags like "0.54.0-beta.1". +const isVersion = (s) => /^\d+\.\d+/.test(s.trim()); + +// 1. Fetch the latest published releases (newest first) โ€” drafts and prereleases +// aren't a full release users run, so they don't belong in the dropdown. +const raw = execFileSync( + "gh", + [ + "release", + "list", + "--repo", + REPO, + "--exclude-drafts", + "--exclude-pre-releases", + "--limit", + String(LIMIT), + "--json", + "tagName", + "--jq", + ".[].tagName", + ], + // Bounded timeout so a stuck gh process fails the job fast instead of + // holding the workflow open until the job-level timeout. + { encoding: "utf8", timeout: 30_000 }, +); +const seen = new Set(); +const versions = []; +for (const tag of raw.split("\n")) { + if (!tag) continue; + const ver = tag.trim().replace(/^v/, ""); + if (!isVersion(ver) || seen.has(ver)) continue; + seen.add(ver); + versions.push(ver); +} + +if (!versions.length) { + console.error("No release versions found โ€” leaving the form untouched."); + process.exit(1); +} + +// 2. rewrite the dropdown options, preserving non-version sentinels +// (e.g. "older", "TestFlight/Development build") at the end of the list. +const lines = read(FORM, "utf8").split("\n"); +const idIdx = lines.findIndex((l) => + l.match(new RegExp(`^\\s*id:\\s*${DROPDOWN_ID}\\s*$`)), +); +if (idIdx === -1) + throw new Error(`dropdown id: ${DROPDOWN_ID} not found in ${FORM}`); +const optIdx = lines.findIndex( + (l, i) => i > idIdx && /^\s*options:\s*$/.test(l), +); +if (optIdx === -1) + throw new Error(`options: not found after id: ${DROPDOWN_ID}`); + +const itemIndent = lines[optIdx].match(/^\s*/)[0] + " "; // options items are nested one level deeper +let end = optIdx + 1; +const sentinels = []; +while (end < lines.length && /^\s*-\s+/.test(lines[end])) { + const val = lines[end].replace(/^\s*-\s+/, ""); + if (!isVersion(val)) sentinels.push(val); + end++; +} + +const newOptions = [...versions, ...sentinels].map( + (v) => `${itemIndent}- ${v}`, +); +const updated = [ + ...lines.slice(0, optIdx + 1), + ...newOptions, + ...lines.slice(end), +].join("\n"); + +console.log( + `Versions: ${versions.join(", ")}${sentinels.length ? ` | kept: ${sentinels.join(", ")}` : ""}`, +); +if (DRY) { + console.log("--dry-run: not writing."); +} else { + write(FORM, updated); + console.log(`Updated ${FORM}.`); +} + +// Expose the resulting list for the workflow (PR description). +if (process.env.GITHUB_OUTPUT) { + appendFileSync( + process.env.GITHUB_OUTPUT, + `versions=${versions.join(", ")}\n`, + ); +}