UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

243 lines (242 loc) 9.28 kB
#!/usr/bin/env node /** * Generate registry.json from component files * This script scans the src/components directory and creates a registry file */ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); function extractDescriptionFromFile(filePath) { try { const content = readFileSync(filePath, "utf-8"); // Look for JSDoc description const jsDocMatch = content.match(/\/\*\*\s*\n\s*\*\s*(.+?)\s*\n/); if (jsDocMatch?.[1]) { return jsDocMatch[1]; } // Look for component description comment const commentMatch = content.match(/\/\/\s*(.+?)\s*\n/); if (commentMatch?.[1]) { return commentMatch[1]; } // Look for interface description const interfaceMatch = content.match(/interface\s+\w+Props.*?\{[\s\S]*?\/\*\*\s*\n\s*\*\s*(.+?)\s*\n/); if (interfaceMatch?.[1]) { return interfaceMatch[1]; } // Default description based on component name const componentNameMatch = content.match(/export\s+(?:function|const)\s+(\w+)/); if (componentNameMatch) { const componentName = componentNameMatch[1]; return `${componentName} component for Bitcoin applications`; } return "Bitcoin UI component"; } catch { return "Bitcoin UI component"; } } function getComponentType(dir) { const typeMap = { authentication: "component:auth", social: "component:social", wallet: "component:wallet", market: "component:market", profiles: "component:profile", backup: "component:backup", ui: "component:ui", providers: "component:provider", layout: "component:layout", "bap-identity": "component:bap", security: "component:security", "developer-tools": "component:developer", hooks: "hook:utility", }; return typeMap[dir] || "component:other"; } function extractDependencies(filePath) { try { const content = readFileSync(filePath, "utf-8"); const dependencies = new Set(); // Extract npm package imports const importMatches = content.matchAll(/import.*?from\s+['"]([^'"]+)['"]/g); for (const match of importMatches) { const importPath = match[1]; // Skip relative imports or undefined paths if (!importPath || importPath.startsWith(".") || importPath.startsWith("@/")) { continue; } // Get base package name (handle scoped packages and sub-imports) const packageName = importPath.startsWith("@") ? importPath .split("/") .slice(0, 2) .join("/") // @scope/package : importPath.split("/")[0]; // package // Known BigBlocks dependencies const knownDeps = [ "@bsv/sdk", "bitcoin-auth", "bitcoin-backup", "bsv-bap", "@radix-ui/react-icons", "qrious", "js-1sat-ord", "@tanstack/react-query", "react", "react-dom", "lucide-react", "bmap-api-types", "shiki", ]; if (packageName && knownDeps.includes(packageName)) { dependencies.add(packageName); } } return Array.from(dependencies); } catch { return []; } } function extractRegistryDependencies(filePath, allComponentNames) { try { const content = readFileSync(filePath, "utf-8"); const registryDeps = new Set(); // Look for component imports from relative paths const importMatches = content.matchAll(/import.*?\{([^}]+)\}.*?from\s+['"]\.\.?\/[^'"]*['"]/g); for (const match of importMatches) { const imports = match[1]; if (!imports) continue; const componentNames = imports .split(",") .map((name) => name.trim().replace(/\s+as\s+.+/, "")) .filter((name) => name.length > 0); for (const componentName of componentNames) { // Convert PascalCase to kebab-case const kebabName = componentName .replace(/([a-z])([A-Z])/g, "$1-$2") .toLowerCase(); if (allComponentNames.has(kebabName)) { registryDeps.add(kebabName); } } } return Array.from(registryDeps); } catch { return []; } } function scanComponents() { const components = []; const srcDir = join(__dirname, "..", "src", "components"); if (!existsSync(srcDir)) { console.error("Components directory not found"); return []; } // First pass: collect all component names const allComponentNames = new Set(); const componentFiles = []; const directories = readdirSync(srcDir, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name); for (const dir of directories) { const dirPath = join(srcDir, dir); const componentType = getComponentType(dir); // Scan for .tsx and .ts files const files = readdirSync(dirPath, { withFileTypes: true }) .filter((dirent) => dirent.isFile() && (dirent.name.endsWith(".tsx") || dirent.name.endsWith(".ts"))) .map((dirent) => dirent.name); for (const file of files) { // Skip index files and stories if (file === "index.ts" || file === "index.tsx" || file.includes(".stories.") || file.includes(".test.")) { continue; } const componentName = file .replace(/\.(tsx?|ts)$/, "") .toLowerCase() .replace(/([a-z])([A-Z])/g, "$1-$2"); allComponentNames.add(componentName); componentFiles.push({ dir, file, type: componentType }); } } // Second pass: extract dependencies with knowledge of all component names for (const { dir, file, type } of componentFiles) { const dirPath = join(srcDir, dir); const filePath = join(dirPath, file); const componentName = file .replace(/\.(tsx?|ts)$/, "") .toLowerCase() .replace(/([a-z])([A-Z])/g, "$1-$2"); const description = extractDescriptionFromFile(filePath); const dependencies = extractDependencies(filePath); const registryDependencies = extractRegistryDependencies(filePath, allComponentNames); components.push({ name: componentName, type: type, files: [`src/components/${dir}/${file}`], dependencies, registryDependencies, description, }); } return components; } function generateRegistry() { console.log("🔍 Scanning components..."); const components = scanComponents(); console.log(`📦 Found ${components.length} components`); // Read version from package.json let version = "0.0.13"; // fallback const packagePath = join(__dirname, "..", "package.json"); if (existsSync(packagePath)) { try { const pkg = JSON.parse(readFileSync(packagePath, "utf-8")); version = pkg.version || version; } catch { console.log("⚠️ Could not read version from package.json, using fallback"); } } const registry = { $schema: "https://ui.shadcn.com/schema.json", name: "bigblocks", type: "registry", version, homepage: "https://github.com/b-open-io/bigblocks", dependencies: ["@bsv/sdk", "bitcoin-auth", "bitcoin-backup", "bsv-bap"], devDependencies: [], components: components.sort((a, b) => a.name.localeCompare(b.name)), }; const registryPath = join(__dirname, "..", "registry", "registry.json"); writeFileSync(registryPath, JSON.stringify(registry, null, 2)); console.log(`✅ Registry generated: ${registryPath}`); console.log(`📊 Version: ${version}`); console.log("📊 Components by type:"); const typeCount = components.reduce((acc, comp) => { const type = comp.type.split(":")[1] || "other"; acc[type] = (acc[type] || 0) + 1; return acc; }, {}); for (const [type, count] of Object.entries(typeCount)) { console.log(` ${type}: ${count}`); } console.log(""); console.log(`🔗 Registry dependencies automatically detected for ${components.filter((c) => c.registryDependencies.length > 0).length} components`); } // Run if called directly if (import.meta.url === `file://${process.argv[1]}`) { generateRegistry(); } export { generateRegistry };