feat(site): add util reference pipeline (#537)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-02-24 15:34:34 -06:00
committed by GitHub
co-authored by Claude Opus 4.6
parent c11395ece1
commit 78112fbefd
143 changed files with 7031 additions and 481 deletions
+45 -25
View File
@@ -4,20 +4,22 @@ import * as ts from 'typescript';
import * as tae from 'typescript-api-extractor';
import { extractCore } from './core-handler.js';
import { extractDataAttrs } from './data-attrs-handler.js';
import { abbreviateType } from './formatter.js';
import { extractHtml } from './html-handler.js';
import { extractPartDescription, extractParts } from './parts-handler.js';
import {
type ComponentApiReference,
ComponentApiReferenceSchema,
type ComponentReference,
ComponentReferenceSchema,
type ComponentSource,
type CoreExtraction,
type DataAttrDef,
type DataAttrsExtraction,
type PartApiReference,
type PartReference,
type PartSource,
type PropDef,
type StateDef,
} from './types.js';
import { generateUtilReferences } from './util-handler.js';
import { kebabToPascal, partKebabFromSource, sortProps } from './utils.js';
// Components whose PascalCase name doesn't match simple kebab-to-pascal conversion.
@@ -30,13 +32,13 @@ function buildProps(coreData: CoreExtraction): Record<string, PropDef> {
for (const prop of coreData.props) {
props[prop.name] = {
type: prop.type,
shortType: prop.shortType,
detailedType: prop.detailedType,
description: prop.description,
default: coreData.defaultProps[prop.name] ?? prop.default,
required: prop.required,
};
if (props[prop.name]!.shortType === undefined) delete props[prop.name]!.shortType;
if (props[prop.name]!.detailedType === undefined) delete props[prop.name]!.detailedType;
if (props[prop.name]!.description === undefined) delete props[prop.name]!.description;
if (props[prop.name]!.default === undefined) delete props[prop.name]!.default;
if (!props[prop.name]!.required) delete props[prop.name]!.required;
@@ -49,10 +51,10 @@ function buildState(coreData: CoreExtraction): Record<string, StateDef> {
for (const s of coreData.state) {
state[s.name] = {
type: s.type,
shortType: s.shortType,
detailedType: s.detailedType,
description: s.description,
};
if (state[s.name]!.shortType === undefined) delete state[s.name]!.shortType;
if (state[s.name]!.detailedType === undefined) delete state[s.name]!.detailedType;
if (state[s.name]!.description === undefined) delete state[s.name]!.description;
}
return state;
@@ -61,7 +63,17 @@ function buildState(coreData: CoreExtraction): Record<string, StateDef> {
function buildDataAttrs(dataAttrsData: DataAttrsExtraction): Record<string, DataAttrDef> {
const dataAttributes: Record<string, DataAttrDef> = {};
for (const attr of dataAttrsData.attrs) {
dataAttributes[attr.name] = { description: attr.description };
const def: DataAttrDef = { description: attr.description };
if (attr.type) {
const abbreviated = abbreviateType(attr.name, attr.type);
if (abbreviated) {
def.type = abbreviated;
def.detailedType = attr.type;
} else {
def.type = attr.type;
}
}
dataAttributes[attr.name] = def;
}
return dataAttributes;
}
@@ -81,7 +93,8 @@ const MONOREPO_ROOT = path.resolve(import.meta.dirname, '../../../../');
const CORE_UI_PATH = path.join(MONOREPO_ROOT, 'packages/core/src/core/ui');
const HTML_UI_PATH = path.join(MONOREPO_ROOT, 'packages/html/src/ui');
const REACT_UI_PATH = path.join(MONOREPO_ROOT, 'packages/react/src/ui');
const OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-api-reference');
const COMPONENT_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-component-reference');
const UTIL_OUTPUT_PATH = path.join(MONOREPO_ROOT, 'site/src/content/generated-util-reference');
/**
* Discover all components by scanning the core/ui directory.
@@ -191,7 +204,7 @@ function createProgram(sources: ComponentSource[]): ts.Program {
/**
* Build the API reference for a single-part component.
*/
function buildSingleComponentApiReference(source: ComponentSource, program: ts.Program): ComponentApiReference | null {
function buildSingleComponentReference(source: ComponentSource, program: ts.Program): ComponentReference | null {
// Extract from core
const coreData = source.corePath ? extractCore(source.corePath, program, source.name) : null;
@@ -207,7 +220,7 @@ function buildSingleComponentApiReference(source: ComponentSource, program: ts.P
const htmlData = source.htmlPath ? extractHtml(source.htmlPath, program, source.name) : null;
// Build result
const result: ComponentApiReference = {
const result: ComponentReference = {
name: source.name,
description: coreData.description,
props: buildProps(coreData),
@@ -312,12 +325,12 @@ function discoverParts(source: ComponentSource, program: ts.Program): PartSource
* - Props, state, and data attributes are empty (no dedicated core file)
* - HTML tag comes from their sub-part element file (`{name}-{part}-element.ts`)
*/
function buildMultiPartApiReference(
function buildMultiPartReference(
source: ComponentSource,
program: ts.Program,
parts: PartSource[]
): ComponentApiReference | null {
const partsRecord: Record<string, PartApiReference> = {};
): ComponentReference | null {
const partsRecord: Record<string, PartReference> = {};
for (const part of parts) {
// Extract JSDoc description from React component file
@@ -334,7 +347,7 @@ function buildMultiPartApiReference(
const elementName = `${source.name}Element`;
const htmlData = part.htmlPath ? extractHtml(part.htmlPath, program, source.name, elementName) : null;
const partRef: PartApiReference = {
const partRef: PartReference = {
name: part.name,
description,
props: coreData ? sortProps(buildProps(coreData)) : {},
@@ -354,7 +367,7 @@ function buildMultiPartApiReference(
const elementName = `${source.name}${part.name}Element`;
const htmlData = part.htmlPath ? extractHtml(part.htmlPath, program, source.name, elementName) : null;
const partRef: PartApiReference = {
const partRef: PartReference = {
name: part.name,
description,
props: {},
@@ -385,15 +398,15 @@ function buildMultiPartApiReference(
/**
* Build the API reference for a single component.
*/
function buildComponentApiReference(source: ComponentSource, program: ts.Program): ComponentApiReference | null {
function buildComponentReference(source: ComponentSource, program: ts.Program): ComponentReference | null {
if (source.partsIndexPath) {
const parts = discoverParts(source, program);
if (parts.length > 0) {
return buildMultiPartApiReference(source, program, parts);
return buildMultiPartReference(source, program, parts);
}
}
return buildSingleComponentApiReference(source, program);
return buildSingleComponentReference(source, program);
}
/**
@@ -412,8 +425,8 @@ function main() {
};
// Ensure output directory exists
if (!fs.existsSync(OUTPUT_PATH)) {
fs.mkdirSync(OUTPUT_PATH, { recursive: true });
if (!fs.existsSync(COMPONENT_OUTPUT_PATH)) {
fs.mkdirSync(COMPONENT_OUTPUT_PATH, { recursive: true });
}
// Discover components
@@ -434,14 +447,14 @@ function main() {
for (const source of components) {
try {
const apiRef = buildComponentApiReference(source, program);
const apiRef = buildComponentReference(source, program);
if (apiRef) {
// Sort props (top-level only for single-part)
apiRef.props = sortProps(apiRef.props);
// Validate against schema before writing
const validated = ComponentApiReferenceSchema.safeParse(apiRef);
const validated = ComponentReferenceSchema.safeParse(apiRef);
if (!validated.success) {
log.error(`Schema validation failed for ${source.name}:`);
for (const issue of validated.error.issues) {
@@ -452,7 +465,7 @@ function main() {
}
// Write JSON file
const outputFile = path.join(OUTPUT_PATH, `${source.kebab}.json`);
const outputFile = path.join(COMPONENT_OUTPUT_PATH, `${source.kebab}.json`);
const json = `${JSON.stringify(validated.data, null, 2)}\n`;
fs.writeFileSync(outputFile, json);
@@ -465,7 +478,14 @@ function main() {
}
}
log.info(`Done! Generated ${successCount} files.`);
log.info(`Done! Generated ${successCount} component files.`);
// Generate util references
const utilResult = generateUtilReferences(UTIL_OUTPUT_PATH, MONOREPO_ROOT);
successCount += utilResult.success;
errorCount += utilResult.errors;
log.info(`Done! Generated ${utilResult.success} util files.`);
console.warn = originalWarn;