bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
243 lines (242 loc) • 9.28 kB
JavaScript
/**
* 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 };