Closes #27 Adds a dead-simple CI smoke test that runs on every PR and push to main: Parse checks: - Chapter validation (structure, numbering, H1 headers) - Markdown build (combines all chapters) - Compiled manuscript size verification (>10k words) - Python syntax check on all .py files - YAML syntax check on workflow files Secret scan: - Scans for common API key/token patterns (sk-ant-, sk-or-, ghp_, AKIA, etc.) - Searches all text files, excludes .git and the smoke test itself - Hard fail if any secrets found Two files: - scripts/smoke.sh — the smoke test script - .gitea/workflows/smoke.yml — Gitea Actions workflow
112 lines
3.4 KiB
Bash
Executable File
112 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# The Testament — Smoke Test
|
|
# Dead simple CI: parse check + secret scan.
|
|
# Ref: https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/issues/27
|
|
set -euo pipefail
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " ✓ $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
# ─── Section 1: Parse checks ───────────────────────────────────────
|
|
echo "── Parse Checks ──"
|
|
|
|
# 1a. Chapter validation (structure, numbering, headers)
|
|
if python3 compile.py --validate 2>&1; then
|
|
pass "Chapter validation passed"
|
|
else
|
|
fail "Chapter validation failed"
|
|
fi
|
|
|
|
# 1b. Build markdown combination
|
|
if python3 build/build.py --md >/dev/null 2>&1; then
|
|
pass "Markdown build passed"
|
|
else
|
|
fail "Markdown build failed"
|
|
fi
|
|
|
|
# 1c. Verify compiled output exists and is non-empty
|
|
if [ -s build/the-testament-full.md ]; then
|
|
WORDS=$(wc -w < build/the-testament-full.md | tr -d ' ')
|
|
if [ "$WORDS" -gt 10000 ]; then
|
|
pass "Compiled manuscript: $WORDS words"
|
|
else
|
|
fail "Compiled manuscript suspiciously short: $WORDS words"
|
|
fi
|
|
else
|
|
fail "Compiled manuscript missing or empty"
|
|
fi
|
|
|
|
# 1d. Python syntax check on all .py files
|
|
PY_OK=true
|
|
for f in $(find . -name "*.py" -not -path "./.git/*"); do
|
|
if ! python3 -c "import ast; ast.parse(open('$f').read())" 2>/dev/null; then
|
|
fail "Python syntax error in $f"
|
|
PY_OK=false
|
|
fi
|
|
done
|
|
if $PY_OK; then
|
|
pass "All Python files parse cleanly"
|
|
fi
|
|
|
|
# 1e. YAML syntax check on workflow files
|
|
YAML_OK=true
|
|
for f in $(find .gitea -name "*.yml" -o -name "*.yaml" 2>/dev/null); do
|
|
if ! python3 -c "import yaml; yaml.safe_load(open('$f'))" 2>/dev/null; then
|
|
fail "YAML syntax error in $f"
|
|
YAML_OK=false
|
|
fi
|
|
done
|
|
if $YAML_OK; then
|
|
pass "All YAML files parse cleanly"
|
|
fi
|
|
|
|
# ─── Section 2: Secret scan ────────────────────────────────────────
|
|
echo ""
|
|
echo "── Secret Scan ──"
|
|
|
|
# Patterns that should never appear in a book repo
|
|
SECRET_PATTERNS=(
|
|
"sk-ant-"
|
|
"sk-or-"
|
|
"sk-[a-zA-Z0-9]{20,}"
|
|
"ghp_[a-zA-Z0-9]{36}"
|
|
"gho_[a-zA-Z0-9]{36}"
|
|
"AKIA[0-9A-Z]{16}"
|
|
"AKIA[A-Z0-9]{16}"
|
|
"xox[bpsa]-"
|
|
"SG\."
|
|
"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY"
|
|
)
|
|
|
|
FOUND_SECRETS=false
|
|
for pattern in "${SECRET_PATTERNS[@]}"; do
|
|
# Search text files only, skip .git and binary files
|
|
HITS=$(grep -rn "$pattern" --include="*.md" --include="*.py" --include="*.sh" --include="*.yml" --include="*.yaml" --include="*.json" --include="*.html" --include="*.js" --include="*.css" --include="*.txt" --include="*.cfg" --include="*.ini" --exclude-dir=.git . 2>/dev/null | grep -v "scripts/smoke.sh" || true)
|
|
if [ -n "$HITS" ]; then
|
|
fail "Possible secret found: $pattern"
|
|
echo "$HITS" | head -5
|
|
FOUND_SECRETS=true
|
|
fi
|
|
done
|
|
if ! $FOUND_SECRETS; then
|
|
pass "No secrets detected"
|
|
fi
|
|
|
|
# ─── Summary ───────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
echo "SMOKE TEST FAILED"
|
|
exit 1
|
|
else
|
|
echo "SMOKE TEST PASSED"
|
|
exit 0
|
|
fi
|