mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-25 21:56:08 +00:00
154 lines
3.9 KiB
TypeScript
154 lines
3.9 KiB
TypeScript
"use server"
|
|
|
|
import { promises as fs } from "fs"
|
|
import { tmpdir } from "os"
|
|
import path from "path"
|
|
import { Index } from "@/__registry__"
|
|
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
|
|
import { z } from "zod"
|
|
|
|
import { highlightCode } from "@/lib/highlight-code"
|
|
import { BlockChunk, blockSchema, registryEntrySchema } from "@/registry/schema"
|
|
import { Style } from "@/registry/styles"
|
|
|
|
const DEFAULT_BLOCKS_STYLE = "default" satisfies Style["name"]
|
|
|
|
const project = new Project({
|
|
compilerOptions: {},
|
|
})
|
|
|
|
export async function getAllBlockIds(
|
|
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
|
) {
|
|
const blocks = await _getAllBlocks(style)
|
|
return blocks.map((block) => block.name)
|
|
}
|
|
|
|
export async function getBlock(
|
|
name: string,
|
|
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
|
) {
|
|
const entry = Index[style][name]
|
|
|
|
const content = await _getBlockContent(name, style)
|
|
|
|
const chunks = await Promise.all(
|
|
entry.chunks?.map(async (chunk: BlockChunk) => {
|
|
const code = await readFile(chunk.file)
|
|
|
|
const tempFile = await createTempSourceFile(`${chunk.name}.tsx`)
|
|
const sourceFile = project.createSourceFile(tempFile, code, {
|
|
scriptKind: ScriptKind.TSX,
|
|
})
|
|
|
|
sourceFile
|
|
.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
|
|
.filter((node) => {
|
|
return node.getAttribute("x-chunk") !== undefined
|
|
})
|
|
?.map((component) => {
|
|
component
|
|
.getAttribute("x-chunk")
|
|
?.asKind(SyntaxKind.JsxAttribute)
|
|
?.remove()
|
|
})
|
|
|
|
return {
|
|
...chunk,
|
|
code: sourceFile
|
|
.getText()
|
|
.replaceAll(`@/registry/${style}/`, "@/components/"),
|
|
}
|
|
})
|
|
)
|
|
|
|
return blockSchema.parse({
|
|
style,
|
|
highlightedCode: content.code ? await highlightCode(content.code) : "",
|
|
...entry,
|
|
...content,
|
|
chunks,
|
|
description: content.description || "",
|
|
type: "components:block",
|
|
})
|
|
}
|
|
|
|
async function _getAllBlocks(style: Style["name"] = DEFAULT_BLOCKS_STYLE) {
|
|
const index = z.record(registryEntrySchema).parse(Index[style])
|
|
|
|
return Object.values(index).filter(
|
|
(block) => block.type === "components:block"
|
|
)
|
|
}
|
|
|
|
async function _getBlockCode(
|
|
name: string,
|
|
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
|
) {
|
|
const entry = Index[style][name]
|
|
if (!entry) {
|
|
console.error(`Block ${name} not found in style ${style}`)
|
|
return ""
|
|
}
|
|
const block = registryEntrySchema.parse(entry)
|
|
|
|
if (!block.source) {
|
|
return ""
|
|
}
|
|
|
|
return await readFile(block.source)
|
|
}
|
|
|
|
async function readFile(source: string) {
|
|
const filepath = path.join(process.cwd(), source)
|
|
return await fs.readFile(filepath, "utf-8")
|
|
}
|
|
|
|
async function createTempSourceFile(filename: string) {
|
|
const dir = await fs.mkdtemp(path.join(tmpdir(), "codex-"))
|
|
return path.join(dir, filename)
|
|
}
|
|
|
|
async function _getBlockContent(name: string, style: Style["name"]) {
|
|
const raw = await _getBlockCode(name, style)
|
|
|
|
const tempFile = await createTempSourceFile(`${name}.tsx`)
|
|
const sourceFile = project.createSourceFile(tempFile, raw, {
|
|
scriptKind: ScriptKind.TSX,
|
|
})
|
|
|
|
// Extract meta.
|
|
const description = _extractVariable(sourceFile, "description")
|
|
const iframeHeight = _extractVariable(sourceFile, "iframeHeight")
|
|
const containerClassName = _extractVariable(sourceFile, "containerClassName")
|
|
|
|
// Format the code.
|
|
let code = sourceFile.getText()
|
|
code = code.replaceAll(`@/registry/${style}/`, "@/components/")
|
|
code = code.replaceAll("export default", "export")
|
|
|
|
return {
|
|
description,
|
|
code,
|
|
container: {
|
|
height: iframeHeight,
|
|
className: containerClassName,
|
|
},
|
|
}
|
|
}
|
|
|
|
function _extractVariable(sourceFile: SourceFile, name: string) {
|
|
const variable = sourceFile.getVariableDeclaration(name)
|
|
if (!variable) {
|
|
return null
|
|
}
|
|
|
|
const value = variable
|
|
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
|
.getLiteralValue()
|
|
|
|
variable.remove()
|
|
|
|
return value
|
|
}
|