import fs from 'fs';
import path from 'path';
import { Project, SyntaxKind } from 'ts-morph';
export interface ComponentInfo {
name: string;
filePath: string;
props: {
defaultValue: string;
description: string;
name: string;
type: string;
optional: boolean;
}[];
isDefaultExport?: boolean;
}
function isComponentFile(file: string): boolean {
return /\.(tsx|jsx)$/.test(file);
}
export function crawlComponents(rootDir = process.cwd(), baseDir = 'components'): ComponentInfo[] {
const dir = path.join(rootDir, baseDir);
if (!fs.existsSync(dir)) return [];
const results: ComponentInfo[] = [];
const project = new Project({
tsConfigFilePath: path.join(rootDir, 'tsconfig.json'),
skipAddingFilesFromTsConfig: false,
});
function walk(currentDir: string) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
walk(fullPath);
} else if (isComponentFile(entry.name)) {
project.addSourceFileAtPath(fullPath);
}
}
}
walk(dir);
// Analyze each file
project.getSourceFiles().forEach((sourceFile) => {
sourceFile.getExportedDeclarations().forEach((decls, exportName) => {
decls.forEach((decl) => {
if (
decl.getKind() === SyntaxKind.FunctionDeclaration ||
decl.getKind() === SyntaxKind.VariableDeclaration
) {
const name = exportName;
const props: {
name: string;
type: string;
optional: boolean;
defaultValue: string;
description: string;
}[] = [];
// Try to extract props from first parameter
if (decl.getKind() === SyntaxKind.FunctionDeclaration) {
const func = decl.asKindOrThrow(SyntaxKind.FunctionDeclaration);
const param = func.getParameters()[0];
if (param) {
const paramType = param.getType();
paramType.getProperties().forEach((prop) => {
props.push({
name: prop.getName(),
type: prop.getTypeAtLocation(param).getText(),
optional: prop.isOptional(),
defaultValue: '', // or provide logic to extract default value if available
description: '', // or provide logic to extract description if available
});
});
}
}
if (decl.getKind() === SyntaxKind.VariableDeclaration) {
const variable = decl.asKindOrThrow(SyntaxKind.VariableDeclaration);
const initializer = variable.getInitializer();
if (initializer && initializer.getKind() === SyntaxKind.ArrowFunction) {
const arrowFunc = initializer.asKindOrThrow(SyntaxKind.ArrowFunction);
const param = arrowFunc.getParameters()[0];
if (param) {
const paramType = param.getType();
paramType.getProperties().forEach((prop) => {
props.push({
name: prop.getName(),
type: prop.getTypeAtLocation(param).getText(),
optional: prop.isOptional(),
defaultValue: '', // or provide logic to extract default value if available
description: '', // or provide logic to extract description if available
});
});
}
}
}
results.push({
name,
filePath: sourceFile.getFilePath(),
props,
isDefaultExport: name === 'default',
});
}
});
});
});
return results;
}