Files
netto/scripts/generate-manifest.mjs
T

167 lines
5.1 KiB
JavaScript
Raw Normal View History

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(" ")
}