/ website / scripts / prebuild.mjs
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");