From 961d1972e1e75da749161a57d4cf3957c8cdaf6f Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sat, 18 Jul 2026 02:40:52 -0500 Subject: [PATCH] ci(publish): pin Doenet site DoenetML version on publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a dev or production npm publish, POST the exact published version to the Doenet site's /api/info/updateTrackedDoenetmlVersion endpoint so the site pins its tracked DoenetML version to an immutable jsDelivr URL — users get the new bundle without waiting out a browser cache. - Expose the published version as a job output (dev and production). - Add notify-site-dev / notify-site-prod as separate jobs so a failure here does NOT republish to npm: GitHub notifies and "Re-run failed jobs" retries only this step (the site endpoint is idempotent). - Shared .github/scripts/notify-site-version.sh: production (doenet.org) is required and fails the job loudly; dev3 is best-effort (it auto-scales to zero and may be asleep). Requires the repository secret DOENET_VERSION_UPDATE_SECRET (repo-level so both the dev and production jobs can read it). Pairs with Doenet/DoenetApps#3011. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014i6e7VFUmE7svTjjSyida7 --- .github/scripts/notify-site-version.sh | 54 ++++++++++++++++++++++++++ .github/workflows/publish.yml | 53 +++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100755 .github/scripts/notify-site-version.sh diff --git a/.github/scripts/notify-site-version.sh b/.github/scripts/notify-site-version.sh new file mode 100755 index 0000000000..5e430fc35f --- /dev/null +++ b/.github/scripts/notify-site-version.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Pin the Doenet site's tracked DoenetML version to a concrete published version. +# +# Called by publish.yml after a dev or production npm publish. POSTs to the +# site's /api/info/updateTrackedDoenetmlVersion endpoint, which updates the +# doenetmlVersions row that tracks the given npm dist-tag so the jsDelivr bundle +# URL becomes immutable (browser-cacheable) instead of a moving tag — users get +# the new bundle without clearing their cache. +# +# Environment: +# SECRET - shared bearer secret (repo secret DOENET_VERSION_UPDATE_SECRET) +# TAG - npm dist-tag being pinned: "dev" or "latest" +# VERSION - concrete version just published (e.g. 0.7.21 or 0.7.21-dev.343) +# +# Production (doenet.org) is REQUIRED: a failure fails the job so GitHub notifies +# and the job can be re-run. dev3 is BEST-EFFORT because it auto-scales to zero +# and may be asleep when a publish happens. + +set -u + +if [[ -z "${SECRET:-}" ]]; then + echo "::error::DOENET_VERSION_UPDATE_SECRET is not set — add it as a repository secret" + exit 1 +fi +if [[ -z "${TAG:-}" || -z "${VERSION:-}" ]]; then + echo "::error::TAG and VERSION must both be set (TAG='${TAG:-}', VERSION='${VERSION:-}')" + exit 1 +fi + +# $1 = site base URL, $2 = required ("true" fails the job on error) +post() { + local url="$1" required="$2" + echo "Pinning ${TAG} -> ${VERSION} on ${url} ..." + if curl -fsS --connect-timeout 15 --max-time 60 \ + -X POST "${url}/api/info/updateTrackedDoenetmlVersion" \ + -H "Authorization: Bearer ${SECRET}" \ + -H "Content-Type: application/json" \ + -d "{\"tag\":\"${TAG}\",\"version\":\"${VERSION}\"}"; then + echo "" + echo " updated ${url}" + elif [[ "${required}" == "true" ]]; then + echo "" + echo "::error::Failed to pin DoenetML version on ${url}" + exit 1 + else + echo "" + echo "::warning::Failed to pin DoenetML version on ${url} (best-effort; the site may be asleep)" + fi +} + +post "https://doenet.org" true +post "https://dev3.doenet.org" false + +echo "Done." diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a66aeff737..5cca8f87d6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,6 +34,8 @@ jobs: dev-release: name: Dev Release runs-on: ubuntu-latest + outputs: + version: ${{ steps.compute-version.outputs.version }} if: >- (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && @@ -71,10 +73,14 @@ jobs: run: node .github/scripts/verify-ci.mjs - name: Update versions + id: compute-version run: | CURRENT_VERSION=$(node -p "require('./packages/doenetml/package.json').version") VERSION="${CURRENT_VERSION}-dev.${{ github.run_number }}" npm run version -- ${VERSION} --no-git-tag-version + # Exposed as a job output so the notify-site-dev job can pin the + # Doenet site to the exact version published here. + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - name: Build workspaces run: | @@ -206,6 +212,8 @@ jobs: name: Production Release runs-on: ubuntu-latest environment: production + outputs: + version: ${{ steps.release-tag.outputs.version }} if: >- github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'production') @@ -298,3 +306,48 @@ jobs: run: npm run publish -w @doenet/vscode-extension env: VSCE_PAT: ${{ secrets.VSCE_PAT }} + + # After a dev publish, pin the Doenet site's tracked dev DoenetML version to + # the exact version just published, so users get the new bundle immediately + # from an immutable jsDelivr URL instead of waiting out a browser cache. + # + # This runs as a separate job so a failure here does NOT republish to npm: + # GitHub notifies on the failed job and "Re-run failed jobs" retries only + # this step. The site endpoint is idempotent, so retries are safe. + # + # Requires the repository-level secret DOENET_VERSION_UPDATE_SECRET (must be + # repo-level, not only in the `production` environment, so both this dev job + # and the production job below can read it — same requirement as VSCE_PAT). + notify-site-dev: + name: Pin dev version on Doenet sites + needs: dev-release + runs-on: ubuntu-latest + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Pin tracked dev version + env: + SECRET: ${{ secrets.DOENET_VERSION_UPDATE_SECRET }} + TAG: dev + VERSION: ${{ needs.dev-release.outputs.version }} + run: bash .github/scripts/notify-site-version.sh + + # After a production publish, pin the site's tracked `latest` DoenetML version + # to the released version. Same rationale and retry behavior as the dev job. + notify-site-prod: + name: Pin latest version on Doenet sites + needs: production-release + runs-on: ubuntu-latest + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Pin tracked latest version + env: + SECRET: ${{ secrets.DOENET_VERSION_UPDATE_SECRET }} + TAG: latest + VERSION: ${{ needs.production-release.outputs.version }} + run: bash .github/scripts/notify-site-version.sh