Skip to content

Commit 5b73603

Browse files
Add repo guard: no solutions leak, no CDN fonts, no sandstone, brand assets present
1 parent fad9a51 commit 5b73603

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

.github/guard.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env bash
2+
# Branding & content guard for a lecture repo.
3+
#
4+
# bash .github/guard.sh # run from the repo root — locally or in CI
5+
#
6+
# Fast (a few git/grep calls, no Quarto/Julia/Python, no build), and asserts the
7+
# handful of invariants this brand rollout established, each of which broke once
8+
# and would break silently again:
9+
#
10+
# 1 no worked solutions are tracked under a top-level solutions/ directory
11+
# (the Applied-Optimization leak: a .gitignore line cannot untrack what is
12+
# already committed, and nothing was watching). Lectures that publish
13+
# solutions ON PURPOSE are unaffected — they live elsewhere:
14+
# Optimization-with-Julia ships part-NN/solution/, and the retired DuSpo
15+
# lectures gate them behind `profile:`. Only a top-level solutions/ fails.
16+
#
17+
# 2 no live Google-Fonts source in _brand.yml. `source: google` makes Quarto
18+
# emit an @import of fonts.googleapis.com into every page (a GDPR problem
19+
# here — LG München, 2022). Comments are stripped first, because the German
20+
# note in Grundlagen-OR's _brand.yml literally reads "... source: google
21+
# mehr" and must NOT trip the guard.
22+
#
23+
# 3 no `sandstone` theme. Bootswatch's sandstone hardcodes its own Google
24+
# Roboto import that no brand config can strip, so it re-introduces the CDN
25+
# call by the back door.
26+
#
27+
# 4 the brand assets exist: styles.scss and _fonts.scss are hard requirements
28+
# (losing _fonts.scss silently drops every self-hosted face); a missing
29+
# images/favicon.svg is only a warning, because Mathe-Advent-25 has no logo
30+
# to derive one from yet.
31+
#
32+
# The check deliberately runs against TRACKED files (git ls-files), because a
33+
# leak is by definition something committed — and that automatically ignores the
34+
# build tree (_site/, .quarto/, _freeze/). The generated chatbot corpus
35+
# (docs/_repo-md/) and third-party _extensions/ are filtered out explicitly.
36+
37+
set -u
38+
39+
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
40+
cd "$root" || exit 2
41+
repo="$(basename "$root")"
42+
43+
fail=0
44+
warn=0
45+
say() { printf '%s\n' "$*"; }
46+
ok() { printf ' \033[32mok\033[0m %s\n' "$*"; }
47+
bad() { printf ' \033[31mFAIL\033[0m %s\n' "$*"; fail=1; }
48+
note() { printf ' \033[33mwarn\033[0m %s\n' "$*"; warn=1; }
49+
50+
# Strip YAML/SCSS comments without eating "#hex" values inside quotes: drop a
51+
# whole-line comment (optional leading space then #) and any trailing " #...".
52+
strip_comments() { sed -E 's/^[[:space:]]*#.*$//; s/[[:space:]]#.*$//'; }
53+
54+
say "guard: $repo"
55+
56+
# 1 — no top-level solutions/ tracked -----------------------------------------
57+
leaked="$(git ls-files 'solutions' 'solutions/**' 2>/dev/null)"
58+
if [ -n "$leaked" ]; then
59+
bad "tracked files under a top-level solutions/ directory:"
60+
printf ' %s\n' $leaked
61+
else
62+
ok "no top-level solutions/ tracked"
63+
fi
64+
65+
# 2 — no live Google-Fonts source in _brand.yml -------------------------------
66+
if [ -f _brand.yml ]; then
67+
hit="$(strip_comments < _brand.yml \
68+
| grep -nE 'fonts\.googleapis|fonts\.gstatic|source:[[:space:]]*google' || true)"
69+
if [ -n "$hit" ]; then
70+
bad "_brand.yml declares a live Google-Fonts source:"
71+
printf ' %s\n' "$hit"
72+
else
73+
ok "_brand.yml has no live Google-Fonts source"
74+
fi
75+
else
76+
note "no _brand.yml at repo root"
77+
fi
78+
79+
# 3 — no sandstone theme -------------------------------------------------------
80+
scan="$(git ls-files '*.yml' '*.yaml' '*.qmd' \
81+
| grep -vE '^(docs/_repo-md/|_extensions/|_site/)' || true)"
82+
sand=""
83+
if [ -n "$scan" ]; then
84+
sand="$(printf '%s\n' "$scan" | while IFS= read -r f; do
85+
strip_comments < "$f" | grep -niw 'sandstone' | sed "s|^|$f:|"
86+
done)"
87+
fi
88+
if [ -n "$sand" ]; then
89+
bad "sandstone theme referenced (re-adds a Google Roboto import):"
90+
printf ' %s\n' "$sand"
91+
else
92+
ok "no sandstone theme referenced"
93+
fi
94+
95+
# 4 — brand assets -------------------------------------------------------------
96+
for f in styles.scss _fonts.scss; do
97+
if [ -f "$f" ]; then ok "$f present"; else bad "$f missing"; fi
98+
done
99+
if [ -f images/favicon.svg ]; then
100+
ok "images/favicon.svg present"
101+
else
102+
note "images/favicon.svg missing (expected only for a lecture with no logo yet)"
103+
fi
104+
105+
say ""
106+
if [ "$fail" -ne 0 ]; then
107+
say "guard: FAILED"
108+
exit 1
109+
fi
110+
if [ "$warn" -ne 0 ]; then
111+
say "guard: passed with warnings"
112+
else
113+
say "guard: passed"
114+
fi
115+
exit 0

.github/workflows/guard.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Branding & content guard. Runs the checks in .github/guard.sh on every push
2+
# and PR — no Quarto, Julia or Python, so it finishes in seconds. What it asserts
3+
# and why is documented in guard.sh itself. Both files are generated from
4+
# Lecture-Foundations/brand/ by scripts/sync-guard.py; edit them there.
5+
name: guard
6+
7+
on:
8+
push:
9+
pull_request:
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
guard:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Run guard
21+
run: bash .github/guard.sh

0 commit comments

Comments
 (0)