import { exec, execFile } from "child_process" import { existsSync, promises as fs } from "fs" import path from "path" import { rimraf } from "rimraf" import { registrySchema } from "shadcn/schema" import { getAllBlocks } from "@/lib/blocks" import { STYLES, type Style } from "@/registry/styles" async function buildRegistryIndex(styles: Style[]) { 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 style of styles) { // Dynamically import the registry for this style. const { registry: importedRegistry } = await import( `../registry/${style.name}/registry.ts` ) // Validate the registry schema. const parseResult = registrySchema.safeParse(importedRegistry) if (!parseResult.success) { console.error(`โŒ Registry validation failed for ${style.name}:`) console.error(parseResult.error.format()) throw new Error(`Invalid registry schema for ${style.name}`) } const registry = parseResult.data index += ` "${style.name}": {` for (const item of registry.items) { const files = item.files?.map((file) => ({ path: typeof file === "string" ? file : file.path, type: typeof file === "string" ? item.type : file.type, target: typeof file === "string" ? undefined : file.target, })) ?? [] if (files.length === 0) { continue } const componentPath = item.files?.[0]?.path ? `@/registry/${style.name}/${item.files[0].path}` : "" index += ` "${item.name}": { name: "${item.name}", description: "${item.description ?? ""}", type: "${item.type}", registryDependencies: ${JSON.stringify(item.registryDependencies)}, files: [${files.map((file) => { const filePath = `registry/${style.name}/${file.path}` return `{ 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 += ` },` } index += ` }` console.log( `#๏ธโƒฃ Built multi-style index with ${styles.length} styles: ${styles.map((s) => s.name).join(", ")}` ) // Write unified index. rimraf.sync(path.join(process.cwd(), "registry/__index__.tsx")) await fs.writeFile(path.join(process.cwd(), "registry/__index__.tsx"), index) } async function buildRegistryJsonFile(styleName: string) { // 1. Import the registry for this style. const { registry: importedRegistry } = await import( `../registry/${styleName}/registry.ts` ) // 2. Validate the registry schema. const parseResult = registrySchema.safeParse(importedRegistry) if (!parseResult.success) { console.error(`โŒ Registry validation failed for ${styleName}:`) console.error(parseResult.error.format()) throw new Error(`Invalid registry schema for ${styleName}`) } const registry = parseResult.data // 3. Fix the path for registry items. const fixedRegistry = { ...registry, items: registry.items.map((item) => { const files = item.files?.map((file) => { return { ...file, path: `registry/${styleName}/${file.path}`, } }) return { ...item, files, } }), } // 3. Create the output directory and write registry.json. const outputDir = path.join( process.cwd(), styleName === "new-york-v4" ? `public/r/styles/${styleName}` : `public/r/${styleName}` ) await fs.mkdir(outputDir, { recursive: true }) // 4. Write registry.json to output directory and format it. const registryJsonPath = path.join(outputDir, "registry.json") await fs.writeFile(registryJsonPath, JSON.stringify(fixedRegistry, null, 2)) await new Promise((resolve, reject) => { execFile('prettier', ['--write', registryJsonPath], (error) => { if (error) { reject(error); } else { resolve(); } }); }) // 5. Write temporary registry file needed by shadcn build. const tempRegistryPath = path.join(process.cwd(), `registry-${styleName}.json`) await fs.writeFile(tempRegistryPath, JSON.stringify(fixedRegistry, null, 2)) } async function buildRegistry(styleName: string) { return new Promise((resolve, reject) => { // Use local shadcn copy. const outputPath = styleName === "new-york-v4" ? `public/r/styles/${styleName}` : `public/r/${styleName}` const process = exec( `node ../../packages/shadcn/dist/index.js build registry-${styleName}.json --output ${outputPath}` ) // exec( // `pnpm dlx shadcn build registry-${styleName}.json --output public/r/styles/${styleName}` // ) process.on("exit", (code) => { if (code === 0) { resolve(undefined) } else { reject(new Error(`Process exited with code ${code}`)) } }) }) } 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 exec(`prettier --write registry/__blocks__.json`) } try { const styles = Array.from(STYLES) console.log(`๐ŸŽจ Found ${styles.length} styles: ${styles.map((s) => s.name).join(", ")}`) // Build unified multi-style index. console.log("\n๐Ÿ—‚๏ธ Building unified multi-style registry/__index__.tsx...") await buildRegistryIndex(styles) for (const style of styles) { console.log(`\n๐Ÿ“ฆ Processing style: ${style.name}`) console.log(`๐Ÿ’… Building registry-${style.name}.json...`) await buildRegistryJsonFile(style.name) console.log(`๐Ÿ—๏ธ Building registry for ${style.name}...`) await buildRegistry(style.name) } console.log("\n๐Ÿ—‚๏ธ Building registry/__blocks__.json...") await buildBlocksIndex() // Clean up intermediate files. console.log("\n๐Ÿงน Cleaning up intermediate files...") for (const style of styles) { if (existsSync(path.join(process.cwd(), `registry-${style.name}.json`))) { await fs.unlink(path.join(process.cwd(), `registry-${style.name}.json`)) } } console.log("\nโœ… Build complete!") } catch (error) { console.error(error) process.exit(1) }