Skip to content

Regenerate app-map.html from APP_MAP.md; fix malformed Section 7 list #103

Regenerate app-map.html from APP_MAP.md; fix malformed Section 7 list

Regenerate app-map.html from APP_MAP.md; fix malformed Section 7 list #103

Workflow file for this run

name: Deploy to GitHub Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Validate OpenAPI specs
run: |
echo "=== OpenAPI Spec Validation ==="
errors=0
total=0
# Validate all JSON specs are parseable
for dir in swagger-*-model/api swagger-*-model/api; do
if [ -d "$dir" ]; then
for f in "$dir"/*.json; do
[ -f "$f" ] || continue
total=$((total + 1))
if ! python3 -c "import json; json.load(open('$f'))" 2>/dev/null; then
echo "❌ Invalid JSON: $f"
errors=$((errors + 1))
fi
done
fi
done
echo "Validated $total JSON files, $errors errors"
if [ $errors -gt 0 ]; then
echo "::error::$errors spec files have invalid JSON"
exit 1
fi
echo "✅ All specs valid"
- name: Validate manifests
run: |
echo "=== Manifest Validation ==="
errors=0
for manifest in swagger-*-model/api/manifest.json; do
[ -f "$manifest" ] || continue
dir=$(dirname "$manifest")
model=$(basename $(dirname "$dir"))
# Check manifest module count matches file count.
# Exclude manifest.json itself and underscore-prefixed helper
# indexes (_paths_index.json, etc.) that aren't modules.
declared=$(python3 -c "import json; print(json.load(open('$manifest'))['total_modules'])")
actual=$(ls "$dir"/*.json 2>/dev/null | grep -v -E '/(manifest\.json|_[^/]+\.json)$' | wc -l)
if [ "$declared" != "$actual" ]; then
echo "❌ $model: manifest declares $declared modules but found $actual files"
errors=$((errors + 1))
else
echo "✅ $model: $declared modules OK"
fi
done
if [ $errors -gt 0 ]; then
echo "::error::Manifest count mismatches found"
exit 1
fi
- name: Validate search index
run: |
echo "=== Search Index Validation ==="
python3 -c "
import json
with open('search-index.json', encoding='utf-8') as f:
data = json.load(f)
modules = data['modules']
stats = data['stats']
print(f'Search index: {len(modules)} modules, {stats[\"total_endpoints\"]} endpoints')
# Check no exact duplicates (same name+version+category)
keys = [m['name'] + ':' + m.get('version','v1') + ':' + m.get('category','') for m in modules]
dupes = [n for n in set(keys) if keys.count(n) > 1]
if dupes:
print(f'❌ Exact duplicates found: {dupes[:5]}')
exit(1)
print('✅ No duplicates')
"
- name: G-6 API count regression guard
run: |
echo "=== API / Operation / Module Count Regression ==="
python3 -X utf8 scripts/release_counts.py --check
validate-releases:
# Per-release validation gate (VERSIONING.md §9). Runs only if releases/
# is populated. Each release is checked independently; planned releases
# are skipped automatically by validate_release.py.
runs-on: ubuntu-latest
timeout-minutes: 15
needs: validate
strategy:
fail-fast: false
matrix:
version: ["17.9.x", "17.12.x", "17.15.x", "17.18.1", "26.1.1"]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Validate release ${{ matrix.version }}
run: |
if [ ! -d "releases/${{ matrix.version }}" ]; then
echo "::notice::releases/${{ matrix.version }} not present yet — skipping."
exit 0
fi
python3 scripts/validate_release.py --version "${{ matrix.version }}"
deploy:
needs: [validate, validate-releases]
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Prepare deployment directory
run: |
# Create a clean deployment directory
mkdir -p deploy
# Copy only necessary files for the website (exclude heavy references and scripts)
echo "Copying model directories..."
cp -r swagger-*-model deploy/ 2>/dev/null || true
echo "Copying YANG trees..."
cp -r yang-trees deploy/ 2>/dev/null || true
echo "Copying docs..."
cp -r docs deploy/ 2>/dev/null || true
echo "Copying root files..."
# Copy root HTML files and config
cp *.html deploy/ 2>/dev/null || true
cp *.js deploy/ 2>/dev/null || true
cp *.json deploy/ 2>/dev/null || true
cp README.md deploy/ 2>/dev/null || true
cp FAQ.md deploy/ 2>/dev/null || true
cp CHANGELOG.md deploy/ 2>/dev/null || true
cp .nojekyll deploy/ 2>/dev/null || true
# SEO surface (robots.txt + sitemap.xml refreshed below)
cp robots.txt deploy/ 2>/dev/null || true
# PWA manifest (.webmanifest is not matched by *.json above)
cp site.webmanifest deploy/ 2>/dev/null || true
# Copy tools directory (Postman collection, etc.)
echo "Copying tools..."
cp -r tools deploy/ 2>/dev/null || true
# Copy shared front-end assets (CSS/JS used by all HTML pages)
echo "Copying assets..."
cp -r assets deploy/ 2>/dev/null || true
# Refresh sitemap.xml with today's <lastmod> values
echo "Refreshing sitemap.xml..."
python3 scripts/generate_sitemap.py >/dev/null
cp sitemap.xml deploy/ 2>/dev/null || true
# Regenerate app-map.html from APP_MAP.md (canonical arch doc)
echo "Regenerating app-map.html from APP_MAP.md..."
python3 -X utf8 scripts/build_app_map_html.py
cp app-map.html deploy/ 2>/dev/null || true
# build_app_map_html also (re)generates docs/app_mindmap.png when
# Pillow is available; copy any fresh artifact over the earlier
# `cp -r docs deploy/` snapshot so the deployed image matches the
# current APP_MAP.md.
mkdir -p deploy/docs
cp docs/app_mindmap.png deploy/docs/ 2>/dev/null || true
# Copy releases tree (multi-version artifacts: per-release specs,
# yang-trees, exports, telemetry-index, accountability, etc.)
echo "Copying releases/..."
cp -r releases deploy/ 2>/dev/null || true
# Exclude heavy directories
echo "Excluding references, archive, generators, scripts, internal docs from deployment..."
- name: Set up Node (for minifiers)
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "20"
- name: Minify CSS and JS in deploy/
run: |
# Minify only the small shared front-end files we control.
# We deliberately skip the bulky per-release JSON specs and
# third-party search-index data — those are JSON, not JS/CSS.
npm install --no-save --silent terser@5.x clean-css-cli@5.x
before_total=0
after_total=0
minify_js() {
local f="$1"
local before after
before=$(stat -c%s "$f")
npx --no-install terser "$f" \
--compress --mangle \
--output "$f.tmp" 2>/dev/null || { echo "skip $f"; return; }
mv "$f.tmp" "$f"
after=$(stat -c%s "$f")
before_total=$((before_total + before))
after_total=$((after_total + after))
echo " js ${f#deploy/}: ${before} -> ${after}"
}
minify_css() {
local f="$1"
local before after
before=$(stat -c%s "$f")
npx --no-install cleancss -o "$f.tmp" "$f" 2>/dev/null \
|| { echo "skip $f"; return; }
mv "$f.tmp" "$f"
after=$(stat -c%s "$f")
before_total=$((before_total + before))
after_total=$((after_total + after))
echo " css ${f#deploy/}: ${before} -> ${after}"
}
echo "=== Minifying deploy/assets/ ==="
for f in deploy/assets/css/*.css; do [ -f "$f" ] && minify_css "$f"; done
for f in deploy/assets/js/*.js; do [ -f "$f" ] && minify_js "$f"; done
echo "=== Minifying deploy/ top-level JS ==="
# Only minify our hand-written JS, skip search-index and similar data files
for f in deploy/*.js; do
[ -f "$f" ] || continue
case "$(basename "$f")" in
search-index*.js) echo " skip $f (data)";;
*) minify_js "$f";;
esac
done
if [ $before_total -gt 0 ]; then
pct=$(( (before_total - after_total) * 100 / before_total ))
echo ""
echo "=== Minification summary ==="
echo " before: ${before_total} bytes"
echo " after: ${after_total} bytes"
echo " savings: ${pct}% (-$((before_total - after_total)) bytes)"
fi
- name: Minify JSON data files in deploy/
run: |
# Strip whitespace from top-level JSON data files. These are
# large (search-index, accountability dumps) and the page never
# consumes the indented form. Per-release spec JSONs are left
# alone — they live under releases/ and are not in scope here.
python3 - <<'PY'
import json, os, sys
targets = [
"search-index.json",
"accountability_compare.json",
"yang_accountability.json",
"yang-prefix-map.json",
"version-stats.json",
]
total_before = total_after = 0
for name in targets:
path = os.path.join("deploy", name)
if not os.path.isfile(path):
continue
before = os.path.getsize(path)
with open(path, "r", encoding="utf-8") as fh:
data = json.load(fh)
compact = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
with open(path, "w", encoding="utf-8") as fh:
fh.write(compact)
after = os.path.getsize(path)
total_before += before
total_after += after
pct = (before - after) * 100 // before if before else 0
print(f" json {name}: {before} -> {after} ({pct}% saved)")
if total_before:
pct = (total_before - total_after) * 100 // total_before
print()
print("=== JSON minification summary ===")
print(f" before: {total_before} bytes")
print(f" after: {total_after} bytes")
print(f" savings: {pct}% (-{total_before - total_after} bytes)")
PY
- name: Deployment summary
run: |
echo "=== Deployment Summary ==="
du -sh deploy
echo "Total files to deploy:"
find deploy -type f | wc -l
echo "Top-level deployment contents:"
ls -lah deploy/
- name: Setup Pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: 'deploy'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0