1+ name : Pages – aggregate all branches
2+
3+ on :
4+ push :
5+ branches : ["**"]
6+ workflow_dispatch :
7+
8+ permissions :
9+ contents : read
10+ pages : write
11+ id-token : write
12+
13+ concurrency :
14+ group : pages
15+ cancel-in-progress : true
16+
17+ jobs :
18+ aggregate_deploy :
19+ runs-on : ubuntu-latest
20+ environment :
21+ name : github-pages
22+ url : ${{ steps.deploy.outputs.page_url }}
23+
24+ steps :
25+ - name : Checkout (shallow)
26+ uses : actions/checkout@v4
27+ with :
28+ fetch-depth : 1
29+
30+ - name : Configure Pages
31+ uses : actions/configure-pages@v5
32+
33+ - name : Aggregate and build recursive directory pages
34+ shell : bash
35+ run : |
36+ set -euo pipefail
37+
38+ # jq for JSON escaping (present on runners, but ensure)
39+ if ! command -v jq >/dev/null 2>&1; then
40+ sudo apt-get update -y
41+ sudo apt-get install -y jq
42+ fi
43+
44+ REPO="${GITHUB_REPOSITORY#*/}"
45+ SITE_ROOT="/${REPO}/"
46+
47+ mkdir -p public
48+ : > public/.nojekyll
49+
50+ # Tiny favicon assets to avoid 404s
51+ printf "%s" \
52+ "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='14' font-size='14'>🚀</text></svg>" \
53+ > public/favicon.svg
54+
55+ # List remote branches
56+ mapfile -t BRANCHES < <(
57+ git ls-remote --heads origin | awk '{print $2}' \
58+ | sed 's@refs/heads/@@'
59+ )
60+
61+ # Landing page (no globals except window.__branches)
62+ cat > public/index.html <<'HTML'
63+ <!doctype html>
64+ <meta charset="utf-8" />
65+ <meta name="viewport" content="width=device-width,initial-scale=1" />
66+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ctext y='14' font-size='14'%3E🚀%3C/text%3E%3C/svg%3E" />
67+ <title>Branches</title>
68+ <style>
69+ body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial;
70+ margin:24px;background:#0d1117;color:#e6edf3}
71+ a{color:#58a6ff;text-decoration:none}
72+ .card{background:#161b22;padding:16px;border-radius:8px;margin:12px 0}
73+ input{background:#0b0f14;border:1px solid #30363d;color:#e6edf3;
74+ padding:8px 10px;border-radius:6px;width:100%;max-width:420px}
75+ </style>
76+ <h1>Branches</h1>
77+ <input id="q" placeholder="Filter branches..." />
78+ <div id="list"></div>
79+ <script>window.__branches=window.__branches||[];</script>
80+ HTML
81+
82+ # Emit index.html for a directory
83+ gen_index_dir() {
84+ local DIR="$1" ROOT="$2" BR="$3"
85+ local rel="${DIR#$ROOT}"
86+ local title="Branch: ${BR}${rel:-/}"
87+
88+ {
89+ echo "<!doctype html><meta charset='utf-8'/>"
90+ echo "<meta name='viewport' content='width=device-width,initial-scale=1'/>"
91+ echo "<link rel='icon' href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ctext y='14' font-size='14'%3E🚀%3C/text%3E%3C/svg%3E\" />"
92+ echo "<title>${title}</title>"
93+ echo "<style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial;margin:24px;background:#0d1117;color:#e6edf3}a{color:#58a6ff;text-decoration:none}table{width:100%;border-collapse:collapse}th,td{padding:8px;border-bottom:1px solid #30363d}tr:hover{background:#161b22}</style>"
94+ echo "<h1>${title}</h1>"
95+ if [[ "$DIR" != "$ROOT" ]]; then
96+ echo "<p><a href='../'>⬆ Up</a> • <a href='${SITE_ROOT}'>All branches</a></p>"
97+ else
98+ echo "<p><a href='${SITE_ROOT}'>All branches</a></p>"
99+ fi
100+ echo "<table><thead><tr><th>Name</th><th style='width:120px'>Size</th><th style='width:180px'>Modified</th></tr></thead><tbody>"
101+
102+ # Subdirectories
103+ while IFS= read -r d; do
104+ [[ -n "$d" ]] || continue
105+ printf "<tr><td>📁 <a href='./%s/'>%s/</a></td><td>—</td><td>—</td></tr>\n" "$d" "$d"
106+ done < <(find "$DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort)
107+
108+ # Files
109+ while IFS= read -r f; do
110+ [[ -n "$f" ]] || continue
111+ size=$(stat -c %s "$DIR/$f" 2>/dev/null || echo 0)
112+ size_h=$(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "$size")
113+ mod=$(date -r "$DIR/$f" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "")
114+ printf "<tr><td>📄 <a href='./%s'>%s</a></td><td>%s</td><td>%s</td></tr>\n" "$f" "$f" "$size_h" "$mod"
115+ done < <(find "$DIR" -mindepth 1 -maxdepth 1 -type f ! -name 'index.html' -printf '%f\n' | sort)
116+
117+ echo "</tbody></table>"
118+ echo "<p style='opacity:.7'>Generated $(date -u '+%Y-%m-%d %H:%M UTC')</p>"
119+ } > "$DIR/index.html"
120+ }
121+
122+ # Build per-branch trees and indexes
123+ for BR in "${BRANCHES[@]}"; do
124+ SAFE="$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g')"
125+ mkdir -p "public/$SAFE"
126+
127+ git fetch --depth=1 origin "$BR"
128+ git archive --format=tar FETCH_HEAD | tar -x -C "public/$SAFE"
129+
130+ # Keep only static assets; prune empty dirs
131+ rm -rf "public/$SAFE/.git" "public/$SAFE/.github" \
132+ "public/$SAFE/node_modules"
133+ find "public/$SAFE" -type f ! \
134+ \( -name '*.html' -o -name '*.js' -o -name '*.css' -o \
135+ -name '*.json' -o -name '*.png' -o -name '*.jpg' -o \
136+ -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' \) \
137+ -delete
138+ find "public/$SAFE" -type d -empty -delete
139+
140+ ROOT_DIR="public/$SAFE"
141+ while IFS= read -r -d '' d; do
142+ gen_index_dir "$d" "$ROOT_DIR" "$BR"
143+ done < <(find "$ROOT_DIR" -type d -print0)
144+
145+ # Append to landing page (JSON-escaped)
146+ SAFE_JSON=$(printf '%s' "$SAFE" | jq -Rr @json)
147+ BR_JSON=$(printf '%s' "$BR" | jq -Rr @json)
148+ echo "<script>window.__branches.push({safe:$SAFE_JSON,name:$BR_JSON});</script>" \
149+ >> public/index.html
150+ done
151+
152+ # Finish landing page
153+ cat >> public/index.html <<'HTML'
154+ <script>
155+ (() => {
156+ const q = document.getElementById('q');
157+ const listEl = document.getElementById('list');
158+
159+ function render(filter = '') {
160+ listEl.innerHTML = '';
161+ const qv = filter.toLowerCase();
162+ for (const b of (window.__branches || []).filter(x =>
163+ x.name.toLowerCase().includes(qv)
164+ )) {
165+ const div = document.createElement('div');
166+ div.className = 'card';
167+ div.innerHTML = `<strong>${b.name}</strong><div>
168+ <a href="./${b.safe}/">Open</a>
169+ </div>`;
170+ listEl.appendChild(div);
171+ }
172+ }
173+
174+ q.addEventListener('input', (e) => render(e.target.value));
175+ render();
176+ })();
177+ </script>
178+ HTML
179+
180+ - name : Upload Pages artifact
181+ uses : actions/upload-pages-artifact@v4
182+ with :
183+ path : public
184+
185+ - name : Deploy to GitHub Pages
186+ id : deploy
187+ uses : actions/deploy-pages@v4
0 commit comments