Skip to content

Deploy Pages

Deploy Pages #1206

Workflow file for this run

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