Skip to content

Update versions

Update versions #108

name: Update versions
# Refresh the SDK versions shown on the product family pages from the canonical feed
# https://products.groupdocs.com/versions.json. Upstream regenerates that file twice a day
# (06:00 + 18:00 UTC via its own GitHub Action), so we run +10 minutes later.
#
# scripts/update_versions.py trims the feed to just the fields we render (version + release-notes URL)
# and sorts keys, so data/versions.json only changes when a version actually moves (not on the
# upstream's generatedAt/downloads churn).
#
# Single-branch model: the data file is committed once, to `main`, and staging is redeployed.
# Production is NOT redeployed by default — see AUTO_PROMOTE_VERSIONS below.
#
# Note this job commits with GITHUB_TOKEN, whose pushes deliberately do not trigger other
# workflows. That is why it calls the deploy jobs itself, and why a deploy marker in its commit
# message would be inert — do not add one.
on:
schedule:
- cron: '10 6,18 * * *' # 06:10 + 18:10 UTC (+10 min after upstream publishes versions.json)
workflow_dispatch:
inputs:
force:
description: Redeploy even if versions did not change
type: boolean
default: false
environment:
description: When forcing, which environment(s) to redeploy
type: choice
options: [both, production, staging]
default: staging
permissions:
contents: write
concurrency:
group: update-versions
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
outputs:
do_staging: ${{ steps.sync.outputs.do_staging }}
do_prod: ${{ steps.sync.outputs.do_prod }}
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Fetch + transform versions.json
run: python3 scripts/update_versions.py --out /tmp/versions.json
- name: Commit data/versions.json on main
id: sync
env:
FORCE: ${{ github.event.inputs.force || 'false' }}
FORCE_ENV: ${{ github.event.inputs.environment || 'staging' }}
# Repo variable, default off. When true, a version change also publishes to production.
# Leave it off unless you want the site continuously deployed: `main` is now the single
# branch, so a production run here publishes EVERY product's current content, not just
# the version strings.
AUTO_PROMOTE: ${{ vars.AUTO_PROMOTE_VERSIONS || 'false' }}
run: |
set -euo pipefail
git config user.name "groupdocs-version-bot"
git config user.email "groupdocs-version-bot@users.noreply.github.com"
git fetch origin main
git checkout -B main origin/main
cp /tmp/versions.json data/versions.json
changed=false
if git diff --quiet -- data/versions.json; then
echo "no version changes"
else
git add data/versions.json
git commit -m "Update SDK versions from products.groupdocs.com/versions.json"
# `main` is now the only branch, so it also absorbs every product sync. Retry on a
# racing push rather than failing (or, worse, silently losing) the update.
pushed=false
for i in 1 2 3 4 5; do
if git push origin main; then pushed=true; break; fi
sleep $(( i * 5 ))
git fetch origin main
git rebase origin/main
done
if [ "$pushed" != true ]; then
echo "::error::could not push data/versions.json after 5 attempts"
exit 1
fi
changed=true
echo "versions updated"
fi
do_staging=$changed
do_prod=false
if [ "$AUTO_PROMOTE" = "true" ] && [ "$changed" = "true" ]; then
do_prod=true
fi
# Manual dispatch with force: redeploy the selected environment(s) even with no change.
if [ "$FORCE" = "true" ]; then
case "$FORCE_ENV" in
production) do_prod=true ;;
staging) do_staging=true ;;
*) do_prod=true; do_staging=true ;;
esac
fi
{
echo "do_staging=$do_staging"
echo "do_prod=$do_prod"
} >> "$GITHUB_OUTPUT"
echo "changed=$changed do_staging=$do_staging do_prod=$do_prod"
# Rebuild + redeploy the product family pages with the new versions. Only the 15 product builds
# render versions (home/aggregates don't), so we skip them. Both environments build `main` — the
# single deploy branch.
deploy_staging:
needs: sync
if: needs.sync.outputs.do_staging == 'true'
strategy:
fail-fast: false
matrix:
product_family:
- annotation
- assembly
- classification
- comparison
- conversion
- editor
- markdown
- merger
- metadata
- parser
- redaction
- search
- signature
- viewer
- watermark
uses: ./.github/workflows/deploy_product.yml
with:
product_family: ${{ matrix.product_family }}
environment: staging
ref: main
secrets: inherit
deploy_production:
needs: [sync, deploy_staging]
# `always()` so an explicit production force still runs when deploy_staging was skipped.
if: ${{ always() && needs.sync.outputs.do_prod == 'true'
&& needs.deploy_staging.result != 'failure' && needs.deploy_staging.result != 'cancelled' }}
strategy:
fail-fast: false
matrix:
product_family:
- annotation
- assembly
- classification
- comparison
- conversion
- editor
- markdown
- merger
- metadata
- parser
- redaction
- search
- signature
- viewer
- watermark
uses: ./.github/workflows/deploy_product.yml
with:
product_family: ${{ matrix.product_family }}
environment: production
ref: main
secrets: inherit