167 lines
5.1 KiB
JavaScript
167 lines
5.1 KiB
JavaScript
|
|
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(" ")
|
||
|
|
}
|