Files
shadowdaoandClaude Sonnet 5.5 de240eee75 feat(sitesmith): catalog generator from real component/section/template definitions + drift test
craft/scripts/gen-sitesmith-catalog.mjs emits a compact JSON catalog (props with
TS types/enums/defaults, token-aware colour props, animation + icon support,
child rules, section recipes, templates, palettes) for the WHP panel prompt.
src/sitesmith/catalog.drift.test.ts regenerates and compares to the committed
whp file so drift fails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 09:52:49 -08:00

59 lines
2.8 KiB
TypeScript

// Bundled by scripts/gen-sitesmith-catalog.mjs (esbuild, platform node) and run
// in Node. Collects everything that only exists at RUNTIME in the editor's real
// definitions: component craft defaults/rules, token vocabulary, palettes,
// section families and templates. The TypeScript prop TYPES come from the
// compiler API in the .mjs (types are erased at runtime).
import { componentResolver } from '../../src/components/resolver';
import { TOKEN_VARS, COLOR_TOKENS } from '../../src/utils/tokens';
import { PALETTES } from '../../src/constants/palettes';
import { BUILTIN_SECTIONS } from '../../src/sections/families';
import { SECTION_FAMILIES } from '../../src/sections/catalog';
import { allTemplates } from '../../src/templates/definitions';
import { CANVAS_TYPES, SHELL_INNER } from '../../src/utils/craft-tree';
import { DEFAULT_SITE_DESIGN } from '../../src/state/SiteDesignContext';
type Tpl = { type: string; props: Record<string, any>; children?: Tpl[] };
/** "Section[Heading,TextBlock,ButtonLink]": the structural skeleton of a tree. */
function recipe(n: Tpl): string {
const kids = (n.children ?? []).map(recipe);
// collapse runs of identical siblings: Container*3
const out: string[] = [];
for (const k of kids) {
const last = out[out.length - 1];
const m = last && last.match(/^(.*)\*(\d+)$/);
if (last === k) out[out.length - 1] = `${k}*2`;
else if (m && m[1] === k) out[out.length - 1] = `${k}*${Number(m[2]) + 1}`;
else out.push(k);
}
return out.length ? `${n.type}[${out.join(',')}]` : n.type;
}
export function collect() {
const components: Record<string, any> = {};
for (const [name, comp] of Object.entries(componentResolver) as [string, any][]) {
components[name] = {
defaults: comp.craft?.props ?? {},
displayName: comp.craft?.displayName ?? name,
isCanvas: CANVAS_TYPES.has(name),
shellInner: SHELL_INNER[name] ?? null,
canMoveInFalse: false,
};
}
return {
components,
tokenVars: TOKEN_VARS.map(([v, k]) => ({ var: v, designKey: k, default: (DEFAULT_SITE_DESIGN as any)[k] })),
colorTokens: COLOR_TOKENS.map((t) => t.key),
palettes: PALETTES.map((p) => ({ id: p.id, name: p.name, ...p.design, fonts: p.fonts ?? null })),
families: SECTION_FAMILIES.filter((f) => f.id !== 'custom'),
sections: BUILTIN_SECTIONS.map((s) => ({ id: s.id, name: s.name, family: s.family, keywords: s.keywords, recipe: recipe(s.tree as Tpl) })),
templates: allTemplates.map((t) => ({
id: t.id, name: t.name, category: t.category, description: t.description, multiPage: t.isMultiPage,
design: t.design,
pages: t.pages.map((p) => ({ name: p.name, slug: p.slug, recipe: p.content.components.map((c) => recipe(c as Tpl)) })),
header: t.header.components.map((c) => recipe(c as Tpl)),
footer: t.footer.components.map((c) => recipe(c as Tpl)),
})),
};
}