import { readFile, readdir } from 'node:fs/promises' import { join, normalize, resolve } from 'pathe' import { compileScript, parse } from 'vue/compiler-sfc' import type { Registry } from '../../lib/registry' const DEPENDENCIES = new Map([ ['@vueuse/core', []], ['v-calendar', []], ['@tanstack/vue-table', []], ['vee-validate', ['@vee-validate/zod', 'zod']], ]) // Some dependencies latest tag were not compatible with Vue3. const DEPENDENCIES_WITH_TAGS = new Map([ ['v-calendar', 'v-calendar@next'], ]) const REGISTRY_DEPENDENCY = '@/' type ArrayItem = T extends Array ? X : never type RegistryItem = ArrayItem export async function buildRegistry() { const ui_path = resolve('./src/lib/registry/default/ui') const uiRegistry = await crawlUI(ui_path) const example_path = resolve('./src/lib/registry/default/example') const exampleRegistry = await crawlExample(example_path) return uiRegistry.concat(exampleRegistry) } async function crawlUI(rootPath: string) { const dir = await readdir(rootPath, { recursive: true, withFileTypes: true, }) const uiRegistry: Registry = [] for (const dirent of dir) { if (!dirent.isDirectory()) continue const componentPath = resolve(rootPath, dirent.name) const ui = await buildUIRegistry(componentPath, dirent.name) uiRegistry.push(ui) } return uiRegistry } async function crawlExample(rootPath: string) { const type = 'components:example' const dir = await readdir(rootPath, { recursive: true, withFileTypes: true, }) const exampleRegistry: Registry = [] for (const dirent of dir) { if (dirent.name === 'index.ts') continue if (dirent.isFile()) { const [name] = dirent.name.split('.vue') const file_path = join('example', normalize(dirent.path).split('/example')[1], dirent.name) const { dependencies, registryDependencies } = await getDependencies(join(dirent.path, dirent.name)) exampleRegistry.push({ name, type, files: [file_path], registryDependencies: Array.from(registryDependencies), dependencies: Array.from(dependencies), }) } // ignoring examples with directories for now... // if (dirent.isDirectory()) { // const componentPath = resolve(rootPath, dirent.name); // const ui = await buildUIRegistry(componentPath, dirent.name); // exampleRegistry.push({ // ...ui, // type // }); // } } return exampleRegistry } async function buildUIRegistry(componentPath: string, componentName: string) { const dir = await readdir(componentPath, { withFileTypes: true, }) const files: string[] = [] const dependencies = new Set() const registryDependencies = new Set() const type = 'components:ui' for (const dirent of dir) { if (!dirent.isFile()) continue const file_path = join('ui', componentName, dirent.name) files.push(file_path) files.sort() // only grab deps from the vue files if (dirent.name === 'index.ts') continue const deps = await getDependencies(join(componentPath, dirent.name)) deps.dependencies.forEach(dep => dependencies.add(dep)) deps.registryDependencies.forEach(dep => registryDependencies.add(dep), ) } return { name: componentName, type, files, registryDependencies: Array.from(registryDependencies), dependencies: Array.from(dependencies), } satisfies RegistryItem } async function getDependencies(filename: string) { const code = await readFile(filename, { encoding: 'utf8' }) const parsed = parse(code) const registryDependencies = new Set() const dependencies = new Set() if (parsed.descriptor.script?.content || parsed.descriptor.scriptSetup?.content) { const compiled = compileScript(parsed.descriptor, { id: '' }) Object.values(compiled.imports!).forEach((value) => { const source = value.source const peerDeps = DEPENDENCIES.get(source) const taggedDeps = DEPENDENCIES_WITH_TAGS.get(source) if (peerDeps !== undefined) { if (taggedDeps !== undefined) dependencies.add(taggedDeps) else dependencies.add(source) peerDeps.forEach(dep => dependencies.add(dep)) } if (source.startsWith(REGISTRY_DEPENDENCY)) { const component = source.split('/').at(-1)! registryDependencies.add(component) } }) } return { registryDependencies, dependencies } }