shadcn-vue/apps/www/src/lib/registry/registry.ts
Ole Marius Løset ef3ec54a4e
Output consistency across platforms (#181)
* fix: normalize newline

* fix: sort readdir

* fix: correct sorting

* fix: missing npm run in build:registry npm script

* Update apps/www/package.json

Co-authored-by: Sadegh Barati <sadeghbaratiwork@gmail.com>

---------

Co-authored-by: Sadegh Barati <sadeghbaratiwork@gmail.com>
2023-12-31 21:10:47 +03:30

165 lines
4.5 KiB
TypeScript

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<string, string[]>([
['@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<string, string>([
['v-calendar', 'v-calendar@next'],
])
const REGISTRY_DEPENDENCY = '@/'
type ArrayItem<T> = T extends Array<infer X> ? X : never
type RegistryItem = ArrayItem<Registry>
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<string>()
const registryDependencies = new Set<string>()
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<string>()
const dependencies = new Set<string>()
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 }
}