import { exec } from "child_process" import { existsSync, promises as fs } from "fs" import path from "path" import { rimraf } from "rimraf" import { getAllBlocks } from "@/lib/blocks" import { registry } from "@/registry/index" async function buildRegistryIndex() { let index = `/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ // @ts-nocheck // This file is autogenerated by scripts/build-registry.ts // Do not edit this file directly. import * as React from "react" export const Index: Record = {` for (const item of registry.items) { const resolveFiles = item.files?.map( (file) => `registry/creative-tim/${file.path}` ) if (!resolveFiles) { continue } const componentPath = item.files?.[0]?.path ? `@/registry/creative-tim/${item.files[0].path}` : "" index += ` "${item.name}": { name: "${item.name}", description: "${item.description ?? ""}", type: "${item.type}", registryDependencies: ${JSON.stringify(item.registryDependencies)}, files: [${item.files?.map((file) => { const filePath = `registry/creative-tim/${typeof file === "string" ? file : file.path}` const resolvedFilePath = path.resolve(filePath) return typeof file === "string" ? `"${resolvedFilePath}"` : `{ path: "${filePath}", type: "${file.type}", target: "${file.target ?? ""}" }` })}], component: ${ componentPath ? `React.lazy(async () => { const mod = await import("${componentPath}") const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name return { default: mod.default || mod[exportName] } })` : "null" }, categories: ${JSON.stringify(item.categories)}, meta: ${JSON.stringify(item.meta)}, },` } index += ` }` console.log(`#️⃣ ${Object.keys(registry.items).length} items found`) // Write style index. rimraf.sync(path.join(process.cwd(), "registry/__index__.tsx")) await fs.writeFile(path.join(process.cwd(), "registry/__index__.tsx"), index) } async function buildRegistryJsonFile() { // 1. Add registry/creative-tim prefix for the build to work const fixedRegistry = { ...registry, items: registry.items.map((item) => { const files = item.files?.map((file) => { return { ...file, path: `registry/creative-tim/${file.path}`, } }) return { ...item, files, } }), } // 2. Write the content of the registry to `registry.json` rimraf.sync(path.join(process.cwd(), `registry.json`)) await fs.writeFile( path.join(process.cwd(), `registry.json`), JSON.stringify(fixedRegistry, null, 2) ) // 3. Format the registry.json file. await exec(`prettier --write registry.json`) // 3. Copy the registry.json to the www/public/r/styles/creative-tim directory. await fs.cp( path.join(process.cwd(), "registry.json"), path.join( process.cwd(), "../www/public/r/styles/creative-tim/registry.json" ), { recursive: true } ) } async function buildRegistry() { return new Promise((resolve, reject) => { exec( `pnpm dlx shadcn build registry.json --output public/r`, async (error, stdout, stderr) => { if (error) { console.error(`Error: ${error.message}`) reject(error) return } if (stderr) { console.error(`stderr: ${stderr}`) } if (stdout) { console.log(stdout) } // Post-process the generated JSON files to remove registry/creative-tim prefix console.log("📝 Cleaning up paths in generated JSON files...") await cleanupGeneratedPaths() resolve(undefined) } ) }) } async function cleanupGeneratedPaths() { const publicDir = path.join(process.cwd(), "public/r") // Get all JSON files in the public/r directory const files = await fs.readdir(publicDir) const jsonFiles = files.filter(file => file.endsWith('.json')) for (const file of jsonFiles) { const filePath = path.join(publicDir, file) const content = await fs.readFile(filePath, 'utf-8') const json = JSON.parse(content) // Clean up the paths in the files array if (json.files) { // Build a mapping of source paths to target paths for this block const pathMappings = new Map() json.files.forEach((file: any) => { if (typeof file === 'object' && file.path && file.target) { // Map the source registry path to the target path const sourcePath = file.path.replace(/^registry\/creative-tim\//, '') pathMappings.set(sourcePath, file.target) } }) json.files = json.files.map((file: any) => { if (typeof file === 'object' && file.path) { // Remove only registry/ prefix, keep creative-tim/ for branding let cleanPath = file.path.replace(/^registry\//, '') // For UI components, prepend with components/ if (cleanPath.startsWith('creative-tim/ui/')) { cleanPath = 'components/' + cleanPath.replace(/^creative-tim\//, '') } // For examples, prepend with components/ else if (cleanPath.startsWith('creative-tim/examples/')) { cleanPath = 'components/' + cleanPath.replace(/^creative-tim\//, '') } file.path = cleanPath // Clean up imports in the content field using the path mappings if (file.content) { file.content = rewriteImports(file.content, pathMappings) } } return file }) } // Write the cleaned JSON back await fs.writeFile(filePath, JSON.stringify(json, null, 2)) } console.log(`✨ Cleaned paths in ${jsonFiles.length} files`) } function rewriteImports(content: string, pathMappings: Map): string { // Sort mappings by path length (longest first) to handle more specific paths first const sortedMappings = Array.from(pathMappings.entries()).sort( ([a], [b]) => b.length - a.length ) // Handle block-specific imports using the exact path mappings sortedMappings.forEach(([sourcePath, targetPath]) => { if (sourcePath.startsWith('blocks/')) { // Remove the file extension from sourcePath to match import statements const sourcePathNoExt = sourcePath.replace(/\.(tsx?|jsx?)$/, '') // Extract the directory from the target path (removing filename) const targetPathNoExt = targetPath.replace(/\.(tsx?|jsx?)$/, '') // Create regex to match this specific import path const escapedPath = sourcePathNoExt.replace(/\//g, '\\/') const importRegex = new RegExp( `@\\/registry\\/creative-tim\\/${escapedPath}`, 'g' ) content = content.replace(importRegex, `@/${targetPathNoExt}`) } }) // Rewrite imports from @/registry/creative-tim/ui/... to @/components/ui/... content = content.replace( /@\/registry\/creative-tim\/ui\//g, '@/components/ui/' ) // Rewrite imports from @/registry/creative-tim/examples/... to @/components/examples/... content = content.replace( /@\/registry\/creative-tim\/examples\//g, '@/components/examples/' ) // Rewrite imports from @/registry/creative-tim/lib/... to @/lib/... content = content.replace( /@\/registry\/creative-tim\/lib\//g, '@/lib/' ) return content } async function buildPackageJson( packageType: "ui" | "blocks" | "agents", fileName: string, description: string ) { console.log(`🎨 Building ${fileName} for ${packageType}...`) // Filter components by type const typeMap = { ui: "registry:ui", blocks: "registry:block", agents: "registry:agent" } const components = registry.items.filter(item => item.type === typeMap[packageType]) // Aggregate all dependencies and registryDependencies const allDependencies = new Set() const allRegistryDependencies = new Set() const allFiles: any[] = [] // Build path mappings for import rewriting const pathMappings = new Map() for (const component of components) { // Collect dependencies if (component.dependencies) { component.dependencies.forEach(dep => allDependencies.add(dep)) } // Collect registryDependencies if (component.registryDependencies) { component.registryDependencies.forEach(dep => allRegistryDependencies.add(dep)) } // Collect files and read their content if (component.files) { for (const file of component.files) { const filePath = `registry/creative-tim/${file.path}` const absolutePath = path.join(process.cwd(), filePath) // Read file content let content = "" if (existsSync(absolutePath)) { content = await fs.readFile(absolutePath, 'utf-8') } // Clean path - remove registry/creative-tim/ prefix for UI components let cleanPath = file.path if (packageType === "ui") { if (cleanPath.startsWith('ui/')) { cleanPath = 'components/' + cleanPath } else if (cleanPath.startsWith('examples/')) { cleanPath = 'components/' + cleanPath } } // Build path mapping if (file.target) { pathMappings.set(file.path, file.target) } allFiles.push({ path: cleanPath, type: file.type, target: file.target ?? "", content: content }) } } } // Rewrite imports in all file contents allFiles.forEach(file => { if (file.content) { file.content = rewriteImports(file.content, pathMappings) } }) // Create the JSON structure const packageJson = { "$schema": "https://ui.shadcn.com/schema/registry-item.json", name: fileName.replace('.json', ''), type: typeMap[packageType], description: description, dependencies: Array.from(allDependencies).sort(), registryDependencies: Array.from(allRegistryDependencies).sort(), files: allFiles } // Write the JSON file const outputPath = path.join(process.cwd(), "public", "r", fileName) await fs.writeFile(outputPath, JSON.stringify(packageJson, null, 2)) console.log(`✨ Generated ${fileName} with ${components.length} ${packageType} components`) } async function buildAllJson() { console.log("📦 Building all.json...") // Combine all components const allDependencies = new Set() const allRegistryDependencies = new Set() const allFiles: any[] = [] const pathMappings = new Map() for (const component of registry.items) { if (component.dependencies) { component.dependencies.forEach(dep => allDependencies.add(dep)) } if (component.registryDependencies) { component.registryDependencies.forEach(dep => allRegistryDependencies.add(dep)) } if (component.files) { for (const file of component.files) { const filePath = `registry/creative-tim/${file.path}` const absolutePath = path.join(process.cwd(), filePath) let content = "" if (existsSync(absolutePath)) { content = await fs.readFile(absolutePath, 'utf-8') } let cleanPath = file.path if (component.type === "registry:ui") { if (cleanPath.startsWith('ui/')) { cleanPath = 'components/' + cleanPath } else if (cleanPath.startsWith('examples/')) { cleanPath = 'components/' + cleanPath } } if (file.target) { pathMappings.set(file.path, file.target) } allFiles.push({ path: cleanPath, type: file.type, target: file.target ?? "", content: content }) } } } allFiles.forEach(file => { if (file.content) { file.content = rewriteImports(file.content, pathMappings) } }) const allJson = { "$schema": "https://ui.shadcn.com/schema/registry-item.json", name: "all", type: "registry:all", description: "All components from Creative Tim UI (UI components, blocks, and agents)", dependencies: Array.from(allDependencies).sort(), registryDependencies: Array.from(allRegistryDependencies).sort(), files: allFiles } // Write to public/r/all.json const outputPath = path.join(process.cwd(), "public/r/all.json") await fs.writeFile(outputPath, JSON.stringify(allJson, null, 2)) console.log(`✨ Generated all.json with ${registry.items.length} total components`) } async function buildPackageRegistries() { console.log("📦 Building package-specific registries...") // Build UI components registry await buildPackageJson( "ui", "ui.json", "All UI components from @creative-tim/ui" ) // Build blocks registry await buildPackageJson( "blocks", "blocks.json", "All blocks from @creative-tim/blocks" ) // Future: Build agents registry // await buildPackageJson( // "agents", // "agents.json", // "All agent components from @creative-tim/agents" // ) } async function buildBlocksIndex() { const blocks = await getAllBlocks(["registry:block"]) const payload = blocks.map((block) => ({ name: block.name, description: block.description, categories: block.categories, })) rimraf.sync(path.join(process.cwd(), "registry/__blocks__.json")) await fs.writeFile( path.join(process.cwd(), "registry/__blocks__.json"), JSON.stringify(payload, null, 2) ) await new Promise((resolve, reject) => { exec(`prettier --write registry/__blocks__.json`, (error) => { if (error) { reject(error) return } resolve(undefined) }) }) } try { console.log("🗂️ Building registry/__index__.tsx...") await buildRegistryIndex() console.log("🗂️ Building registry/__blocks__.json...") await buildBlocksIndex() console.log("💅 Building registry.json...") await buildRegistryJsonFile() console.log("🏗️ Building registry...") await buildRegistry() console.log("📦 Building package registries...") await buildPackageRegistries() console.log("📦 Building all.json...") await buildAllJson() } catch (error) { console.error(error) process.exit(1) }