prebuild.mjs
1 #!/usr/bin/env node 2 // Runs website/scripts/extract-skills.py and generate-llms-txt.py before 3 // docusaurus build/start so that: 4 // - website/src/data/skills.json (imported by src/pages/skills/index.tsx) 5 // - website/static/llms.txt (agent-friendly short docs index) 6 // - website/static/llms-full.txt (full docs concat for LLM context) 7 // all exist without contributors remembering to run Python scripts manually. 8 // CI workflows still run the extraction explicitly, which is a no-op duplicate 9 // but matches their historical behaviour. 10 // 11 // If python3 or its deps (pyyaml) aren't available on the local machine, we 12 // fall back to writing an empty skills.json so `npm run build` still 13 // succeeds — the Skills Hub page just shows an empty state, and llms.txt 14 // generation is skipped. CI always has the deps installed, so production 15 // deploys get real data. 16 17 import { spawnSync } from "node:child_process"; 18 import { mkdirSync, writeFileSync, existsSync } from "node:fs"; 19 import { dirname, join, resolve } from "node:path"; 20 import { fileURLToPath } from "node:url"; 21 22 const scriptDir = dirname(fileURLToPath(import.meta.url)); 23 const websiteDir = resolve(scriptDir, ".."); 24 const extractScript = join(scriptDir, "extract-skills.py"); 25 const llmsScript = join(scriptDir, "generate-llms-txt.py"); 26 const outputFile = join(websiteDir, "src", "data", "skills.json"); 27 28 function writeEmptyFallback(reason) { 29 mkdirSync(dirname(outputFile), { recursive: true }); 30 writeFileSync(outputFile, "[]\n"); 31 console.warn( 32 `[prebuild] extract-skills.py skipped (${reason}); wrote empty skills.json. ` + 33 `Install python3 + pyyaml locally for a populated Skills Hub page.`, 34 ); 35 } 36 37 function runPython(script, label) { 38 if (!existsSync(script)) { 39 console.warn(`[prebuild] ${label} skipped (script missing)`); 40 return false; 41 } 42 const r = spawnSync("python3", [script], { stdio: "inherit", cwd: websiteDir }); 43 if (r.error && r.error.code === "ENOENT") { 44 console.warn(`[prebuild] ${label} skipped (python3 not found)`); 45 return false; 46 } 47 if (r.status !== 0) { 48 console.warn(`[prebuild] ${label} exited with status ${r.status}`); 49 return false; 50 } 51 return true; 52 } 53 54 // 1) skills.json — required for the Skills Hub page. 55 if (!existsSync(extractScript)) { 56 writeEmptyFallback("extract script missing"); 57 } else { 58 const r = spawnSync("python3", [extractScript], { 59 stdio: "inherit", 60 cwd: websiteDir, 61 }); 62 if (r.error && r.error.code === "ENOENT") { 63 writeEmptyFallback("python3 not found"); 64 } else if (r.status !== 0) { 65 writeEmptyFallback(`extract-skills.py exited with status ${r.status}`); 66 } 67 } 68 69 // 2) llms.txt + llms-full.txt — agent-friendly docs entrypoints. Non-fatal. 70 runPython(llmsScript, "generate-llms-txt.py");