243 lines
9.5 KiB
JavaScript
243 lines
9.5 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Read the original registry
|
|
const registryPath = path.join(__dirname, 'registry.json');
|
|
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
|
|
// Create output directories
|
|
const registryDir = path.join(__dirname, 'registry');
|
|
const componentsDir = path.join(registryDir, 'components');
|
|
const schemasDir = path.join(registryDir, 'schemas');
|
|
|
|
[componentsDir, schemasDir].forEach(dir => {
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
});
|
|
|
|
// Extract intent from description/details
|
|
function extractIntent(description, details, name) {
|
|
const text = `${description} ${details}`.toLowerCase();
|
|
|
|
// More specific intent extraction
|
|
if (text.includes('vertical stack') && text.includes('alternating')) return 'sequential vertical steps with alternating layout';
|
|
if (text.includes('horizontal') && text.includes('timeline')) return 'horizontal timeline';
|
|
if (text.includes('vertical') && text.includes('sticky') && text.includes('scroll')) return 'vertical sticky scroll timeline';
|
|
if (text.includes('hero') || text.includes('landing')) return 'hero with media';
|
|
if (text.includes('sequential') || (text.includes('step') && text.includes('process'))) return 'sequential process';
|
|
if (text.includes('feature') && !text.includes('hero')) return 'feature showcase';
|
|
if (text.includes('pricing') || text.includes('plan')) return 'pricing plans';
|
|
if (text.includes('testimonial') || text.includes('review')) return 'testimonials';
|
|
if (text.includes('about') || text.includes('company')) return 'about section';
|
|
if (text.includes('blog') || text.includes('article')) return 'content listing';
|
|
if (text.includes('contact') || text.includes('form')) return 'contact form';
|
|
if (text.includes('faq') || text.includes('question')) return 'faq section';
|
|
if (text.includes('footer')) return 'footer';
|
|
if (text.includes('metric') || text.includes('kpi') || text.includes('statistic')) return 'metrics display';
|
|
if (text.includes('product') && !text.includes('feature')) return 'product showcase';
|
|
if (text.includes('team') || text.includes('member')) return 'team section';
|
|
if (text.includes('carousel') || text.includes('gallery')) return 'media carousel';
|
|
if (text.includes('grid') && text.includes('card')) return 'card grid';
|
|
if (text.includes('timeline')) return 'timeline';
|
|
if (text.includes('split') && text.includes('layout')) return 'split layout';
|
|
if (text.includes('background')) return 'background';
|
|
if (text.includes('button')) return 'button';
|
|
if (text.includes('navbar') || text.includes('navigation')) return 'navigation';
|
|
if (text.includes('text') && !text.includes('button')) return 'text component';
|
|
if (text.includes('form') || text.includes('input')) return 'form';
|
|
|
|
return 'general component';
|
|
}
|
|
|
|
// Extract bestFor from description/details
|
|
function extractBestFor(description, details) {
|
|
const text = `${description} ${details}`.toLowerCase();
|
|
const bestFor = [];
|
|
|
|
if (text.includes('landing page') || text.includes('hero')) bestFor.push('landing pages');
|
|
if (text.includes('process') || text.includes('step')) bestFor.push('process flows', 'roadmaps', 'step-by-step explanation');
|
|
if (text.includes('feature')) bestFor.push('feature showcases', 'capability displays');
|
|
if (text.includes('pricing')) bestFor.push('pricing pages', 'subscription tiers');
|
|
if (text.includes('portfolio') || text.includes('gallery')) bestFor.push('portfolios', 'image galleries');
|
|
if (text.includes('testimonial')) bestFor.push('social proof', 'customer reviews');
|
|
if (text.includes('about')) bestFor.push('about pages', 'company information');
|
|
if (text.includes('blog')) bestFor.push('blog listings', 'article grids');
|
|
if (text.includes('contact')) bestFor.push('contact pages', 'lead generation');
|
|
if (text.includes('faq')) bestFor.push('help pages', 'support sections');
|
|
if (text.includes('metric') || text.includes('kpi')) bestFor.push('statistics displays', 'achievement showcases');
|
|
if (text.includes('product')) bestFor.push('product catalogs', 'e-commerce');
|
|
if (text.includes('team')) bestFor.push('team pages', 'staff directories');
|
|
|
|
return bestFor.length > 0 ? bestFor : ['general use'];
|
|
}
|
|
|
|
// Extract avoidWhen from constraints and details
|
|
function extractAvoidWhen(details, constraints) {
|
|
const avoidWhen = [];
|
|
const text = details.toLowerCase();
|
|
|
|
if (text.includes('requires') && text.includes('5+')) avoidWhen.push('less than 5 items');
|
|
if (text.includes('requires') && text.includes('3-5')) avoidWhen.push('less than 3 items', 'more than 5 items');
|
|
if (text.includes('single') && !text.includes('multiple')) avoidWhen.push('multiple items');
|
|
if (text.includes('sequential') || text.includes('step')) avoidWhen.push('non-sequential content', 'single item');
|
|
if (text.includes('grid') && !text.includes('carousel')) avoidWhen.push('more than 4 items');
|
|
if (text.includes('carousel') && !text.includes('grid')) avoidWhen.push('less than 5 items');
|
|
|
|
if (constraints?.itemRules) {
|
|
if (constraints.itemRules.minItems > 1) {
|
|
avoidWhen.push(`less than ${constraints.itemRules.minItems} items`);
|
|
}
|
|
if (constraints.itemRules.maxItems) {
|
|
avoidWhen.push(`more than ${constraints.itemRules.maxItems} items`);
|
|
}
|
|
}
|
|
|
|
return avoidWhen.length > 0 ? avoidWhen : [];
|
|
}
|
|
|
|
// Extract requires from constraints and propsSchema
|
|
function extractRequires(constraints, propsSchema) {
|
|
const requires = [];
|
|
|
|
// Check propsSchema for array props (main data requirements)
|
|
if (propsSchema) {
|
|
for (const [key, value] of Object.entries(propsSchema)) {
|
|
if (typeof value === 'string' && value.includes('Array') && !key.includes('ClassName') && key !== 'className') {
|
|
requires.push(`${key}[]`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add item constraints if no array props found
|
|
if (requires.length === 0 && constraints?.itemRules?.minItems) {
|
|
requires.push(`minimum ${constraints.itemRules.minItems} items`);
|
|
}
|
|
|
|
return requires;
|
|
}
|
|
|
|
// Extract import path from import statement
|
|
function extractImportPath(importStatement) {
|
|
if (!importStatement) return '';
|
|
// Extract path from: import Component from '@/path/to/Component';
|
|
const match = importStatement.match(/from\s+['"]([^'"]+)['"]/);
|
|
return match ? match[1] : importStatement;
|
|
}
|
|
|
|
// Simplify propsSchema - remove className props
|
|
function simplifyPropsSchema(propsSchema) {
|
|
if (!propsSchema) return {};
|
|
|
|
const simplified = {};
|
|
for (const [key, value] of Object.entries(propsSchema)) {
|
|
// Skip className props
|
|
if (!key.includes('ClassName') && key !== 'className') {
|
|
simplified[key] = value;
|
|
}
|
|
}
|
|
return simplified;
|
|
}
|
|
|
|
// Process all components
|
|
const indexData = {};
|
|
const intentsMap = {};
|
|
const allComponents = [];
|
|
|
|
// Process componentRegistry
|
|
Object.keys(registry.componentRegistry || {}).forEach(category => {
|
|
registry.componentRegistry[category].forEach(component => {
|
|
allComponents.push({ ...component, category, type: 'component' });
|
|
});
|
|
});
|
|
|
|
// Process sectionRegistry
|
|
Object.keys(registry.sectionRegistry || {}).forEach(category => {
|
|
registry.sectionRegistry[category].forEach(component => {
|
|
allComponents.push({ ...component, category, type: 'section' });
|
|
});
|
|
});
|
|
|
|
// Process each component
|
|
allComponents.forEach(component => {
|
|
const name = component.name;
|
|
const intent = extractIntent(component.description, component.details, name);
|
|
const bestFor = extractBestFor(component.description, component.details);
|
|
const avoidWhen = extractAvoidWhen(component.details, component.constraints);
|
|
const requires = extractRequires(component.constraints, component.propsSchema);
|
|
const importPath = extractImportPath(component.import);
|
|
|
|
// Add to index.json (lightweight catalog)
|
|
indexData[name] = {
|
|
category: component.category,
|
|
intent: intent,
|
|
bestFor: bestFor,
|
|
avoidWhen: avoidWhen,
|
|
requires: requires,
|
|
import: importPath
|
|
};
|
|
|
|
// Add to intents map
|
|
if (!intentsMap[intent]) {
|
|
intentsMap[intent] = [];
|
|
}
|
|
if (!intentsMap[intent].includes(name)) {
|
|
intentsMap[intent].push(name);
|
|
}
|
|
|
|
// Create component detail file
|
|
const componentData = {
|
|
name: name,
|
|
description: component.description,
|
|
constraints: component.constraints || {},
|
|
propsSchema: simplifyPropsSchema(component.propsSchema),
|
|
usageExample: component.usage || '',
|
|
do: [
|
|
...bestFor.map(bf => `Use for ${bf}`),
|
|
...requires.map(r => `Requires ${r}`)
|
|
],
|
|
dont: [
|
|
...avoidWhen.map(aw => `Do not use ${aw}`)
|
|
],
|
|
editRules: {
|
|
textOnly: true,
|
|
layoutLocked: true,
|
|
styleLocked: true
|
|
}
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(componentsDir, `${name}.json`),
|
|
JSON.stringify(componentData, null, 2)
|
|
);
|
|
|
|
// Create schema file (full propsSchema)
|
|
const schemaData = {
|
|
name: name,
|
|
propsSchema: component.propsSchema || {}
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(schemasDir, `${name}.schema.json`),
|
|
JSON.stringify(schemaData, null, 2)
|
|
);
|
|
});
|
|
|
|
// Write index.json
|
|
fs.writeFileSync(
|
|
path.join(registryDir, 'index.json'),
|
|
JSON.stringify(indexData, null, 2)
|
|
);
|
|
|
|
// Write intents.json
|
|
fs.writeFileSync(
|
|
path.join(registryDir, 'intents.json'),
|
|
JSON.stringify(intentsMap, null, 2)
|
|
);
|
|
|
|
console.log(`✅ Created LLM-friendly registry structure:`);
|
|
console.log(` - ${Object.keys(indexData).length} components in index.json`);
|
|
console.log(` - ${Object.keys(intentsMap).length} intent mappings in intents.json`);
|
|
console.log(` - ${allComponents.length} component detail files`);
|
|
console.log(` - ${allComponents.length} schema files`);
|