UNPKG

@spaced-out/ui-design-system

Version:
408 lines (342 loc) • 12.7 kB
#!/usr/bin/env node /** * Build script to extract design system metadata into JSON * This runs before publishing to npm to bundle all component/token data */ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from 'fs'; import { join, resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Design system is parent directory const DESIGN_SYSTEM_PATH = resolve(__dirname, '..'); const OUTPUT_DIR = join(__dirname, 'data'); const OUTPUT_FILE = join(OUTPUT_DIR, 'design-system.json'); console.log('Building Genesis MCP data...\n'); console.log(`Design System: ${DESIGN_SYSTEM_PATH}`); console.log(`Output: ${OUTPUT_FILE}\n`); function safeReadFile(filePath) { try { return readFileSync(filePath, 'utf-8'); } catch (error) { return null; } } function getDirectories(path) { try { return readdirSync(path).filter(f => { const fullPath = join(path, f); return statSync(fullPath).isDirectory() && !f.startsWith('.'); }); } catch (error) { console.error(`āŒ Error reading directories in ${path}:`, error.message); return []; } } /** * Get all files recursively in a directory */ function getAllFilesRecursively(dirPath, filesList = [], baseDir = dirPath) { try { const files = readdirSync(dirPath); files.forEach(file => { if (file.startsWith('.')) return; const filePath = join(dirPath, file); const stat = statSync(filePath); if (stat.isDirectory()) { getAllFilesRecursively(filePath, filesList, baseDir); } else { const relativePath = filePath.replace(baseDir + '/', ''); filesList.push(relativePath); } }); } catch (error) { // Ignore errors for non-existent directories } return filesList; } /** * Extract all components with their files (including sub-components) * For main component files and sub-components, we use .d.ts files from lib/ * to avoid exposing source code in the public npm package */ function buildComponentsData() { console.log('šŸ“¦ Extracting components...'); const srcComponentsPath = join(DESIGN_SYSTEM_PATH, 'src/components'); const libComponentsPath = join(DESIGN_SYSTEM_PATH, 'lib/components'); const components = {}; if (!existsSync(srcComponentsPath)) { console.warn('āš ļø Components path not found'); return components; } const componentDirs = getDirectories(srcComponentsPath).filter(name => name !== 'index.ts'); for (const componentName of componentDirs) { const srcComponentDir = join(srcComponentsPath, componentName); const libComponentDir = join(libComponentsPath, componentName); // Get all files in the component directory (from src for reference) const allFiles = getAllFilesRecursively(srcComponentDir); // Extract main component .d.ts file from lib/ (type definitions only) const mainDts = join(libComponentDir, `${componentName}.d.ts`); const mainContent = safeReadFile(mainDts); // Extract story files (keep from src - useful for usage examples) const storyTsx = join(srcComponentDir, `${componentName}.stories.tsx`); const storyTs = join(srcComponentDir, `${componentName}.stories.ts`); const storyContent = safeReadFile(storyTsx) || safeReadFile(storyTs); // Extract CSS file (keep from src - needed for styling reference) const cssFile = join(srcComponentDir, `${componentName}.module.css`); const cssContent = safeReadFile(cssFile); // Extract index .d.ts file from lib/ const indexDts = join(libComponentDir, 'index.d.ts'); const indexContent = safeReadFile(indexDts); // Extract all additional sub-component .d.ts files from lib/ const additionalFiles = {}; const libAllFiles = existsSync(libComponentDir) ? getAllFilesRecursively(libComponentDir) : []; for (const file of libAllFiles) { // Skip main files we already extracted if ( file === `${componentName}.d.ts` || file === 'index.d.ts' || !file.endsWith('.d.ts') || file.endsWith('.d.ts.map') ) { continue; } // Include .d.ts files for sub-components const fullPath = join(libComponentDir, file); const content = safeReadFile(fullPath); if (content) { additionalFiles[file] = { path: file, content: content }; } } components[componentName] = { name: componentName, path: `src/components/${componentName}`, files: { main: mainContent ? { path: `${componentName}.d.ts`, content: mainContent } : null, story: storyContent ? { path: `${componentName}.stories.tsx`, content: storyContent } : null, css: cssContent ? { path: `${componentName}.module.css`, content: cssContent } : null, index: indexContent ? { path: 'index.d.ts', content: indexContent } : null, additional: additionalFiles, }, allFiles, }; } console.log(` āœ… Extracted ${Object.keys(components).length} components`); return components; } /** * Extract all hooks * For main hook files, we use .d.ts files from lib/ * to avoid exposing source code in the public npm package */ function buildHooksData() { console.log('šŸŖ Extracting hooks...'); const srcHooksPath = join(DESIGN_SYSTEM_PATH, 'src/hooks'); const libHooksPath = join(DESIGN_SYSTEM_PATH, 'lib/hooks'); const hooks = {}; if (!existsSync(srcHooksPath)) { console.warn('āš ļø Hooks path not found'); return hooks; } const hookDirs = getDirectories(srcHooksPath).filter(name => name !== 'index.ts'); for (const hookName of hookDirs) { const srcHookDir = join(srcHooksPath, hookName); const libHookDir = join(libHooksPath, hookName); // Read main hook .d.ts file from lib/ (type definitions only) const mainDts = join(libHookDir, `${hookName}.d.ts`); const mainContent = safeReadFile(mainDts); // Read story file (keep from src - useful for usage examples) const storyTsx = join(srcHookDir, `${hookName}.stories.tsx`); const storyTs = join(srcHookDir, `${hookName}.stories.ts`); const storyContent = safeReadFile(storyTsx) || safeReadFile(storyTs); // Read index .d.ts file from lib/ const indexDts = join(libHookDir, 'index.d.ts'); const indexContent = safeReadFile(indexDts); const allFiles = existsSync(srcHookDir) ? readdirSync(srcHookDir).filter(f => !f.startsWith('.')) : []; hooks[hookName] = { name: hookName, path: `src/hooks/${hookName}`, files: { main: mainContent ? { path: `${hookName}.d.ts`, content: mainContent } : null, story: storyContent ? { path: `${hookName}.stories.tsx`, content: storyContent } : null, index: indexContent ? { path: 'index.d.ts', content: indexContent } : null, }, allFiles, }; } console.log(` āœ… Extracted ${Object.keys(hooks).length} hooks`); return hooks; } /** * Extract all design tokens */ function buildTokensData() { console.log('šŸŽØ Extracting design tokens...'); const tokensPath = join(DESIGN_SYSTEM_PATH, 'design-tokens'); const tokens = {}; if (!existsSync(tokensPath)) { console.warn('āš ļø Design tokens path not found'); return tokens; } const categories = getDirectories(tokensPath); for (const category of categories) { tokens[category] = {}; const categoryPath = join(tokensPath, category); try { const files = readdirSync(categoryPath).filter(f => f.endsWith('.json')); for (const file of files) { const filePath = join(categoryPath, file); try { const content = readFileSync(filePath, 'utf-8'); const tokenData = JSON.parse(content); tokens[category][file.replace('.json', '')] = tokenData; } catch (error) { console.warn(` āš ļø Error parsing ${file}:`, error.message); } } } catch (error) { console.warn(` āš ļø Error reading category ${category}:`, error.message); } } const tokenCount = Object.values(tokens).reduce((sum, cat) => sum + Object.keys(cat).length, 0); console.log(` āœ… Extracted ${tokenCount} token files across ${Object.keys(tokens).length} categories`); return tokens; } /** * Extract all utils * For util files, we use .d.ts files from lib/ * to avoid exposing source code in the public npm package */ function buildUtilsData() { console.log('šŸ”§ Extracting utils...'); const srcUtilsPath = join(DESIGN_SYSTEM_PATH, 'src/utils'); const libUtilsPath = join(DESIGN_SYSTEM_PATH, 'lib/utils'); const utils = {}; if (!existsSync(srcUtilsPath)) { console.warn('āš ļø Utils path not found'); return utils; } const utilDirs = getDirectories(srcUtilsPath); for (const utilName of utilDirs) { const srcUtilDir = join(srcUtilsPath, utilName); const libUtilDir = join(libUtilsPath, utilName); // Get all files in the util directory (from src for reference) const allFiles = getAllFilesRecursively(srcUtilDir); // Read all .d.ts files from lib/ const files = {}; const libAllFiles = existsSync(libUtilDir) ? getAllFilesRecursively(libUtilDir) : []; for (const file of libAllFiles) { // Only include .d.ts files (skip .js and .d.ts.map) if (file.endsWith('.d.ts') && !file.endsWith('.d.ts.map')) { const fullPath = join(libUtilDir, file); const content = safeReadFile(fullPath); if (content) { files[file] = { path: file, content: content }; } } } utils[utilName] = { name: utilName, path: `src/utils/${utilName}`, files: files, allFiles: allFiles, }; } console.log(` āœ… Extracted ${Object.keys(utils).length} util modules`); return utils; } /** * Extract all types */ function buildTypesData() { console.log('šŸ“ Extracting types...'); const typesPath = join(DESIGN_SYSTEM_PATH, 'src/types'); const types = {}; if (!existsSync(typesPath)) { console.warn('āš ļø Types path not found'); return types; } try { const files = readdirSync(typesPath).filter(f => !f.startsWith('.') && (f.endsWith('.ts') || f.endsWith('.tsx')) && !f.endsWith('.d.ts') ); for (const file of files) { const filePath = join(typesPath, file); const content = safeReadFile(filePath); if (content) { const typeName = file.replace(/\.(ts|tsx)$/, ''); types[typeName] = { name: typeName, path: `src/types/${file}`, content: content, }; } } } catch (error) { console.warn(` āš ļø Error reading types:`, error.message); } console.log(` āœ… Extracted ${Object.keys(types).length} type files`); return types; } /** * Get package version */ function getVersion() { try { const pkgPath = join(DESIGN_SYSTEM_PATH, 'package.json'); const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); return pkg.version || '0.0.0'; } catch (error) { return '0.0.0'; } } /** * Main build function */ function build() { try { const data = { metadata: { buildDate: new Date().toISOString(), version: getVersion(), designSystemPath: DESIGN_SYSTEM_PATH, }, components: buildComponentsData(), hooks: buildHooksData(), tokens: buildTokensData(), utils: buildUtilsData(), types: buildTypesData(), }; // Create output directory if it doesn't exist if (!existsSync(OUTPUT_DIR)) { mkdirSync(OUTPUT_DIR, { recursive: true }); } // Write to JSON file writeFileSync(OUTPUT_FILE, JSON.stringify(data, null, 2)); console.log('\n✨ Build complete!'); console.log(`šŸ“Š Summary:`); console.log(` - Components: ${Object.keys(data.components).length}`); console.log(` - Hooks: ${Object.keys(data.hooks).length}`); console.log(` - Token categories: ${Object.keys(data.tokens).length}`); console.log(` - Utils: ${Object.keys(data.utils).length}`); console.log(` - Types: ${Object.keys(data.types).length}`); console.log(` - Version: ${data.metadata.version}`); console.log(` - Output: ${OUTPUT_FILE}`); console.log(` - Size: ${(Buffer.byteLength(JSON.stringify(data)) / 1024 / 1024).toFixed(2)} MB\n`); } catch (error) { console.error('āŒ Build failed:', error); process.exit(1); } } // Run the build build();