Files
sglang/docs/scripts/check_cookbook_configs.mjs
T
2026-08-03 16:51:00 -07:00

204 lines
8.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Static guard for the cookbook deployment/playground engines and their configs.
// Zero dependencies, no browser, no Mintlify — plain `node`.
//
// node docs/scripts/check_cookbook_configs.mjs
//
// What it protects, in order of how expensive the bug is to find by hand:
//
// 1. MIRROR drift. The overlay-resolution rule is written in both engines
// because Mintlify snippets cannot import each other. If the copies drift,
// the Deploy command and the playground's base disagree and the reader sees
// phantom +/- lines in the diff — with no error anywhere.
// 2. Sibling identity. Overlay resolution clones the base cell, so sibling
// detection must compare match dimensions rather than object references.
// 3. Config/engine contract. A cell keyed on a dimension the config no longer
// declares silently stops matching; the panel just shows a different cell.
// 4. Predicate safety. showWhen / disabled / flags run against selections the
// author never clicked through; a throw there blanks the whole widget.
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
const SNIPPETS = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "snippets");
const CONFIGS = join(SNIPPETS, "configs");
const LEGACY_DIMS = ["variants", "quantizations", "strategies", "nodesOptions"];
const failures = [];
const fail = (where, msg) => failures.push(`${where}: ${msg}`);
// ---------------------------------------------------------------- 1. MIRROR
// Compare the marked blocks with comments and whitespace normalized away, so
// wording may differ per file but the rule may not.
const mirrorBody = (file) => {
const src = readFileSync(join(SNIPPETS, file), "utf8");
const start = src.indexOf("==== MIRROR");
const end = src.indexOf("==== end MIRROR");
if (start === -1 || end === -1) return null;
return src
.slice(src.indexOf("\n", start), end)
.split("\n")
.map((l) => l.trim())
.filter((l) => l && !l.startsWith("//"))
.join(" ")
.replace(/\s+/g, " ");
};
const a = mirrorBody("_deployment.jsx");
const b = mirrorBody("_playground.jsx");
if (a === null) fail("_deployment.jsx", "MIRROR markers missing");
if (b === null) fail("_playground.jsx", "MIRROR markers missing");
if (a && b && a !== b) {
fail("MIRROR", "overlay resolution has drifted between the two engines");
const [la, lb] = [a.split(" "), b.split(" ")];
const i = la.findIndex((t, k) => t !== lb[k]);
fail("MIRROR", `first divergence near token ${i}: `
+ `_deployment "${la.slice(i, i + 8).join(" ")}" vs `
+ `_playground "${lb.slice(i, i + 8).join(" ")}"`);
}
// `withOverlay` returns a clone, so object identity can never distinguish the
// current base cell from a true sibling. This previously made every cookbook
// show a spurious "matches … / switch base" hint before the reader changed
// anything.
const playgroundSource = readFileSync(join(SNIPPETS, "_playground.jsx"), "utf8");
if (/\bmatchedCell\s*!==\s*baseCell\b/.test(playgroundSource)) {
fail("_playground.jsx", "sibling detection compares cloned cells by object identity");
}
// --------------------------------------------------------------- 3/4. Configs
// Configs are .jsx with a single `export const config` literal; import them
// through a data: URL so no temp file is needed.
const loadConfig = async (path) => {
const src = readFileSync(path, "utf8");
const mod = await import(
"data:text/javascript," + encodeURIComponent(src)
);
return mod.config;
};
// Every combination of match dims + overlay dims the reader can produce.
const selectionSpace = (config) => {
const dims = [
{ id: "hw", options: (config.supportedHardware || []).map((id) => ({ id })) },
...(config.matchDims || []),
...(config.overlayDims || []),
];
let space = [{}];
for (const d of dims) {
const next = [];
for (const partial of space) {
for (const opt of (d.options || [])) next.push({ ...partial, [d.id]: opt.id });
}
space = next.length ? next : space;
if (space.length > 20000) return space.slice(0, 20000); // cheap blow-up guard
}
return space;
};
const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
e.isDirectory() ? walk(join(dir, e.name))
: (e.name.endsWith(".jsx")
&& !e.name.includes("benchmark")
&& e.name !== "popular-models.jsx"
? [join(dir, e.name)] : []));
for (const path of walk(CONFIGS)) {
const where = relative(join(SNIPPETS, ".."), path);
let config;
try {
config = await loadConfig(path);
} catch (e) {
fail(where, `does not parse as a module: ${e.message}`);
continue;
}
if (!config) { fail(where, "no `export const config`"); continue; }
const custom = Array.isArray(config.matchDims);
// A config either declares its own dims or carries the full legacy set —
// half of each means the engine silently renders a dimension nobody authored.
if (!custom) {
for (const k of LEGACY_DIMS) {
if (!Array.isArray(config[k])) fail(where, `legacy config is missing \`${k}\``);
}
}
const matchIds = ["hw", ...(custom
? config.matchDims.map((d) => d.id)
: LEGACY_DIMS.map((k) => ({ variants: "variant", quantizations: "quant",
strategies: "strategy", nodesOptions: "nodes" })[k]))];
for (const [i, cell] of (config.cells || []).entries()) {
const keys = Object.keys(cell.match || {}).sort();
const want = [...matchIds].sort();
if (keys.join(",") !== want.join(",")) {
fail(where, `cells[${i}].match keys [${keys}] != declared dims [${want}]`);
}
for (const dim of (config.matchDims || [])) {
const v = cell.match[dim.id];
if (!(dim.options || []).some((o) => o.id === v)) {
fail(where, `cells[${i}].match.${dim.id}="${v}" is not an option of that dim`);
}
}
// Without a `nodes` dim the node count rides on the cell; a missing one
// silently degrades a multi-node recipe to single-node.
if (custom && !matchIds.includes("nodes") && cell.nnodes === undefined) {
fail(where, `cells[${i}] has no \`nnodes\` and the config declares no nodes dim`);
}
}
for (const dim of (config.overlayDims || [])) {
const ids = (dim.options || []).map((o) => o.id);
if (dim.default !== undefined && !ids.includes(dim.default)) {
fail(where, `overlayDims.${dim.id}.default="${dim.default}" is not one of [${ids}]`);
}
}
// Predicates and flag builders must survive every reachable selection.
const space = selectionSpace(config);
const probe = (fn, label) => {
for (const sel of space) {
try { fn(sel); } catch (e) {
fail(where, `${label} throws on ${JSON.stringify(sel)}: ${e.message}`);
return;
}
}
};
for (const dim of [...(config.matchDims || []), ...(config.overlayDims || [])]) {
if (typeof dim.showWhen === "function") probe(dim.showWhen, `${dim.id}.showWhen`);
for (const opt of (dim.options || [])) {
const tag = `${dim.id}.${opt.id}`;
if (typeof opt.showWhen === "function") probe(opt.showWhen, `${tag}.showWhen`);
if (typeof opt.disabled === "function") probe(opt.disabled, `${tag}.disabled`);
for (const key of ["flags", "env", "hints"]) {
if (typeof opt[key] !== "function") continue;
probe((sel) => {
const out = opt[key](sel);
if (out !== undefined && !Array.isArray(out)) throw new Error(`${key} returned ${typeof out}, expected an array`);
for (const f of (out || [])) {
if (typeof f !== "string") throw new Error(`${key} yielded a non-string entry`);
if (/undefined|NaN/.test(f)) throw new Error(`${key} produced "${f}"`);
}
}, `${tag}.${key}`);
}
}
}
if (typeof config.curl === "function") {
probe((sel) => {
const out = config.curl(sel, null);
if (typeof out !== "string") {
throw new Error(`curl returned ${typeof out}, expected a string`);
}
}, "curl");
}
}
if (failures.length) {
console.error(`FAIL (${failures.length})`);
for (const f of failures) console.error(" - " + f);
process.exit(1);
}
console.log("cookbook config check: OK");