shadcn-vue/apps/www/scripts/build-registry.ts
2024-11-22 17:58:53 +08:00

911 lines
29 KiB
TypeScript

import type { z } from 'zod'
import type {
Registry,
RegistryEntry,
registryItemTypeSchema,
} from '../src/registry/schema'
// @sts-nocheck
import { existsSync, promises as fs } from 'node:fs'
import path from 'node:path'
import { template } from 'lodash-es'
import { rimraf } from 'rimraf'
import { registry } from '../src/registry'
import { buildRegistry as crawlContent } from '../src/registry/crawl-content'
import { baseColors } from '../src/registry/registry-base-colors'
import { colorMapping, colors } from '../src/registry/registry-colors'
import { iconLibraries, icons } from '../src/registry/registry-icons'
import { styles } from '../src/registry/registry-styles'
import {
registryEntrySchema,
registrySchema,
} from '../src/registry/schema'
const REGISTRY_PATH = path.join(process.cwd(), 'src/public/r')
const REGISTRY_INDEX_WHITELIST: z.infer<typeof registryItemTypeSchema>[] = [
'registry:ui',
'registry:lib',
'registry:hook',
'registry:theme',
'registry:block',
'registry:example',
]
// const project = new Project({
// compilerOptions: {},
// })
// async function createTempSourceFile(filename: string) {
// const dir = await fs.mkdtemp(path.join(tmpdir(), 'shadcn-'))
// return path.join(dir, filename)
// }
// ----------------------------------------------------------------------------
// Build __registry__/index.ts.
// ----------------------------------------------------------------------------
async function buildRegistry(registry: Registry) {
let index = `// @ts-nocheck
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
export const Index: Record<string, any> = {
`
for (const style of styles) {
index += ` "${style.name}": {`
// Build style index.
for (const item of registry) {
const resolveFiles = item.files?.map(
file =>
`registry/${style.name}/${
typeof file === 'string' ? file : file.path
}`,
)
if (!resolveFiles) {
continue
}
const type = item.type.split(':')[1]
const sourceFilename = ''
// const chunks: any = []
// if (item.type === 'registry:block') {
// const file = resolveFiles[0]
// let raw: string
// try {
// if (file) {
// const filename = path.basename(file)
// }
// raw = await fs.readFile(file, 'utf8')
// }
// catch (error) {
// continue
// }
// // const tempFile = await createTempSourceFile(filename)
// // const sourceFile = project.createSourceFile(tempFile, raw, {
// // scriptKind: ScriptKind.TS,
// // })
// // const description = sourceFile
// // .getVariableDeclaration('description')
// // ?.getInitializerOrThrow()
// // .asKindOrThrow(SyntaxKind.StringLiteral)
// // .getLiteralValue()
// // item.description = description ?? ''
// // // Find all imports.
// // const imports = new Map<
// // string,
// // {
// // module: string
// // text: string
// // isDefault?: boolean
// // }
// // >()
// // sourceFile.getImportDeclarations().forEach((node) => {
// // const module = node.getModuleSpecifier().getLiteralValue()
// // node.getNamedImports().forEach((item) => {
// // imports.set(item.getText(), {
// // module,
// // text: node.getText(),
// // })
// // })
// // const defaultImport = node.getDefaultImport()
// // if (defaultImport) {
// // imports.set(defaultImport.getText(), {
// // module,
// // text: defaultImport.getText(),
// // isDefault: true,
// // })
// // }
// // })
// // Find all opening tags with x-chunk attribute.
// // const components = sourceFile
// // .getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
// // .filter((node) => {
// // return node.getAttribute('x-chunk') !== undefined
// // })
// // chunks = await Promise.all(
// // components.map(async (component, index) => {
// // const chunkName = `${item.name}-chunk-${index}`
// // // Get the value of x-chunk attribute.
// // const attr = component
// // .getAttributeOrThrow('x-chunk')
// // .asKindOrThrow(SyntaxKind.JsxAttribute)
// // const description = attr
// // .getInitializerOrThrow()
// // .asKindOrThrow(SyntaxKind.StringLiteral)
// // .getLiteralValue()
// // // Delete the x-chunk attribute.
// // attr.remove()
// // // Add a new attribute to the component.
// // component.addAttribute({
// // name: 'x-chunk',
// // initializer: `"${chunkName}"`,
// // })
// // // Get the value of x-chunk-container attribute.
// // const containerAttr = component
// // .getAttribute('x-chunk-container')
// // ?.asKindOrThrow(SyntaxKind.JsxAttribute)
// // const containerClassName = containerAttr
// // ?.getInitializer()
// // ?.asKindOrThrow(SyntaxKind.StringLiteral)
// // .getLiteralValue()
// // containerAttr?.remove()
// // const parentJsxElement = component.getParentIfKindOrThrow(
// // SyntaxKind.JsxElement,
// // )
// // // Find all opening tags on component.
// // const children = parentJsxElement
// // .getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
// // .map((node) => {
// // return node.getTagNameNode().getText()
// // })
// // .concat(
// // parentJsxElement
// // .getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
// // .map((node) => {
// // return node.getTagNameNode().getText()
// // }),
// // )
// // const componentImports = new Map<
// // string,
// // string | string[] | Set<string>
// // >()
// // children.forEach((child) => {
// // const importLine = imports.get(child)
// // if (importLine) {
// // const imports = componentImports.get(importLine.module) || []
// // const newImports = importLine.isDefault
// // ? importLine.text
// // : new Set([...imports, child])
// // componentImports.set(
// // importLine.module,
// // importLine?.isDefault ? newImports : Array.from(newImports),
// // )
// // }
// // })
// // const componnetImportLines = Array.from(
// // componentImports.keys(),
// // ).map((key) => {
// // const values = componentImports.get(key)
// // const specifier = Array.isArray(values)
// // ? `{${values.join(',')}}`
// // : values
// // return `import ${specifier} from "${key}"`
// // })
// // const code = `
// // 'use client'
// // ${componnetImportLines.join('\n')}
// // export default function Component() {
// // return (${parentJsxElement.getText()})
// // }`
// // const targetFile = file.replace(item.name, `${chunkName}`)
// // const targetFilePath = path.join(
// // cwd(),
// // `registry/${style.name}/${type}/${chunkName}.ts`,
// // )
// // // Write component file.
// // rimraf.sync(targetFilePath)
// // await writeFile(targetFilePath, code, 'utf8')
// // return {
// // name: chunkName,
// // description,
// // component: `React.lazy(() => import("@/registry/${style.name}/${type}/${chunkName}")),`,
// // file: targetFile,
// // container: {
// // className: containerClassName,
// // },
// // }
// // }),
// // )
// // // Write the source file for blocks only.
// sourceFilename = `__registry__/${style.name}/${type}/${item.name}.ts`
// if (item.files) {
// const files = item.files.map(file =>
// typeof file === 'string'
// ? { type: 'registry:page', path: file }
// : file,
// )
// if (files?.length) {
// sourceFilename = `__registry__/${style.name}/${files[0].path}`
// }
// }
// const sourcePath = path.join(process.cwd(), sourceFilename)
// if (!existsSync(sourcePath)) {
// await fs.mkdir(sourcePath, { recursive: true })
// }
// rimraf.sync(sourcePath)
// // await writeFile(sourcePath, sourceFile.getText())
// await writeFile(sourcePath, raw)
// }
let componentPath = `@/registry/${style.name}/${type}/${item.name}`
if (item.files) {
const files = item.files.map(file =>
typeof file === 'string'
? { type: 'registry:page', path: file }
: file,
)
if (files?.length) {
componentPath = `@/registry/${style.name}/${files[0].path}`
}
}
index += `
"${item.name}": {
name: "${item.name}",
description: "${item.description ?? ''}",
type: "${item.type}",
registryDependencies: ${JSON.stringify(item.registryDependencies)},
files: [${item.files?.map((file) => {
const filePath = `registry/${style.name}/${
typeof file === 'string' ? file : file.path
}`
const resolvedFilePath = path.resolve(filePath)
return typeof file === 'string'
? `"${resolvedFilePath}"`
: `{
path: "${filePath}",
type: "${file.type}",
target: "${file.target ?? ''}"
}`
})}],
component: () => import("${componentPath}").then((m) => m.default),
raw: () => import("${componentPath}?raw").then((m) => m.default),
source: "${sourceFilename}",
category: "${item.category ?? ''}",
subcategory: "${item.subcategory ?? ''}"
},`
}
// TODO: maybe implement chunk?
// chunks: [${chunks.map(
// chunk => `{
// name: "${chunk.name}",
// description: "${chunk.description ?? 'No description'}",
// component: ${chunk.component}
// file: "${chunk.file}",
// container: {
// className: "${chunk.container.className}"
// }
// }`,
// )}]
index += `
},`
}
index += `
}
`
// ----------------------------------------------------------------------------
// Build registry/index.json.
// ----------------------------------------------------------------------------
const items = registry
.filter(item => ['registry:ui'].includes(item.type))
.map((item) => {
return {
...item,
files: item.files?.map((_file) => {
const file = { path: _file.path, type: item.type }
return file
}),
}
})
const registryJson = JSON.stringify(items, null, 2)
rimraf.sync(path.join(REGISTRY_PATH, 'index.json'))
await writeFile(
path.join(REGISTRY_PATH, 'index.json'),
registryJson,
)
// Write style index.
rimraf.sync(path.join(process.cwd(), '__registry__/index.ts'))
await writeFile(path.join(process.cwd(), '__registry__/index.ts'), index)
}
// ----------------------------------------------------------------------------
// Build registry/styles/[style]/[name].json.
// ----------------------------------------------------------------------------
async function buildStyles(registry: Registry) {
for (const style of styles) {
const targetPath = path.join(REGISTRY_PATH, 'styles', style.name)
// Create directory if it doesn't exist.
if (!existsSync(targetPath)) {
await fs.mkdir(targetPath, { recursive: true })
}
for (const item of registry) {
if (!REGISTRY_INDEX_WHITELIST.includes(item.type)) {
continue
}
let files
if (item.files) {
files = await Promise.all(
item.files.map(async (_file) => {
const file = {
path: _file.path,
type: _file.type,
content: '',
target: _file.target ?? '',
}
let content: string
try {
content = await fs.readFile(
path.join(process.cwd(), 'src', 'registry', style.name, file.path),
'utf8',
)
}
catch (error) {
return
}
// TODO: remove meta content
// const tempFile = await createTempSourceFile(file.path)
// const sourceFile = project.createSourceFile(tempFile, content, {
// scriptKind: ScriptKind.TS,
// })
// sourceFile.getVariableDeclaration('iframeHeight')?.remove()
// sourceFile.getVariableDeclaration('containerClassName')?.remove()
// sourceFile.getVariableDeclaration('description')?.remove()
const target = file.target || ''
// if ((!target || target === '') && item.name.startsWith('v0-')) {
// const fileName = file.path.split('/').pop()
// if (
// file.type === 'registry:block'
// || file.type === 'registry:component'
// || file.type === 'registry:example'
// ) {
// target = `components/${fileName}`
// }
// if (file.type === 'registry:ui') {
// target = `components/ui/${fileName}`
// }
// if (file.type === 'registry:hook') {
// target = `hooks/${fileName}`
// }
// if (file.type === 'registry:lib') {
// target = `lib/${fileName}`
// }
// }
return {
path: file.path,
type: file.type,
// content: sourceFile.getText(),
content,
target,
}
}),
)
}
// if (item.type === 'registry:block' && item.name === 'Sidebar01')
// console.log(item.name, item.files?.[0], files?.[0])
const payload = registryEntrySchema
.omit({
// source: true,
category: true,
subcategory: true,
// chunks: true,
})
.safeParse({
...item,
files,
})
if (payload.success) {
await writeFile(
path.join(targetPath, `${item.name}.json`),
JSON.stringify(payload.data, null, 2),
)
}
}
}
// ----------------------------------------------------------------------------
// Build registry/styles/index.json.
// ----------------------------------------------------------------------------
const stylesJson = JSON.stringify(styles, null, 2)
await writeFile(
path.join(REGISTRY_PATH, 'styles/index.json'),
stylesJson,
)
}
// ----------------------------------------------------------------------------
// Build registry/styles/[name]/index.json.
// ----------------------------------------------------------------------------
async function buildStylesIndex() {
for (const style of styles) {
const targetPath = path.join(REGISTRY_PATH, 'styles', style.name)
const dependencies = [
'tailwindcss-animate',
'class-variance-authority',
'lucide-vue-next',
]
// TODO: Remove this when we migrate to lucide-vue-next.
// if (style.name === "new-york") {
// dependencies.push("@radix-ui/react-icons")
// }
const payload: RegistryEntry = {
name: style.name,
type: 'registry:style',
dependencies,
registryDependencies: ['utils'],
tailwind: {
config: {
plugins: [`require("tailwindcss-animate")`],
},
},
cssVars: {},
files: [],
}
await writeFile(
path.join(targetPath, 'index.json'),
JSON.stringify(payload, null, 2),
)
}
}
// ----------------------------------------------------------------------------
// Build registry/colors/index.json.
// ----------------------------------------------------------------------------
async function buildThemes() {
const colorsTargetPath = path.join(REGISTRY_PATH, 'colors')
rimraf.sync(colorsTargetPath)
if (!existsSync(colorsTargetPath)) {
await fs.mkdir(colorsTargetPath, { recursive: true })
}
const colorsData: Record<string, any> = {}
for (const [color, value] of Object.entries(colors)) {
if (typeof value === 'string') {
colorsData[color] = value
continue
}
if (Array.isArray(value)) {
colorsData[color] = value.map(item => ({
...item,
rgbChannel: item.rgb.replace(/^rgb\((\d+),(\d+),(\d+)\)$/, '$1 $2 $3'),
hslChannel: item.hsl.replace(
/^hsl\(([\d.]+),([\d.]+%),([\d.]+%)\)$/,
'$1 $2 $3',
),
}))
continue
}
if (typeof value === 'object') {
colorsData[color] = {
...value,
rgbChannel: value.rgb.replace(/^rgb\((\d+),(\d+),(\d+)\)$/, '$1 $2 $3'),
hslChannel: value.hsl.replace(
/^hsl\(([\d.]+),([\d.]+%),([\d.]+%)\)$/,
'$1 $2 $3',
),
}
continue
}
}
await writeFile(
path.join(colorsTargetPath, 'index.json'),
JSON.stringify(colorsData, null, 2),
)
// ----------------------------------------------------------------------------
// Build registry/colors/[base].json.
// ----------------------------------------------------------------------------
const BASE_STYLES = `@tailwind base;
@tailwind components;
@tailwind utilities;
`
const BASE_STYLES_WITH_VARIABLES = `@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: <%- colors.light["background"] %>;
--foreground: <%- colors.light["foreground"] %>;
--card: <%- colors.light["card"] %>;
--card-foreground: <%- colors.light["card-foreground"] %>;
--popover: <%- colors.light["popover"] %>;
--popover-foreground: <%- colors.light["popover-foreground"] %>;
--primary: <%- colors.light["primary"] %>;
--primary-foreground: <%- colors.light["primary-foreground"] %>;
--secondary: <%- colors.light["secondary"] %>;
--secondary-foreground: <%- colors.light["secondary-foreground"] %>;
--muted: <%- colors.light["muted"] %>;
--muted-foreground: <%- colors.light["muted-foreground"] %>;
--accent: <%- colors.light["accent"] %>;
--accent-foreground: <%- colors.light["accent-foreground"] %>;
--destructive: <%- colors.light["destructive"] %>;
--destructive-foreground: <%- colors.light["destructive-foreground"] %>;
--border: <%- colors.light["border"] %>;
--input: <%- colors.light["input"] %>;
--ring: <%- colors.light["ring"] %>;
--radius: 0.5rem;
--chart-1: <%- colors.light["chart-1"] %>;
--chart-2: <%- colors.light["chart-2"] %>;
--chart-3: <%- colors.light["chart-3"] %>;
--chart-4: <%- colors.light["chart-4"] %>;
--chart-5: <%- colors.light["chart-5"] %>;
}
.dark {
--background: <%- colors.dark["background"] %>;
--foreground: <%- colors.dark["foreground"] %>;
--card: <%- colors.dark["card"] %>;
--card-foreground: <%- colors.dark["card-foreground"] %>;
--popover: <%- colors.dark["popover"] %>;
--popover-foreground: <%- colors.dark["popover-foreground"] %>;
--primary: <%- colors.dark["primary"] %>;
--primary-foreground: <%- colors.dark["primary-foreground"] %>;
--secondary: <%- colors.dark["secondary"] %>;
--secondary-foreground: <%- colors.dark["secondary-foreground"] %>;
--muted: <%- colors.dark["muted"] %>;
--muted-foreground: <%- colors.dark["muted-foreground"] %>;
--accent: <%- colors.dark["accent"] %>;
--accent-foreground: <%- colors.dark["accent-foreground"] %>;
--destructive: <%- colors.dark["destructive"] %>;
--destructive-foreground: <%- colors.dark["destructive-foreground"] %>;
--border: <%- colors.dark["border"] %>;
--input: <%- colors.dark["input"] %>;
--ring: <%- colors.dark["ring"] %>;
--chart-1: <%- colors.dark["chart-1"] %>;
--chart-2: <%- colors.dark["chart-2"] %>;
--chart-3: <%- colors.dark["chart-3"] %>;
--chart-4: <%- colors.dark["chart-4"] %>;
--chart-5: <%- colors.dark["chart-5"] %>;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}`
for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone']) {
const base: Record<string, any> = {
inlineColors: {},
cssVars: {},
}
for (const [mode, values] of Object.entries(colorMapping)) {
base.inlineColors[mode] = {}
base.cssVars[mode] = {}
for (const [key, value] of Object.entries(values)) {
if (typeof value === 'string') {
// Chart colors do not have a 1-to-1 mapping with tailwind colors.
if (key.startsWith('chart-')) {
base.cssVars[mode][key] = value
continue
}
const resolvedColor = value.replace(/\{\{base\}\}-/g, `${baseColor}-`)
base.inlineColors[mode][key] = resolvedColor
const [resolvedBase, scale] = resolvedColor.split('-')
const color = scale
? colorsData[resolvedBase].find(
(item: any) => item.scale === Number.parseInt(scale),
)
: colorsData[resolvedBase]
if (color) {
base.cssVars[mode][key] = color.hslChannel
}
}
}
}
// Build css vars.
base.inlineColorsTemplate = template(BASE_STYLES)({})
base.cssVarsTemplate = template(BASE_STYLES_WITH_VARIABLES)({
colors: base.cssVars,
})
await writeFile(
path.join(REGISTRY_PATH, `colors/${baseColor}.json`),
JSON.stringify(base, null, 2),
)
// ----------------------------------------------------------------------------
// Build registry/themes.css
// ----------------------------------------------------------------------------
const THEME_STYLES_WITH_VARIABLES = `
.theme-<%- theme %> {
--background: <%- colors.light["background"] %>;
--foreground: <%- colors.light["foreground"] %>;
--muted: <%- colors.light["muted"] %>;
--muted-foreground: <%- colors.light["muted-foreground"] %>;
--popover: <%- colors.light["popover"] %>;
--popover-foreground: <%- colors.light["popover-foreground"] %>;
--card: <%- colors.light["card"] %>;
--card-foreground: <%- colors.light["card-foreground"] %>;
--border: <%- colors.light["border"] %>;
--input: <%- colors.light["input"] %>;
--primary: <%- colors.light["primary"] %>;
--primary-foreground: <%- colors.light["primary-foreground"] %>;
--secondary: <%- colors.light["secondary"] %>;
--secondary-foreground: <%- colors.light["secondary-foreground"] %>;
--accent: <%- colors.light["accent"] %>;
--accent-foreground: <%- colors.light["accent-foreground"] %>;
--destructive: <%- colors.light["destructive"] %>;
--destructive-foreground: <%- colors.light["destructive-foreground"] %>;
--ring: <%- colors.light["ring"] %>;
--radius: <%- colors.light["radius"] %>;
}
.dark .theme-<%- theme %> {
--background: <%- colors.dark["background"] %>;
--foreground: <%- colors.dark["foreground"] %>;
--muted: <%- colors.dark["muted"] %>;
--muted-foreground: <%- colors.dark["muted-foreground"] %>;
--popover: <%- colors.dark["popover"] %>;
--popover-foreground: <%- colors.dark["popover-foreground"] %>;
--card: <%- colors.dark["card"] %>;
--card-foreground: <%- colors.dark["card-foreground"] %>;
--border: <%- colors.dark["border"] %>;
--input: <%- colors.dark["input"] %>;
--primary: <%- colors.dark["primary"] %>;
--primary-foreground: <%- colors.dark["primary-foreground"] %>;
--secondary: <%- colors.dark["secondary"] %>;
--secondary-foreground: <%- colors.dark["secondary-foreground"] %>;
--accent: <%- colors.dark["accent"] %>;
--accent-foreground: <%- colors.dark["accent-foreground"] %>;
--destructive: <%- colors.dark["destructive"] %>;
--destructive-foreground: <%- colors.dark["destructive-foreground"] %>;
--ring: <%- colors.dark["ring"] %>;
}`
const themeCSS = []
for (const theme of baseColors) {
themeCSS.push(
template(THEME_STYLES_WITH_VARIABLES)({
colors: theme.cssVars,
theme: theme.name,
}),
)
}
await writeFile(
path.join(REGISTRY_PATH, `themes.css`),
themeCSS.join('\n'),
)
// ----------------------------------------------------------------------------
// Build registry/themes/[theme].json
// ----------------------------------------------------------------------------
rimraf.sync(path.join(REGISTRY_PATH, 'themes'))
for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone']) {
const payload: Record<string, any> = {
name: baseColor,
label: baseColor.charAt(0).toUpperCase() + baseColor.slice(1),
cssVars: {},
}
for (const [mode, values] of Object.entries(colorMapping)) {
payload.cssVars[mode] = {}
for (const [key, value] of Object.entries(values)) {
if (typeof value === 'string') {
const resolvedColor = value.replace(/\{\{base\}\}-/g, `${baseColor}-`)
payload.cssVars[mode][key] = resolvedColor
const [resolvedBase, scale] = resolvedColor.split('-')
const color = scale
? colorsData[resolvedBase].find(
(item: any) => item.scale === Number.parseInt(scale),
)
: colorsData[resolvedBase]
if (color) {
payload.cssVars[mode][key] = color.hslChannel
}
}
}
}
const targetPath = path.join(REGISTRY_PATH, 'themes')
// Create directory if it doesn't exist.
if (!existsSync(targetPath)) {
await fs.mkdir(targetPath, { recursive: true })
}
await writeFile(
path.join(targetPath, `${payload.name}.json`),
JSON.stringify(payload, null, 2),
)
}
}
}
// ----------------------------------------------------------------------------
// Build registry/icons/index.json.
// ----------------------------------------------------------------------------
async function buildIcons() {
const iconsTargetPath = path.join(REGISTRY_PATH, 'icons')
rimraf.sync(iconsTargetPath)
if (!existsSync(iconsTargetPath)) {
await fs.mkdir(iconsTargetPath, { recursive: true })
}
const iconsData = icons
await writeFile(
path.join(iconsTargetPath, 'index.json'),
JSON.stringify(iconsData, null, 2),
)
}
// ----------------------------------------------------------------------------
// Build __registry__/icons.ts.
// ----------------------------------------------------------------------------
async function buildRegistryIcons() {
let index = `// @ts-nocheck
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
import * as React from "react"
export const Icons = {
`
for (const [icon, libraries] of Object.entries(icons)) {
index += ` "${icon}": {`
for (const [library, componentName] of Object.entries(libraries)) {
const packageName = iconLibraries[library as keyof typeof iconLibraries].package
if (packageName) {
index += `
${library}: () => import("${packageName}").then(mod => ({
default: mod.${componentName}
})),`
}
}
index += `
},`
}
index += `
}
`
// Write style index.
rimraf.sync(path.join(process.cwd(), '__registry__/icons.ts'))
await writeFile(
path.join(process.cwd(), '__registry__/icons.ts'),
index,
)
}
try {
const content = await crawlContent()
const result = registrySchema.safeParse([...registry, ...content])
await writeFile(
path.join(REGISTRY_PATH, 'temp.json'),
JSON.stringify(result.data ?? '', null, 2),
)
if (!result.success) {
console.error(result.error)
process.exit(1)
}
await buildRegistry(result.data)
await buildStyles(result.data)
await buildStylesIndex()
await buildThemes()
// await buildRegistryIcons()
// await buildIcons()
// eslint-disable-next-line no-console
console.log('✅ Done!')
}
catch (error) {
console.error(error)
process.exit(1)
}
async function writeFile(path: string, payload: any) {
return fs.writeFile(
path,
`${payload}\r\n`,
'utf8',
)
}