From b0bfa50b0b9ef9deac2e4e9d4ccea0e4229d289a Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Mon, 13 Jul 2026 19:00:50 +0100 Subject: [PATCH 1/2] [MS-1505] Add release dashboard promotion workflows - Add update-release-stage.yml: manual "Promote Release Stage" workflow to record that a version has reached Beta (production_ready), Delivery Testing, or Production. Looks up the GitHub Release for the version to find the APK download URL automatically. The version input is a choice dropdown seeded with the 15 most recent published release tags, wrapped in marker comments for automated regeneration. - Add reusable-update-dashboard.yml: reusable workflow that writes the promoted version's info into status.json on gh-pages and commits/pushes the change. - Add sync-release-stage-versions.yml: workflow triggered on `release: published` (and manual dispatch) that regenerates the version dropdown in update-release-stage.yml from the live list of release tags and commits the change back to main, keeping the list in sync. - Avoid script injection by never interpolating workflow_dispatch inputs directly into `run:` blocks; all user-controlled values are passed via `env:` and referenced as shell variables instead. --- .../workflows/reusable-update-dashboard.yml | 66 +++++++++++++++++ .../workflows/sync-release-stage-versions.yml | 54 ++++++++++++++ .github/workflows/update-release-stage.yml | 71 +++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 .github/workflows/reusable-update-dashboard.yml create mode 100644 .github/workflows/sync-release-stage-versions.yml create mode 100644 .github/workflows/update-release-stage.yml diff --git a/.github/workflows/reusable-update-dashboard.yml b/.github/workflows/reusable-update-dashboard.yml new file mode 100644 index 0000000000..d1b740ac2e --- /dev/null +++ b/.github/workflows/reusable-update-dashboard.yml @@ -0,0 +1,66 @@ +name: "[Reusable] Update Release Dashboard" + +on: + workflow_call: + inputs: + environment: + description: "Target environment: production_ready | delivery_testing | production" + type: string + required: true + version: + description: "Version name (e.g. 2026.2.0)" + type: string + required: true + apk_url: + description: "Direct APK download URL" + type: string + required: false + default: "" + release_url: + description: "GitHub Release page URL" + type: string + required: false + default: "" + +permissions: + contents: write + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v6 + with: + ref: gh-pages + + - name: Update status.json + env: + ENVIRONMENT: ${{ inputs.environment }} + VERSION: ${{ inputs.version }} + APK_URL: ${{ inputs.apk_url }} + RELEASE_URL: ${{ inputs.release_url }} + run: | + UPDATED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + jq \ + --arg env "$ENVIRONMENT" \ + --arg version "$VERSION" \ + --arg updated_at "$UPDATED_AT" \ + --arg apk_url "$APK_URL" \ + --arg release_url "$RELEASE_URL" \ + '.[$env] = {version: $version, updated_at: $updated_at, apk_url: $apk_url, release_url: $release_url}' \ + status.json > status.json.tmp + mv status.json.tmp status.json + + - name: Commit and push + env: + ENVIRONMENT: ${{ inputs.environment }} + VERSION: ${{ inputs.version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add status.json + git diff --cached --quiet && echo "No changes" && exit 0 + git commit -m "dashboard: update ${ENVIRONMENT} → ${VERSION}" + git push diff --git a/.github/workflows/sync-release-stage-versions.yml b/.github/workflows/sync-release-stage-versions.yml new file mode 100644 index 0000000000..7d220ae263 --- /dev/null +++ b/.github/workflows/sync-release-stage-versions.yml @@ -0,0 +1,54 @@ +name: "Sync Release Stage Version Dropdown" +# Keeps the `version` choice list in update-release-stage.yml in sync with actual +# GitHub Release tags, since workflow_dispatch choice options must be static and +# can't be populated dynamically at trigger time. + +on: + release: + types: [published] + workflow_dispatch: {} + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Regenerate version list + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + WORKFLOW_FILE=".github/workflows/update-release-stage.yml" + OPTIONS_FILE="$(mktemp)" + LIMIT=15 + + # Fetch published (non-draft) release tags, newest first by semantic version. + gh release list --repo "${{ github.repository }}" --limit 60 \ + --json tagName,isDraft \ + --jq '.[] | select(.isDraft==false) | .tagName' \ + | sed 's/^v//' \ + | sort -t. -k1,1nr -k2,2nr -k3,3nr \ + | head -n "$LIMIT" \ + | sed 's/^/ - /' > "$OPTIONS_FILE" + + # Replace the block between the marker comments with the freshly generated options. + awk -v optfile="$OPTIONS_FILE" ' + /# AUTO-GENERATED-VERSIONS-START/ { print; while ((getline line < optfile) > 0) print line; skip=1; next } + /# AUTO-GENERATED-VERSIONS-END/ { skip=0 } + skip != 1 { print } + ' "$WORKFLOW_FILE" > "$WORKFLOW_FILE.tmp" + mv "$WORKFLOW_FILE.tmp" "$WORKFLOW_FILE" + rm -f "$OPTIONS_FILE" + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add .github/workflows/update-release-stage.yml + git diff --cached --quiet && echo "No changes" && exit 0 + git commit -m "chore: sync release stage version dropdown" + git push diff --git a/.github/workflows/update-release-stage.yml b/.github/workflows/update-release-stage.yml new file mode 100644 index 0000000000..f6ddf5b283 --- /dev/null +++ b/.github/workflows/update-release-stage.yml @@ -0,0 +1,71 @@ +name: "Promote Release Stage" +# Manually record that a version has reached Beta (Production Ready), Delivery Testing, or Production. +# Looks up the GitHub Release for that version to find the APK download URL automatically. + +on: + workflow_dispatch: + inputs: + stage: + description: "Stage to promote to" + type: choice + required: true + options: + - production_ready + - delivery_testing + - production + version: + description: "Version to promote" + type: choice + required: true + options: + # AUTO-GENERATED-VERSIONS-START (kept in sync by .github/workflows/sync-release-stage-versions.yml) + - 2026.2.1 + - 2026.2.0 + - 2026.1.0 + - 2025.4.2 + - 2025.4.0 + - 2025.3.1 + - 2025.3.0 + - 2025.2.1 + - 2025.2.0 + - 2025.1.0 + - 2024.2.2 + - 2024.2.1 + - 2024.2.0 + - 2024.1.5 + - 2024.1.4 + # AUTO-GENERATED-VERSIONS-END + +jobs: + resolve-release-info: + runs-on: ubuntu-latest + outputs: + apk_url: ${{ steps.info.outputs.apk_url }} + release_url: ${{ steps.info.outputs.release_url }} + + steps: + - name: Resolve GitHub Release info + id: info + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + run: | + TAG="v${VERSION}" + RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/$TAG" + echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT" + + APK_URL=$(gh release view "$TAG" \ + --repo "${{ github.repository }}" \ + --json assets \ + --jq '[.assets[] | select(.name | endswith(".apk")) | .browserDownloadUrl] | first // ""' \ + 2>/dev/null || echo "") + echo "apk_url=$APK_URL" >> "$GITHUB_OUTPUT" + + update-dashboard: + needs: resolve-release-info + uses: ./.github/workflows/reusable-update-dashboard.yml + with: + environment: ${{ inputs.stage }} + version: ${{ inputs.version }} + apk_url: ${{ needs.resolve-release-info.outputs.apk_url }} + release_url: ${{ needs.resolve-release-info.outputs.release_url }} From bb7f41561850d2d37d5d09f5750689ca3335a48b Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Tue, 14 Jul 2026 10:39:37 +0100 Subject: [PATCH 2/2] [MS-1505] Address PR review comments on release dashboard workflows - Grant contents:write permission in update-release-stage.yml so the reusable update-dashboard workflow can push to gh-pages - Checkout the default branch explicitly in sync-release-stage-versions.yml since release:published triggers run on a detached tag ref - Add a concurrency group to reusable-update-dashboard.yml to serialize concurrent updates to the shared gh-pages status.json --- .github/workflows/reusable-update-dashboard.yml | 4 ++++ .github/workflows/sync-release-stage-versions.yml | 2 ++ .github/workflows/update-release-stage.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/reusable-update-dashboard.yml b/.github/workflows/reusable-update-dashboard.yml index d1b740ac2e..15c01ade6e 100644 --- a/.github/workflows/reusable-update-dashboard.yml +++ b/.github/workflows/reusable-update-dashboard.yml @@ -25,6 +25,10 @@ on: permissions: contents: write +concurrency: + group: update-release-dashboard-gh-pages + cancel-in-progress: false + jobs: update: runs-on: ubuntu-latest diff --git a/.github/workflows/sync-release-stage-versions.yml b/.github/workflows/sync-release-stage-versions.yml index 7d220ae263..297ad3bf7d 100644 --- a/.github/workflows/sync-release-stage-versions.yml +++ b/.github/workflows/sync-release-stage-versions.yml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + ref: ${{ github.event.repository.default_branch }} - name: Regenerate version list env: diff --git a/.github/workflows/update-release-stage.yml b/.github/workflows/update-release-stage.yml index f6ddf5b283..4fade6023b 100644 --- a/.github/workflows/update-release-stage.yml +++ b/.github/workflows/update-release-stage.yml @@ -36,6 +36,9 @@ on: - 2024.1.4 # AUTO-GENERATED-VERSIONS-END +permissions: + contents: write + jobs: resolve-release-info: runs-on: ubuntu-latest