Skip to content

Build Explainer

Build Explainer #44

name: Build Explainer
# Hosted runner for explainmyrepo: given a GitHub repo, build its explainer with the
# owner's keys (repo secrets), deploy it to its own Netlify site, and write progress
# back to a status gist that the website polls. Triggered by the /build Netlify function.
#
# RAILS (deterministic): 20-min hard timeout · serialized concurrency (no cost fan-out) ·
# --no-refine (caps the expensive vision passes) · --ship-best-effort (ALWAYS delivers a
# URL) · --no-publish (no per-repo GitHub repo/invite, so no extra token needed).
on:
workflow_dispatch:
inputs:
target_repo:
description: "owner/repo to explain"
required: true
build_id:
description: "correlation id (matches the status gist)"
required: false
gist_id:
description: "status gist to write progress into"
required: false
submitter_email:
description: "email to notify when done (optional)"
required: false
concurrency:
group: build-explainer
cancel-in-progress: false
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout the explainer tool
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Validate input (abuse guard)
run: |
REPO='${{ github.event.inputs.target_repo }}'
echo "target: $REPO"
if ! printf '%s' "$REPO" | grep -qE '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$'; then
echo "::error::invalid repo format (expected owner/name): $REPO"; exit 1
fi
- name: Install dependencies
run: |
npm ci || npm install
# Belt-and-suspenders: guarantee the 3 runtime deps are present (CI 'npm ci' was leaving
# playwright unresolved -> quality-grade failed). EXACT pinned versions + --no-save so this
# cannot drift to a newer/compromised patch or rewrite the committed lockfile (no supply-
# chain bypass; these match package-lock.json).
npm install --no-save playwright@1.61.1 @xenova/transformers@2.17.2 @ruvector/rvf@0.2.2
npx playwright install --with-deps chromium
# System tools the pipeline shells out to (scanned from tools/*.mjs), not all preinstalled
# on the runner: ImageMagick (magick/convert/identify — favicon, social card), xmllint
# (libxml2-utils — diagram SVG validation), dot (graphviz — diagram layout), zip (AI pack).
# Fonts help text render in generated imagery.
sudo apt-get update && sudo apt-get install -y imagemagick libxml2-utils graphviz zip fonts-liberation fonts-dejavu-core
- name: Build, deploy, and report status
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
EXPLAINER_NONINTERACTIVE: "1"
EXPLAINER_SKIP_INVITE: "1"
# Email fallback (non-blocking): if these secrets exist, notify emails the submitter.
SMTP_USER: ${{ secrets.GMAIL_USER }}
SMTP_PASS: ${{ secrets.GMAIL_APP_PASSWORD }}
EMAIL_TO: ${{ github.event.inputs.submitter_email }}
# Gist status write-back (needs `gist` scope; same account that created the gist):
GIST_TOKEN: ${{ secrets.EXPLAINER_GH_TOKEN }}
BUILD_ID: ${{ github.event.inputs.build_id }}
GIST_ID: ${{ github.event.inputs.gist_id }}
REPO: ${{ github.event.inputs.target_repo }}
run: |
set +e
patch_status() { # status step stepName liveUrl error
[ -z "$GIST_ID" ] || [ -z "$GIST_TOKEN" ] && return 0
local content
content=$(jq -n --arg b "$BUILD_ID" --argjson s "$1" --arg sn "$3" --arg st "$2" \
--arg u "$4" --arg e "$5" --arg r "$REPO" \
'{buildId:$b, step:$s, totalSteps:16, stepName:$sn, status:$st, repo:$r,
result: (if $u=="" then null else {liveUrl:$u} end),
error: (if $e=="" then null else $e end)}')
curl -s -X PATCH "https://api.github.com/gists/$GIST_ID" \
-H "Authorization: Bearer $GIST_TOKEN" -H "Accept: application/vnd.github+json" \
-d "$(jq -n --arg c "$content" '{files:{"status.json":{content:$c}}}')" >/dev/null || true
}
patch_status 1 building "Reading and understanding the repo…" "" ""
set -o pipefail
node bin/explainmyrepo.mjs "$REPO" --ship-best-effort --no-publish --no-refine 2>&1 | tee /tmp/build.log
RC=${PIPESTATUS[0]}
URL=$(grep -oE 'https://[a-zA-Z0-9.-]+\.netlify\.app' /tmp/build.log | tail -1)
if [ "$RC" -eq 0 ] && [ -n "$URL" ]; then
patch_status 16 done "Done — your explainer is live." "$URL" ""
echo "LIVE: $URL"
echo "### Explainer ready → $URL" >> "$GITHUB_STEP_SUMMARY"
else
# SECURITY: never publish raw build-log tails to the (public) status gist — a log line
# could contain an API key. Curated, fixed message to the gist; real detail stays in the
# GitHub Actions run log (visible only to repo collaborators).
patch_status 16 failed "The build couldn't finish." "" "It didn't complete this time. Try another repo, or try again in a bit."
echo "::error::build failed (rc=$RC, url=$URL)"
echo "---- build.log tail (Actions run log only — NOT published) ----"
tail -c 2000 /tmp/build.log || true
exit 1
fi