Deploy Pages #1206
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Pages | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SINGLE queue, never-cancel deploy. | |
| # • schedule — deploys the data the private engine publishes. The engine scans | |
| # at :17 every 3h and commits via the Contents API with "[skip ci]", which | |
| # deliberately suppresses the push trigger below; so without this cron the | |
| # site would never pick up a scan. (It replaces a workflow_run trigger on | |
| # "AxonOS Radar" — that workflow lived in THIS repo and was retired in the | |
| # open-core cutover, leaving the trigger dead and the site frozen. Cross-repo | |
| # workflow_run is not possible, hence a clock.) | |
| # • push to main — deploys source changes (feature commits, which do NOT carry | |
| # "[skip ci]") immediately, instead of waiting for the next tick. | |
| # • workflow_dispatch — manual redeploy button. | |
| # concurrency.cancel-in-progress: FALSE → overlapping deploys queue and wait; | |
| # nothing is ever cancelled (this was the root of the old "unable to cancel" | |
| # errors). Assets are stamped with the commit SHA at assemble time, so browsers | |
| # can never serve a stale app.css/app.js under fresh HTML again. | |
| # | |
| # One-time setup: repo Settings → Pages → Source = "GitHub Actions". | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| on: | |
| schedule: | |
| - cron: "47 */3 * * *" # ~30 min after the engine's :17 scan lands | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| # Queue deploys; never cancel a running one. This is the opposite of the old | |
| # setting and is what stops the "unable to cancel, it's finished" error. | |
| concurrency: | |
| group: pages-deploy | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: Build and deploy site artifact | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| # Every trigger this workflow declares must be listed here explicitly — | |
| # a guard that names fewer events than the on: block silently skips the | |
| # rest. Current triggers: schedule (the safety clock), push (source | |
| # changes), workflow_dispatch (manual + sync's redeploy). | |
| if: > | |
| github.event_name == 'schedule' || | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: main # workflow_run checks out the triggering sha; be explicit | |
| - name: Assemble site (public files only — never .git or tooling) | |
| run: | | |
| mkdir -p _site/data _site/assets | |
| cp index.html stats.html report.html support.html feed.xml og-image.png _site/ | |
| cp -r assets/. _site/assets/ | |
| cp data/*.json _site/data/ | |
| touch _site/.nojekyll | |
| echo "site files: $(find _site -type f | wc -l)" | |
| - name: Build ecosystem manifest + live badge (deploy-time, zero commits) | |
| # Derives data/ecosystem.json (the organism manifest) and | |
| # data/badge-ecosystem.json (shields.io endpoint) from the committed | |
| # registry + the current radar.json, straight into the artifact. Always | |
| # in sync with the deployed data, never a bot commit. | |
| run: python3 scripts/build_ecosystem_manifest.py --out _site/data | |
| - name: Build Signals + feeds (deploy-time, zero commits) | |
| # Turns the engine's new/rising/cooling flags into data/signals.json and | |
| # feeds/{signals,new,rising}.xml, straight into the artifact. Pure read | |
| # of the published dataset; commits nothing. | |
| run: python3 scripts/build_signals.py --out _site | |
| - name: Build data exports (deploy-time, zero commits) | |
| # projects.ndjson — one project per line for pandas/jq/DuckDB, always | |
| # in sync with the deployed radar.json, never a bot commit. | |
| run: python3 scripts/build_exports.py --out _site/data | |
| - name: Build trajectories + talent (deploy-time, zero commits) | |
| run: | | |
| python3 scripts/build_trajectory.py --out _site | |
| python3 scripts/build_talent.py --out _site | |
| - name: Build per-project badges (deploy-time, zero commits) | |
| run: python3 scripts/build_badges.py --out _site | |
| - name: Build the API index (deploy-time, zero commits) | |
| # Walks the ASSEMBLED artifact and lists only what exists — the front | |
| # door that cannot promise what the deploy does not carry. Must run | |
| # after every other builder. | |
| run: python3 scripts/build_api_index.py --out _site | |
| - name: Cache-bust asset URLs with the commit SHA | |
| # Append ?v=<sha> to every local CSS/JS asset link in the shipped HTML so | |
| # a new build always has new asset URLs. Without this, GitHub Pages' | |
| # long-lived asset caching serves the previous app.css/app.js under the | |
| # new markup — the "two versions at once" bug. Source files stay clean; | |
| # only the deployed copies are stamped. | |
| run: | | |
| SHA="${GITHUB_SHA:0:8}" | |
| for f in _site/index.html _site/stats.html _site/report.html _site/support.html; do | |
| [ -f "$f" ] || continue | |
| sed -i -E "s#((href|src)=\"\.?/?assets/[A-Za-z0-9_.-]+\.(css|js))\"#\1?v=${SHA}\"#g" "$f" | |
| done | |
| echo "stamped assets with v=${SHA}:" | |
| grep -oE "assets/[A-Za-z0-9_.-]+\.(css|js)\?v=[a-f0-9]+" _site/index.html | sort -u | |
| - name: Publish the build fingerprint (HTML self-heal) | |
| # The one caching layer ?v= stamping cannot cover is the HTML DOCUMENT | |
| # itself: browsers (especially mobile session-restore) happily show a | |
| # cached index.html whose footer still says the previous version. Fix: | |
| # 1. every deployed HTML copy gets <meta name="radar-build" ...>; | |
| # 2. data/build.json carries the same fingerprint, always fetched | |
| # no-store by app.js; | |
| # 3. on mismatch app.js navigates ONCE (session-guarded) to | |
| # ?b=<build> — a new document URL the cache has never seen — | |
| # so a stale shell can never survive a launch. Source files stay | |
| # clean; only deployed copies are stamped. | |
| run: | | |
| SHA="${GITHUB_SHA:0:8}" | |
| printf '{"build":"%s","deployed_at":"%s"}\n' "$SHA" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > _site/data/build.json | |
| for f in _site/index.html _site/stats.html _site/report.html _site/support.html; do | |
| [ -f "$f" ] || continue | |
| sed -i "s#<meta charset=\"utf-8\">#<meta charset=\"utf-8\"><meta name=\"radar-build\" content=\"${SHA}\">#" "$f" | |
| done | |
| grep -q "radar-build" _site/index.html || { echo "meta injection failed"; exit 1; } | |
| echo "build fingerprint: $SHA -> data/build.json + <meta radar-build> in every page" | |
| - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 | |
| - name: Make the map legible to machines that are not browsers | |
| # The map publishes eighteen JSON files and a scored ledger per project. | |
| # A crawler finds the front page; a data-catalogue harvester finds | |
| # nothing, because nothing said what the data is or where it starts. | |
| # | |
| # Runs after every derived file exists, and fails rather than advertise | |
| # a dataset the deploy did not produce: sending a harvester to a 404 | |
| # teaches it that this catalogue is unreliable. | |
| run: python3 scripts/build_discovery.py --out _site | |
| - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 | |
| with: | |
| path: _site | |
| - id: deployment | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 |