From 1fa80d8ecd2e043cb61fdc8310f9f7ffef83698c Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:49:18 -0700 Subject: [PATCH] fix(docs): wire the savings leaderboard's Supabase anon key into the docs build (#596) The docs-site leaderboard (docs/javascripts/leaderboard.js) reads the public Supabase anon key from window.OPENJARVIS_SUPABASE_ANON_KEY, but nothing set it, so the published leaderboard always rendered "Leaderboard not configured yet". Add a generated config file (leaderboard-config.js) loaded before leaderboard.js that supplies the global, and inject its value at docs-build time from the existing VITE_SUPABASE_ANON_KEY repo secret. The committed default is empty, so local `mkdocs build` and fork PRs (no secret) degrade gracefully. The anon key is public by design (Supabase RLS protects the data). - docs/javascripts/leaderboard-config.js: empty-default global declaration. - mkdocs.yml: load leaderboard-config.js before leaderboard.js. - docs.yml: write the config from the secret (read via env, JSON-encoded into a JS string literal to avoid injection) before `mkdocs build`. - tests/deployment/test_docs_leaderboard.py: guard the wiring + load order. Verified with a local `mkdocs build`: the generated config ships in site/ and loads before leaderboard.js. Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/docs.yml | 20 ++++++++++ docs/javascripts/leaderboard-config.js | 12 ++++++ mkdocs.yml | 1 + tests/deployment/test_docs_leaderboard.py | 45 +++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 docs/javascripts/leaderboard-config.js create mode 100644 tests/deployment/test_docs_leaderboard.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index de1d504e..9f7e78cb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,6 +41,26 @@ jobs: - name: Install dependencies run: uv sync --extra docs + # Inject the public Supabase anon key so the savings leaderboard works on + # the published docs site. Missing/empty (e.g. fork PRs) leaves the + # leaderboard gracefully disabled. The key is read from env (not inlined) + # and JSON-encoded into a JS string literal to avoid any injection. + - name: Inject leaderboard Supabase anon key + env: + OPENJARVIS_LEADERBOARD_ANON: ${{ secrets.VITE_SUPABASE_ANON_KEY }} + run: | + python3 - <<'PY' + import json, os, pathlib + + key = os.environ.get("OPENJARVIS_LEADERBOARD_ANON", "") + pathlib.Path("docs/javascripts/leaderboard-config.js").write_text( + "// Generated at docs-build time from the VITE_SUPABASE_ANON_KEY secret.\n" + "window.OPENJARVIS_SUPABASE_ANON_KEY = " + json.dumps(key) + ";\n", + encoding="utf-8", + ) + print("leaderboard anon key:", "set" if key else "empty (leaderboard disabled)") + PY + - name: Build documentation run: uv run mkdocs build diff --git a/docs/javascripts/leaderboard-config.js b/docs/javascripts/leaderboard-config.js new file mode 100644 index 00000000..9f88a889 --- /dev/null +++ b/docs/javascripts/leaderboard-config.js @@ -0,0 +1,12 @@ +// Public Supabase config for the savings leaderboard. +// +// This file is loaded *before* leaderboard.js and supplies the anon key it +// reads from `window.OPENJARVIS_SUPABASE_ANON_KEY`. The key is injected at +// docs-build time from the VITE_SUPABASE_ANON_KEY repo secret (see +// .github/workflows/docs.yml). It is intentionally empty here so that local +// `mkdocs build` and fork pull requests — which have no secret — render the +// graceful "Leaderboard not configured yet" message instead of failing. +// +// The anon key is public by design: Supabase Row-Level Security protects the +// data, so shipping it in the public docs bundle is expected. +window.OPENJARVIS_SUPABASE_ANON_KEY = ""; diff --git a/mkdocs.yml b/mkdocs.yml index 7cd82f57..228056dc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -127,6 +127,7 @@ markdown_extensions: - pymdownx.tilde extra_javascript: + - javascripts/leaderboard-config.js - javascripts/leaderboard.js - https://cdn.jsdelivr.net/npm/@docsearch/js@3 - javascripts/docsearch-init.js diff --git a/tests/deployment/test_docs_leaderboard.py b/tests/deployment/test_docs_leaderboard.py new file mode 100644 index 00000000..911a68b5 --- /dev/null +++ b/tests/deployment/test_docs_leaderboard.py @@ -0,0 +1,45 @@ +"""Static guards for the docs-site savings-leaderboard Supabase wiring. + +`docs/javascripts/leaderboard.js` reads the public Supabase anon key from +`window.OPENJARVIS_SUPABASE_ANON_KEY`. That global is set by a generated +config file (`leaderboard-config.js`) which must load *before* leaderboard.js, +and whose value is injected at docs-build time from the VITE_SUPABASE_ANON_KEY +secret (see `.github/workflows/docs.yml`). These are text-only checks — no +mkdocs build required — so they run in the default CI lane. +""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent.parent +MKDOCS = ROOT / "mkdocs.yml" +DOCS_WORKFLOW = ROOT / ".github" / "workflows" / "docs.yml" +CONFIG_JS = ROOT / "docs" / "javascripts" / "leaderboard-config.js" +LEADERBOARD_JS = ROOT / "docs" / "javascripts" / "leaderboard.js" + +_ANON_GLOBAL = "window.OPENJARVIS_SUPABASE_ANON_KEY" + + +def test_config_js_declares_anon_key_global(): + assert CONFIG_JS.is_file(), "leaderboard-config.js is missing" + assert _ANON_GLOBAL in CONFIG_JS.read_text() + + +def test_leaderboard_reads_the_anon_key_global(): + # leaderboard.js must consume the global the config file sets. + assert _ANON_GLOBAL in LEADERBOARD_JS.read_text() + + +def test_config_is_loaded_before_leaderboard_in_mkdocs(): + content = MKDOCS.read_text() + cfg = content.index("javascripts/leaderboard-config.js") + lb = content.index("javascripts/leaderboard.js") + assert cfg < lb, "leaderboard-config.js must be listed before leaderboard.js" + + +def test_docs_workflow_injects_the_anon_key(): + content = DOCS_WORKFLOW.read_text() + assert "VITE_SUPABASE_ANON_KEY" in content, "workflow doesn't read the secret" + assert "leaderboard-config.js" in content, "workflow doesn't write the config file" + assert _ANON_GLOBAL in content, "workflow doesn't set the anon-key global"