Files
shadcn-ui/packages/shadcn/src/registry/api.ts
shadcn 4f5333ea7a feat(shadcn): add view command (#7994)
* feat(shadcn): add view command

* test(shadcn): add tests for view command

* feat(shadcn): allow shadow config in view command

* chore: changeset

* test(shadcn): skip view

* test(shadcn): update view port number

* feat(shadcn): add list command

* fix

* feat(shadcn): implement search command

* fix: tests

* fix

* chore: update changesets
2025-08-11 13:01:05 +04:00

218 lines
5.1 KiB
TypeScript

import path from "path"
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
import { configWithDefaults } from "@/src/registry/config"
import { BASE_COLORS } from "@/src/registry/constants"
import {
clearRegistryContext,
setRegistryHeaders,
} from "@/src/registry/context"
import {
RegistryNotFoundError,
RegistryParseError,
} from "@/src/registry/errors"
import { fetchRegistry } from "@/src/registry/fetcher"
import {
fetchRegistryItems,
resolveRegistryTree,
} from "@/src/registry/resolver"
import {
iconsSchema,
registryBaseColorSchema,
registryIndexSchema,
registryItemSchema,
registrySchema,
stylesSchema,
} from "@/src/schema"
import { Config } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { z } from "zod"
// Schema for validating registry names
const registryNameSchema = z
.string()
.regex(
/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/,
"Registry name must start with @ followed by alphanumeric characters, hyphens, or underscores"
)
export async function getRegistry(
name: `@${string}`,
config?: Partial<Config>
) {
// Validate the registry name using Zod schema
try {
registryNameSchema.parse(name.split("/")[0])
} catch (error) {
throw new RegistryParseError(name, error)
}
if (!name.endsWith("/registry")) {
name = `${name}/registry`
}
const urlAndHeaders = buildUrlAndHeadersForRegistryItem(
name,
configWithDefaults(config)
)
if (!urlAndHeaders?.url) {
throw new RegistryNotFoundError(name)
}
// Set headers in context if provided
if (urlAndHeaders.headers && Object.keys(urlAndHeaders.headers).length > 0) {
setRegistryHeaders({
[urlAndHeaders.url]: urlAndHeaders.headers,
})
}
const [result] = await fetchRegistry([urlAndHeaders.url])
try {
return registrySchema.parse(result)
} catch (error) {
throw new RegistryParseError(name, error)
}
}
export async function getRegistryItems(
items: string[],
config?: Partial<Config>,
options: { useCache?: boolean } = {}
) {
clearRegistryContext()
return fetchRegistryItems(items, configWithDefaults(config), options)
}
export async function resolveRegistryItems(
items: string[],
config?: Partial<Config>,
options: { useCache?: boolean } = {}
) {
clearRegistryContext()
return resolveRegistryTree(items, configWithDefaults(config), options)
}
export async function getShadcnRegistryIndex() {
try {
const [result] = await fetchRegistry(["index.json"])
return registryIndexSchema.parse(result)
} catch (error) {
logger.error("\n")
handleError(error)
}
}
export async function getRegistryStyles() {
try {
const [result] = await fetchRegistry(["styles/index.json"])
return stylesSchema.parse(result)
} catch (error) {
logger.error("\n")
handleError(error)
return []
}
}
export async function getRegistryIcons() {
try {
const [result] = await fetchRegistry(["icons/index.json"])
return iconsSchema.parse(result)
} catch (error) {
handleError(error)
return {}
}
}
export async function getRegistryBaseColors() {
return BASE_COLORS
}
export async function getRegistryBaseColor(baseColor: string) {
try {
const [result] = await fetchRegistry([`colors/${baseColor}.json`])
return registryBaseColorSchema.parse(result)
} catch (error) {
handleError(error)
}
}
/**
* @deprecated This function is deprecated and will be removed in a future version.
*/
export async function resolveTree(
index: z.infer<typeof registryIndexSchema>,
names: string[]
) {
const tree: z.infer<typeof registryIndexSchema> = []
for (const name of names) {
const entry = index.find((entry) => entry.name === name)
if (!entry) {
continue
}
tree.push(entry)
if (entry.registryDependencies) {
const dependencies = await resolveTree(index, entry.registryDependencies)
tree.push(...dependencies)
}
}
return tree.filter(
(component, index, self) =>
self.findIndex((c) => c.name === component.name) === index
)
}
/**
* @deprecated This function is deprecated and will be removed in a future version.
*/
export async function fetchTree(
style: string,
tree: z.infer<typeof registryIndexSchema>
) {
try {
const paths = tree.map((item) => `styles/${style}/${item.name}.json`)
const results = await fetchRegistry(paths)
return results.map((result) => registryItemSchema.parse(result))
} catch (error) {
handleError(error)
return []
}
}
/**
* @deprecated This function is deprecated and will be removed in a future version.
*/
export async function getItemTargetPath(
config: Config,
item: Pick<z.infer<typeof registryItemSchema>, "type">,
override?: string
) {
if (override) {
return override
}
if (item.type === "registry:ui") {
return config.resolvedPaths.ui ?? config.resolvedPaths.components
}
const [parent, type] = item.type?.split(":") ?? []
if (!(parent in config.resolvedPaths)) {
return null
}
return path.join(
config.resolvedPaths[parent as keyof typeof config.resolvedPaths],
type
)
}