mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-09 06:55:07 +00:00
* chore(shadcn): implement registries poc * feat(shadcn): refactor our initial implementation * feat(shadcn): properly resolve namespaced registryDependencies * feat(shadcn): resolve namespaced registries recursively * fix * feat(shadcn): implement dotenv support * test(shadcn): mock shadcn registry * fix * fix * fix * refactor(shadcn): update functions and tests * refactor(shadcn): add fetchFromRegistry (#7937) * fix * feat(shadcn): add shadcn as a built-in registry * fix * feat(shadcn): update no framework and shadcn
132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import { parseRegistryAndItemFromString } from "@/src/registry/parser"
|
|
import { configSchema, registryConfigItemSchema } from "@/src/registry/schema"
|
|
import { validateRegistryConfig } from "@/src/registry/validator"
|
|
import { z } from "zod"
|
|
|
|
import { expandEnvVars } from "./env"
|
|
|
|
const NAME_PLACEHOLDER = "{name}"
|
|
const STYLE_PLACEHOLDER = "{style}"
|
|
const ENV_VAR_PATTERN = /\${(\w+)}/g
|
|
const QUERY_PARAM_SEPARATOR = "?"
|
|
const QUERY_PARAM_DELIMITER = "&"
|
|
|
|
export function buildUrlAndHeadersForRegistryItem(
|
|
name: string,
|
|
config?: z.infer<typeof configSchema>
|
|
) {
|
|
const { registry, item } = parseRegistryAndItemFromString(name)
|
|
|
|
if (!registry) {
|
|
return null
|
|
}
|
|
|
|
const registries = config?.registries || {}
|
|
const registryConfig = registries[registry]
|
|
if (!registryConfig) {
|
|
throw new Error(
|
|
`Unknown registry "${registry}". Make sure it is defined in components.json as follows:\n` +
|
|
`{\n "registries": {\n "${registry}": "https://example.com/{name}.json"\n }\n}`
|
|
)
|
|
}
|
|
|
|
// TODO: I don't like this here.
|
|
// But this will do for now.
|
|
validateRegistryConfig(registry, registryConfig)
|
|
|
|
return {
|
|
url: buildUrlFromRegistryConfig(item, registryConfig, config),
|
|
headers: buildHeadersFromRegistryConfig(registryConfig),
|
|
}
|
|
}
|
|
|
|
export function buildUrlFromRegistryConfig(
|
|
item: string,
|
|
registryConfig: z.infer<typeof registryConfigItemSchema>,
|
|
config?: z.infer<typeof configSchema>
|
|
) {
|
|
if (typeof registryConfig === "string") {
|
|
let url = registryConfig.replace(NAME_PLACEHOLDER, item)
|
|
if (config?.style && url.includes(STYLE_PLACEHOLDER)) {
|
|
url = url.replace(STYLE_PLACEHOLDER, config.style)
|
|
}
|
|
return expandEnvVars(url)
|
|
}
|
|
|
|
let baseUrl = registryConfig.url.replace(NAME_PLACEHOLDER, item)
|
|
if (config?.style && baseUrl.includes(STYLE_PLACEHOLDER)) {
|
|
baseUrl = baseUrl.replace(STYLE_PLACEHOLDER, config.style)
|
|
}
|
|
baseUrl = expandEnvVars(baseUrl)
|
|
|
|
if (!registryConfig.params) {
|
|
return baseUrl
|
|
}
|
|
|
|
return appendQueryParams(baseUrl, registryConfig.params)
|
|
}
|
|
|
|
export function buildHeadersFromRegistryConfig(
|
|
config: z.infer<typeof registryConfigItemSchema>
|
|
) {
|
|
if (typeof config === "string" || !config.headers) {
|
|
return {}
|
|
}
|
|
|
|
const headers: Record<string, string> = {}
|
|
|
|
for (const [key, value] of Object.entries(config.headers)) {
|
|
const expandedValue = expandEnvVars(value)
|
|
|
|
if (shouldIncludeHeader(value, expandedValue)) {
|
|
headers[key] = expandedValue
|
|
}
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
function appendQueryParams(baseUrl: string, params: Record<string, string>) {
|
|
const urlParams = new URLSearchParams()
|
|
|
|
for (const [key, value] of Object.entries(params)) {
|
|
const expandedValue = expandEnvVars(value)
|
|
if (expandedValue) {
|
|
urlParams.append(key, expandedValue)
|
|
}
|
|
}
|
|
|
|
const queryString = urlParams.toString()
|
|
if (!queryString) {
|
|
return baseUrl
|
|
}
|
|
|
|
const separator = baseUrl.includes(QUERY_PARAM_SEPARATOR)
|
|
? QUERY_PARAM_DELIMITER
|
|
: QUERY_PARAM_SEPARATOR
|
|
|
|
return `${baseUrl}${separator}${queryString}`
|
|
}
|
|
|
|
function shouldIncludeHeader(originalValue: string, expandedValue: string) {
|
|
const trimmedExpanded = expandedValue.trim()
|
|
|
|
if (!trimmedExpanded) {
|
|
return false
|
|
}
|
|
|
|
// If the original value contains valid env vars, only include if expansion changed the value.
|
|
if (originalValue.includes("${")) {
|
|
// Check if there are actual env vars in the string
|
|
const envVars = originalValue.match(ENV_VAR_PATTERN)
|
|
if (envVars) {
|
|
const templateWithoutVars = originalValue
|
|
.replace(ENV_VAR_PATTERN, "")
|
|
.trim()
|
|
return trimmedExpanded !== templateWithoutVars
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|