import { readdir, readFile, writeFile } from "node:fs/promises" import path from "node:path" import process from "node:process" const root = process.cwd() const sourceDir = path.join(root, "src") const componentsDir = path.join(sourceDir, "components", "ui") const libDir = path.join(sourceDir, "lib") const outputPath = path.join(root, "manifest.json") const packageJson = JSON.parse(await readFile(path.join(root, "package.json"), "utf8")) const components = await listDirectories(componentsDir) const utils = await listFiles(libDir) const items = {} for (const file of utils) { const name = path.basename(file, path.extname(file)) const source = await readFile(path.join(root, file), "utf8") const target = stripSourcePrefix(file) items[`utils/${name}`] = { name: `utils/${name}`, type: "util", description: `Utility source for ${name}.`, dependencies: collectPackageDependencies(source), files: [ { path: file, target, }, ], } } for (const component of components) { const componentFiles = await listFiles(path.join("src", "components", "ui", component)) const sources = await Promise.all( componentFiles.map(async (file) => readFile(path.join(root, file), "utf8")), ) const source = sources.join("\n") items[component] = { name: component, type: "component", description: `${toTitle(component)} component wrapper for Base UI and Tailwind CSS.`, dependencies: collectPackageDependencies(source), localDependencies: collectLocalDependencies(source, component, components, utils), files: componentFiles.map((file) => ({ path: file, target: stripSourcePrefix(file), })), } } const manifest = { name: "base-ui-tailwind", version: packageJson.version ?? "0.0.0", generatedAt: new Date().toISOString(), command: "base-ui-tailwind", configFile: "base-ui-tailwind.json", dependencies: { "@base-ui/react": packageJson.dependencies?.["@base-ui/react"], "clsx": packageJson.dependencies?.clsx, "lucide-react": packageJson.dependencies?.["lucide-react"], "tailwind-merge": packageJson.dependencies?.["tailwind-merge"], }, items, } await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}\n`) console.log(`Generated manifest.json with ${Object.keys(items).length} items.`) async function listDirectories(directory) { const entries = await readdir(directory, { withFileTypes: true }) return entries .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort((a, b) => a.localeCompare(b)) } async function listFiles(relativeOrAbsoluteDirectory) { const absoluteDirectory = path.isAbsolute(relativeOrAbsoluteDirectory) ? relativeOrAbsoluteDirectory : path.join(root, relativeOrAbsoluteDirectory) const relativeDirectory = path.relative(root, absoluteDirectory) const entries = await readdir(absoluteDirectory, { withFileTypes: true }) const files = [] for (const entry of entries) { const relativePath = path.join(relativeDirectory, entry.name) const absolutePath = path.join(absoluteDirectory, entry.name) if (entry.isDirectory()) { files.push(...(await listFiles(absolutePath))) } else if (entry.isFile()) { files.push(relativePath.split(path.sep).join("/")) } } return files.sort((a, b) => a.localeCompare(b)) } function collectPackageDependencies(source) { const dependencies = new Set() const importPattern = /from\s+["']([^"']+)["']|import\s+["']([^"']+)["']/g for (const match of source.matchAll(importPattern)) { const specifier = match[1] ?? match[2] if (!specifier || specifier.startsWith(".") || specifier.startsWith("@/")) { continue } dependencies.add(getPackageName(specifier)) } return [...dependencies].sort((a, b) => a.localeCompare(b)) } function collectLocalDependencies(source, component, components, utils) { const dependencies = new Set() const localPattern = /from\s+["']@\/([^"']+)["']|import\s+["']@\/([^"']+)["']/g for (const match of source.matchAll(localPattern)) { const specifier = match[1] ?? match[2] if (!specifier) { continue } const componentMatch = specifier.match(/^components\/ui\/([^/]+)/) if (componentMatch && componentMatch[1] !== component && components.includes(componentMatch[1])) { dependencies.add(componentMatch[1]) continue } const utilMatch = specifier.match(/^lib\/([^/]+)/) if (utilMatch) { const utilPath = utils.find( (file) => file === `src/lib/${utilMatch[1]}.ts` || file === `src/lib/${utilMatch[1]}.tsx`, ) if (utilPath) { dependencies.add(`utils/${path.basename(utilPath, path.extname(utilPath))}`) } } } return [...dependencies].sort((a, b) => a.localeCompare(b)) } function stripSourcePrefix(file) { return file.startsWith("src/") ? file.slice("src/".length) : file } function getPackageName(specifier) { if (specifier.startsWith("@")) { const [scope, name] = specifier.split("/") return `${scope}/${name}` } return specifier.split("/")[0] } function toTitle(value) { return value .split("-") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" ") }