UNPKG

shadcn-vue

Version:
1,038 lines (1,025 loc) 37.5 kB
import path from "pathe"; import { z } from "zod"; import { promises } from "node:fs"; import fs from "fs-extra"; import { createPathsMatcher, getTsconfig, parseTsconfig } from "get-tsconfig"; import { glob } from "tinyglobby"; import { cosmiconfig } from "cosmiconfig"; import { colors } from "consola/utils"; import consola from "consola"; import ora from "ora"; import deepmerge from "deepmerge"; import { ofetch } from "ofetch"; import { ProxyAgent } from "undici"; import { tmpdir } from "node:os"; import objectToString from "stringify-object"; import { Project, QuoteKind, ScriptKind, SyntaxKind } from "ts-morph"; //#region src/utils/frameworks.ts const FRAMEWORKS = { vite: { name: "vite", label: "Vite", links: { installation: "https://shadcn-vue.com/docs/installation/vite", tailwind: "https://tailwindcss.com/docs/guides/vite" } }, nuxt: { name: "nuxt", label: "Nuxt", links: { installation: "https://shadcn-vue.com/docs/installation/nuxt", tailwind: "https://tailwindcss.com/docs/guides/nuxtjs" } }, astro: { name: "astro", label: "Astro", links: { installation: "https://shadcn-vue.com/docs/installation/astro", tailwind: "https://tailwindcss.com/docs/guides/astro" } }, laravel: { name: "laravel", label: "Laravel", links: { installation: "https://shadcn-vue.com/docs/installation/laravel", tailwind: "https://tailwindcss.com/docs/guides/laravel" } }, manual: { name: "manual", label: "Manual", links: { installation: "https://shadcn-vue.com/docs/installation/manual", tailwind: "https://tailwindcss.com/docs/installation" } } }; //#endregion //#region src/utils/resolve-import.ts function resolveImport(importPath, config) { const matcher = createPathsMatcher(config); if (matcher === null) return; const paths = matcher(importPath); return paths[0]; } //#endregion //#region src/utils/highlighter.ts const highlighter = { error: colors.red, warn: colors.yellow, info: colors.cyan, success: colors.green }; //#endregion //#region src/utils/get-config.ts /** @deprecated */ const TAILWIND_CSS_PATH = { nuxt: "assets/css/tailwind.css", vite: "src/assets/index.css", laravel: "resources/css/app.css", astro: "src/styles/globals.css" }; const DEFAULT_COMPONENTS = "@/components"; const DEFAULT_UTILS = "@/lib/utils"; const DEFAULT_TAILWIND_CSS = TAILWIND_CSS_PATH.nuxt; const DEFAULT_TAILWIND_CONFIG = "tailwind.config.js"; const explorer = cosmiconfig("components", { searchPlaces: ["components.json"] }); const rawConfigSchema = z.object({ $schema: z.string().optional(), style: z.string(), typescript: z.boolean().default(true), tailwind: z.object({ config: z.string().optional(), css: z.string(), baseColor: z.string(), cssVariables: z.boolean().default(true), prefix: z.string().default("").optional() }), aliases: z.object({ components: z.string(), composables: z.string().optional(), utils: z.string(), ui: z.string().optional(), lib: z.string().optional() }), iconLibrary: z.string().optional() }).strict(); const configSchema = rawConfigSchema.extend({ resolvedPaths: z.object({ cwd: z.string(), tailwindConfig: z.string(), tailwindCss: z.string(), utils: z.string(), components: z.string(), composables: z.string(), lib: z.string(), ui: z.string() }) }); const workspaceConfigSchema = z.record(configSchema); async function getConfig(cwd) { const config = await getRawConfig(cwd); if (!config) return null; if (!config.iconLibrary) config.iconLibrary = config.style === "new-york" ? "radix" : "lucide"; return await resolveConfigPaths(cwd, config); } function getTSConfig(cwd, tsconfigName) { const parsedConfig = getTsconfig(path.resolve(cwd, "package.json"), tsconfigName); if (parsedConfig === null) throw new Error(`Failed to find ${highlighter.info(tsconfigName)}`); return parsedConfig; } async function resolveConfigPaths(cwd, config) { const tsconfigType = config.typescript ? "tsconfig.json" : "jsconfig.json"; const tsConfig = getTSConfig(cwd, tsconfigType); return configSchema.parse({ ...config, resolvedPaths: { cwd, tailwindConfig: config.tailwind.config ? path.resolve(cwd, config.tailwind.config) : "", tailwindCss: path.resolve(cwd, config.tailwind.css), utils: await resolveImport(config.aliases.utils, tsConfig), components: await resolveImport(config.aliases.components, tsConfig), ui: config.aliases.ui ? await resolveImport(config.aliases.ui, tsConfig) : path.resolve(await resolveImport(config.aliases.components, tsConfig) ?? cwd, "ui"), lib: config.aliases.lib ? await resolveImport(config.aliases.lib, tsConfig) : path.resolve(await resolveImport(config.aliases.utils, tsConfig) ?? cwd, ".."), composables: config.aliases.composables ? await resolveImport(config.aliases.composables, tsConfig) : path.resolve(await resolveImport(config.aliases.components, tsConfig) ?? cwd, "..", "composables") } }); } async function getRawConfig(cwd) { try { const configResult = await explorer.search(cwd); if (!configResult) return null; return rawConfigSchema.parse(configResult.config); } catch (error) { throw new Error(`Invalid configuration found in ${cwd}/components.json.`); } } async function getWorkspaceConfig(config) { let resolvedAliases = {}; for (const key of Object.keys(config.aliases)) { if (!isAliasKey(key, config)) continue; const resolvedPath = config.resolvedPaths[key]; const packageRoot = await findPackageRoot(config.resolvedPaths.cwd, resolvedPath); if (!packageRoot) { resolvedAliases[key] = config; continue; } resolvedAliases[key] = await getConfig(packageRoot); } const result = workspaceConfigSchema.safeParse(resolvedAliases); if (!result.success) return null; return result.data; } async function findPackageRoot(cwd, resolvedPath) { const commonRoot = findCommonRoot(cwd, resolvedPath); const relativePath = path.relative(commonRoot, resolvedPath); const packageRoots = await glob("**/package.json", { cwd: commonRoot, deep: 3, ignore: [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/public/**" ] }); const matchingPackageRoot = packageRoots.map((pkgPath) => path.dirname(pkgPath)).find((pkgDir) => relativePath.startsWith(pkgDir)); return matchingPackageRoot ? path.join(commonRoot, matchingPackageRoot) : null; } function isAliasKey(key, config) { return Object.keys(config.resolvedPaths).filter((key$1) => key$1 !== "utils").includes(key); } function findCommonRoot(cwd, resolvedPath) { const parts1 = cwd.split(path.sep); const parts2 = resolvedPath.split(path.sep); const commonParts = []; for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) { if (parts1[i] !== parts2[i]) break; commonParts.push(parts1[i]); } return commonParts.join(path.sep); } async function getTargetStyleFromConfig(cwd, fallback) { const projectInfo = await getProjectInfo(cwd); return projectInfo?.tailwindVersion === "v4" ? "new-york-v4" : fallback; } //#endregion //#region src/utils/get-package-info.ts function getPackageInfo(cwd = "", shouldThrow = true) { const packageJsonPath = path.join(cwd, "package.json"); return fs.readJSONSync(packageJsonPath, { throws: shouldThrow }); } //#endregion //#region src/utils/get-project-info.ts const PROJECT_SHARED_IGNORE = [ "**/node_modules/**", ".nuxt", "public", "dist", "build" ]; const TS_CONFIG_SCHEMA = z.object({ compilerOptions: z.object({ paths: z.record(z.string().or(z.array(z.string()))) }) }); async function getProjectInfo(cwd) { const [configFiles, typescript, tailwindConfigFile, tailwindCssFile, tailwindVersion, aliasPrefix, packageJson] = await Promise.all([ glob("**/{nuxt,vite,astro}.config.*|composer.json", { cwd, deep: 3, ignore: PROJECT_SHARED_IGNORE }), isTypeScriptProject(cwd), getTailwindConfigFile(cwd), getTailwindCssFile(cwd), getTailwindVersion(cwd), getTsConfigAliasPrefix(cwd), getPackageInfo(cwd, false) ]); const type = { framework: FRAMEWORKS.manual, typescript, tailwindConfigFile, tailwindCssFile, tailwindVersion, aliasPrefix }; if (configFiles.find((file) => file.startsWith("nuxt.config."))?.length) { type.framework = FRAMEWORKS.nuxt; return type; } if (configFiles.find((file) => file.startsWith("astro.config."))?.length) { type.framework = FRAMEWORKS.astro; return type; } if (configFiles.find((file) => file.startsWith("composer.json"))?.length) { type.framework = FRAMEWORKS.laravel; return type; } if (configFiles.find((file) => file.startsWith("vite.config."))?.length) { type.framework = FRAMEWORKS.vite; return type; } return type; } async function getTailwindVersion(cwd) { const [packageInfo, config] = await Promise.all([getPackageInfo(cwd), getConfig(cwd)]); if (config?.tailwind?.config === "") return "v4"; if (!packageInfo?.dependencies?.tailwindcss && !packageInfo?.devDependencies?.tailwindcss) return null; if (/^(?:\^|~)?3(?:\.\d+)*(?:-.*)?$/.test(packageInfo?.dependencies?.tailwindcss || packageInfo?.devDependencies?.tailwindcss || "")) return "v3"; return "v4"; } async function getTailwindCssFile(cwd) { const [files, tailwindVersion] = await Promise.all([glob(["**/*.css", "**/*.scss"], { cwd, deep: 5, ignore: PROJECT_SHARED_IGNORE }), getTailwindVersion(cwd)]); if (!files.length) return null; const needle = tailwindVersion === "v4" ? `@import "tailwindcss"` : "@tailwind base"; for (const file of files) { const contents = await fs.readFile(path.resolve(cwd, file), "utf8"); if (contents.includes(`@import "tailwindcss"`) || contents.includes(`@import 'tailwindcss'`) || contents.includes(`@tailwind base`)) return file; } return null; } async function getTailwindConfigFile(cwd) { const files = await glob("tailwind.config.*", { cwd, deep: 3, ignore: PROJECT_SHARED_IGNORE }); if (!files.length) return null; return files[0]; } async function getTsConfigAliasPrefix(cwd) { const isTypescript = await isTypeScriptProject(cwd); const tsconfigType = isTypescript ? "tsconfig.json" : "jsconfig.json"; const tsConfig = getTSConfig(cwd, tsconfigType); const parsedTsConfig = parseTsconfig(tsConfig.path); const aliasPaths = parsedTsConfig.compilerOptions?.paths ?? {}; for (const [alias, paths] of Object.entries(aliasPaths)) if (paths.includes("./*") || paths.includes("./src/*") || paths.includes("./app/*") || paths.includes("./resources/js/*")) { const cleanAlias = alias.replace(/\/\*$/, "") ?? null; return cleanAlias === "#build" ? "@" : cleanAlias; } return Object.keys(aliasPaths)?.[0]?.replace(/\/\*$/, "") ?? null; } async function isTypeScriptProject(cwd) { const files = await glob("tsconfig.*", { cwd, deep: 1, ignore: PROJECT_SHARED_IGNORE }); return files.length > 0; } async function getProjectConfig(cwd, defaultProjectInfo = null) { const [existingConfig, projectInfo] = await Promise.all([getConfig(cwd), !defaultProjectInfo ? getProjectInfo(cwd) : Promise.resolve(defaultProjectInfo)]); if (existingConfig) return existingConfig; if (!projectInfo || !projectInfo.tailwindCssFile || projectInfo.tailwindVersion === "v3" && !projectInfo.tailwindConfigFile) return null; const config = { $schema: "https://shadcn-vue.com/schema.json", typescript: projectInfo.typescript, style: "new-york", tailwind: { config: projectInfo.tailwindConfigFile ?? "", baseColor: "zinc", css: projectInfo.tailwindCssFile, cssVariables: true, prefix: "" }, iconLibrary: "lucide", aliases: { components: `${projectInfo.aliasPrefix}/components`, ui: `${projectInfo.aliasPrefix}/components/ui`, composables: `${projectInfo.aliasPrefix}/composables`, lib: `${projectInfo.aliasPrefix}/lib`, utils: `${projectInfo.aliasPrefix}/lib/utils` } }; return await resolveConfigPaths(cwd, config); } async function getProjectTailwindVersionFromConfig(config) { if (!config.resolvedPaths?.cwd) return "v3"; const projectInfo = await getProjectInfo(config.resolvedPaths.cwd); if (!projectInfo?.tailwindVersion) return null; return projectInfo.tailwindVersion; } //#endregion //#region src/utils/logger.ts const logger = { error(...args) { consola.log(highlighter.error(args.join(" "))); }, warn(...args) { consola.log(highlighter.warn(args.join(" "))); }, info(...args) { consola.log(highlighter.info(args.join(" "))); }, success(...args) { consola.log(highlighter.success(args.join(" "))); }, log(...args) { consola.log(args.join(" ")); }, break() { consola.log(""); } }; //#endregion //#region src/utils/spinner.ts function spinner(text, options) { return ora({ text, isSilent: options?.silent }); } //#endregion //#region src/registry/schema.ts const registryItemTypeSchema = z.enum([ "registry:lib", "registry:block", "registry:component", "registry:ui", "registry:hook", "registry:composable", "registry:page", "registry:file", "registry:theme", "registry:style", "registry:example", "registry:internal" ]); const registryItemFileSchema = z.discriminatedUnion("type", [z.object({ path: z.string(), content: z.string().optional(), type: z.enum(["registry:file", "registry:page"]), target: z.string() }), z.object({ path: z.string(), content: z.string().optional(), type: registryItemTypeSchema.exclude(["registry:file", "registry:page"]), target: z.string().optional() })]); const registryItemTailwindSchema = z.object({ config: z.object({ content: z.array(z.string()).optional(), theme: z.record(z.string(), z.any()).optional(), plugins: z.array(z.string()).optional() }).optional() }); const registryItemCssVarsSchema = z.object({ theme: z.record(z.string(), z.string()).optional(), light: z.record(z.string(), z.string()).optional(), dark: z.record(z.string(), z.string()).optional() }); const registryItemCssSchema = z.record(z.string(), z.lazy(() => z.union([z.string(), z.record(z.string(), z.union([z.string(), z.record(z.string(), z.string())]))]))); const registryItemSchema = z.object({ $schema: z.string().optional(), extends: z.string().optional(), name: z.string(), type: registryItemTypeSchema, title: z.string().optional(), author: z.string().min(2).optional(), description: z.string().optional(), dependencies: z.array(z.string()).optional(), devDependencies: z.array(z.string()).optional(), registryDependencies: z.array(z.string()).optional(), files: z.array(registryItemFileSchema).optional(), tailwind: registryItemTailwindSchema.optional(), cssVars: registryItemCssVarsSchema.optional(), css: registryItemCssSchema.optional(), meta: z.record(z.string(), z.any()).optional(), docs: z.string().optional(), categories: z.array(z.string()).optional() }); const registrySchema = z.object({ name: z.string(), homepage: z.string(), items: z.array(registryItemSchema) }); const registryIndexSchema = z.array(registryItemSchema); const stylesSchema = z.array(z.object({ name: z.string(), label: z.string() })); const iconsSchema = z.record(z.string(), z.record(z.string(), z.string())); const registryBaseColorSchema = z.object({ inlineColors: z.object({ light: z.record(z.string(), z.string()), dark: z.record(z.string(), z.string()) }), cssVars: registryItemCssVarsSchema, cssVarsV4: registryItemCssVarsSchema.optional(), inlineColorsTemplate: z.string(), cssVarsTemplate: z.string() }); const registryResolvedItemsTreeSchema = registryItemSchema.pick({ dependencies: true, devDependencies: true, files: true, tailwind: true, cssVars: true, css: true, docs: true }); //#endregion //#region src/utils/handle-error.ts function handleError(error) { logger.error(`Something went wrong. Please check the error below for more details.`); logger.error(`If the problem persists, please open an issue on GitHub.`); logger.error(""); if (typeof error === "string") { logger.error(error); logger.break(); process.exit(1); } if (error instanceof z.ZodError) { logger.error("Validation failed:"); for (const [key, value] of Object.entries(error.flatten().fieldErrors)) logger.error(`- ${highlighter.info(key)}: ${value}`); logger.break(); process.exit(1); } if (error instanceof Error) { logger.error(error.message); logger.break(); process.exit(1); } logger.break(); process.exit(1); } //#endregion //#region src/utils/updaters/update-tailwind-config.ts async function updateTailwindConfig(tailwindConfig, config, options) { if (!tailwindConfig) return; options = { silent: false, tailwindVersion: "v3", ...options }; if (options.tailwindVersion === "v4") return; const tailwindFileRelativePath = path.relative(config.resolvedPaths.cwd, config.resolvedPaths.tailwindConfig); const tailwindSpinner = spinner(`Updating ${highlighter.info(tailwindFileRelativePath)}`, { silent: options.silent }).start(); const raw = await promises.readFile(config.resolvedPaths.tailwindConfig, "utf8"); const output = await transformTailwindConfig(raw, tailwindConfig, config); await promises.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8"); tailwindSpinner?.succeed(); } async function transformTailwindConfig(input, tailwindConfig, config) { const sourceFile = await _createSourceFile(input, config); const configObject = sourceFile.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression).find((node) => node.getProperties().some((property) => property.isKind(SyntaxKind.PropertyAssignment) && property.getName() === "content")); if (!configObject) return input; const quoteChar = _getQuoteChar(configObject); addTailwindConfigProperty(configObject, { name: "darkMode", value: "class" }, { quoteChar }); tailwindConfig.plugins?.forEach((plugin) => { addTailwindConfigPlugin(configObject, plugin); }); if (tailwindConfig.theme) await addTailwindConfigTheme(configObject, tailwindConfig.theme); return sourceFile.getFullText(); } function addTailwindConfigProperty(configObject, property, { quoteChar }) { const existingProperty = configObject.getProperty("darkMode"); if (!existingProperty) { const newProperty = { name: property.name, initializer: `[${quoteChar}${property.value}${quoteChar}]` }; if (property.name === "darkMode") { configObject.insertPropertyAssignment(0, newProperty); return configObject; } configObject.addPropertyAssignment(newProperty); return configObject; } if (existingProperty.isKind(SyntaxKind.PropertyAssignment)) { const initializer = existingProperty.getInitializer(); const newValue = `${quoteChar}${property.value}${quoteChar}`; if (initializer?.isKind(SyntaxKind.StringLiteral)) { const initializerText = initializer.getText(); initializer.replaceWithText(`[${initializerText}, ${newValue}]`); return configObject; } if (initializer?.isKind(SyntaxKind.ArrayLiteralExpression)) { if (initializer.getElements().map((element) => element.getText()).includes(newValue)) return configObject; initializer.addElement(newValue); } return configObject; } return configObject; } async function addTailwindConfigTheme(configObject, theme) { if (!configObject.getProperty("theme")) configObject.addPropertyAssignment({ name: "theme", initializer: "{}" }); nestSpreadProperties(configObject); const themeProperty = configObject.getPropertyOrThrow("theme")?.asKindOrThrow(SyntaxKind.PropertyAssignment); const themeInitializer = themeProperty.getInitializer(); if (themeInitializer?.isKind(SyntaxKind.ObjectLiteralExpression)) { const themeObjectString = themeInitializer.getText(); const themeObject = await parseObjectLiteral(themeObjectString); const result = deepmerge(themeObject, theme, { arrayMerge: (dst, src) => src }); const resultString = objectToString(result).replace(/'\.\.\.(.*)'/g, "...$1").replace(/'"/g, "'").replace(/"'/g, "'").replace(/'\[/g, "[").replace(/\]'/g, "]").replace(/'\\'/g, "'").replace(/\\'/g, "'").replace(/\\''/g, "'").replace(/''/g, "'"); themeInitializer.replaceWithText(resultString); } unnestSpreadProperties(configObject); } function addTailwindConfigPlugin(configObject, plugin) { const existingPlugins = configObject.getProperty("plugins"); if (!existingPlugins) { configObject.addPropertyAssignment({ name: "plugins", initializer: `[${plugin}]` }); return configObject; } if (existingPlugins.isKind(SyntaxKind.PropertyAssignment)) { const initializer = existingPlugins.getInitializer(); if (initializer?.isKind(SyntaxKind.ArrayLiteralExpression)) { if (initializer.getElements().map((element) => { return element.getText().replace(/["']/g, ""); }).includes(plugin.replace(/["']/g, ""))) return configObject; initializer.addElement(plugin); } return configObject; } return configObject; } async function _createSourceFile(input, config) { const dir = await promises.mkdtemp(path.join(tmpdir(), "shadcn-")); const resolvedPath = config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"; const tempFile = path.join(dir, `shadcn-${path.basename(resolvedPath)}`); const project = new Project({ compilerOptions: {} }); const sourceFile = project.createSourceFile(tempFile, input, { scriptKind: path.extname(resolvedPath) === ".ts" ? ScriptKind.TS : ScriptKind.JS }); return sourceFile; } function _getQuoteChar(configObject) { return configObject.getFirstDescendantByKind(SyntaxKind.StringLiteral)?.getQuoteKind() === QuoteKind.Single ? "'" : "\""; } function nestSpreadProperties(obj) { const properties = obj.getProperties(); for (let i = 0; i < properties.length; i++) { const prop = properties[i]; if (prop.isKind(SyntaxKind.SpreadAssignment)) { const spreadAssignment = prop.asKindOrThrow(SyntaxKind.SpreadAssignment); const spreadText = spreadAssignment.getExpression().getText(); obj.insertPropertyAssignment(i, { name: `"___${spreadText.replace(/^\.\.\./, "")}"`, initializer: `"...${spreadText.replace(/^\.\.\./, "")}"` }); spreadAssignment.remove(); } else if (prop.isKind(SyntaxKind.PropertyAssignment)) { const propAssignment = prop.asKindOrThrow(SyntaxKind.PropertyAssignment); const initializer = propAssignment.getInitializer(); if (initializer && initializer.isKind(SyntaxKind.ObjectLiteralExpression)) nestSpreadProperties(initializer.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)); else if (initializer && initializer.isKind(SyntaxKind.ArrayLiteralExpression)) nestSpreadElements(initializer.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)); } } } function nestSpreadElements(arr) { const elements = arr.getElements(); for (let j = 0; j < elements.length; j++) { const element = elements[j]; if (element.isKind(SyntaxKind.ObjectLiteralExpression)) nestSpreadProperties(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)); else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) nestSpreadElements(element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)); else if (element.isKind(SyntaxKind.SpreadElement)) { const spreadText = element.getText(); arr.removeElement(j); arr.insertElement(j, `"${spreadText}"`); } } } function unnestSpreadProperties(obj) { const properties = obj.getProperties(); for (let i = 0; i < properties.length; i++) { const prop = properties[i]; if (prop.isKind(SyntaxKind.PropertyAssignment)) { const propAssignment = prop; const initializer = propAssignment.getInitializer(); if (initializer && initializer.isKind(SyntaxKind.StringLiteral)) { const value = initializer.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue(); if (value.startsWith("...")) { obj.insertSpreadAssignment(i, { expression: value.slice(3) }); propAssignment.remove(); } } else if (initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) unnestSpreadProperties(initializer); else if (initializer && initializer.isKind(SyntaxKind.ArrayLiteralExpression)) unnsetSpreadElements(initializer.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)); } } } function unnsetSpreadElements(arr) { const elements = arr.getElements(); for (let j = 0; j < elements.length; j++) { const element = elements[j]; if (element.isKind(SyntaxKind.ObjectLiteralExpression)) unnestSpreadProperties(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)); else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) unnsetSpreadElements(element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)); else if (element.isKind(SyntaxKind.StringLiteral)) { const spreadText = element.getText(); const spreadTest = /^['"](\.\.\..*)['"]$/g; if (spreadTest.test(spreadText)) { arr.removeElement(j); arr.insertElement(j, spreadText.replace(spreadTest, "$1")); } } } } async function parseObjectLiteral(objectLiteralString) { const sourceFile = await _createSourceFile(`const theme = ${objectLiteralString}`, null); const statement = sourceFile.getStatements()[0]; if (statement?.getKind() === SyntaxKind.VariableStatement) { const declaration = statement.getDeclarationList()?.getDeclarations()[0]; const initializer = declaration.getInitializer(); if (initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) return await parseObjectLiteralExpression(initializer); } throw new Error("Invalid input: not an object literal"); } function parseObjectLiteralExpression(node) { const result = {}; for (const property of node.getProperties()) if (property.isKind(SyntaxKind.PropertyAssignment)) { const name = property.getName().replace(/'/g, ""); if (property.getInitializer()?.isKind(SyntaxKind.ObjectLiteralExpression)) result[name] = parseObjectLiteralExpression(property.getInitializer()); else if (property.getInitializer()?.isKind(SyntaxKind.ArrayLiteralExpression)) result[name] = parseArrayLiteralExpression(property.getInitializer()); else result[name] = parseValue(property.getInitializer()); } return result; } function parseArrayLiteralExpression(node) { const result = []; for (const element of node.getElements()) if (element.isKind(SyntaxKind.ObjectLiteralExpression)) result.push(parseObjectLiteralExpression(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))); else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) result.push(parseArrayLiteralExpression(element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression))); else result.push(parseValue(element)); return result; } function parseValue(node) { switch (node.getKind()) { case SyntaxKind.StringLiteral: return node.getText(); case SyntaxKind.NumericLiteral: return Number(node.getText()); case SyntaxKind.TrueKeyword: return true; case SyntaxKind.FalseKeyword: return false; case SyntaxKind.NullKeyword: return null; case SyntaxKind.ArrayLiteralExpression: return node.getElements().map(parseValue); case SyntaxKind.ObjectLiteralExpression: return parseObjectLiteralExpression(node); default: return node.getText(); } } function buildTailwindThemeColorsFromCssVars(cssVars) { const result = {}; for (const key of Object.keys(cssVars)) { const parts = key.split("-"); const colorName = parts[0]; const subType = parts.slice(1).join("-"); if (subType === "") if (typeof result[colorName] === "object") result[colorName].DEFAULT = `hsl(var(--${key}))`; else result[colorName] = `hsl(var(--${key}))`; else { if (typeof result[colorName] !== "object") result[colorName] = { DEFAULT: `hsl(var(--${colorName}))` }; result[colorName][subType] = `hsl(var(--${key}))`; } } for (const [colorName, value] of Object.entries(result)) if (typeof value === "object" && value.DEFAULT === `hsl(var(--${colorName}))` && !(colorName in cssVars)) delete value.DEFAULT; return result; } //#endregion //#region src/registry/api.ts const REGISTRY_URL = process.env.REGISTRY_URL ?? "https://shadcn-vue.com/r"; const agent = process.env.https_proxy ? new ProxyAgent(process.env.https_proxy) : void 0; const registryCache = new Map(); async function getRegistryIndex() { try { const [result] = await fetchRegistry(["index.json"]); return registryIndexSchema.parse(result); } catch (error) { logger.error("\n"); handleError(error); } } async function getRegistryStyles() { try { const [result] = await fetchRegistry(["styles/index.json"]); return stylesSchema.parse(result); } catch (error) { logger.error("\n"); handleError(error); return []; } } async function getRegistryIcons() { try { const [result] = await fetchRegistry(["icons/index.json"]); return iconsSchema.parse(result); } catch (error) { handleError(error); return {}; } } async function getRegistryItem(name, style) { try { const [result] = await fetchRegistry([isUrl(name) ? name : `styles/${style}/${name}.json`]); return registryItemSchema.parse(result); } catch (error) { logger.break(); handleError(error); return null; } } const BASE_COLORS = [ { name: "neutral", label: "Neutral" }, { name: "gray", label: "Gray" }, { name: "zinc", label: "Zinc" }, { name: "stone", label: "Stone" }, { name: "slate", label: "Slate" } ]; async function getRegistryBaseColors() { return BASE_COLORS; } async function getRegistryBaseColor(baseColor) { try { const [result] = await fetchRegistry([`colors/${baseColor}.json`]); return registryBaseColorSchema.parse(result); } catch (error) { handleError(error); } } async function resolveTree(index, names) { const tree = []; for (const name of names) { const entry = index.find((entry$1) => entry$1.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$1, self) => self.findIndex((c) => c.name === component.name) === index$1); } async function fetchTree(style, tree) { try { const paths = tree.map((item) => `styles/${style}/${item.name}.json`); const result = await fetchRegistry(paths); return registryIndexSchema.parse(result); } catch (error) { handleError(error); } } async function getItemTargetPath(config, item, override) { 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], type); } async function fetchRegistry(paths) { try { const results = await Promise.all(paths.map(async (path$1) => { const url = getRegistryUrl(path$1); if (registryCache.has(url)) return registryCache.get(url); const fetchPromise = ofetch(url, { dispatcher: agent, parseResponse: JSON.parse }).catch((error) => { const status = error.status || error.statusCode; if (status === 401) throw new Error(`You are not authorized to access the component at ${highlighter.info(url)}.\nIf this is a remote registry, you may need to authenticate.`); if (status === 404) throw new Error(`The component at ${highlighter.info(url)} was not found.\nIt may not exist at the registry. Please make sure it is a valid component.`); if (status === 403) throw new Error(`You do not have access to the component at ${highlighter.info(url)}.\nIf this is a remote registry, you may need to authenticate or a token.`); const message = error.data?.error || error.message || `HTTP ${status || "Unknown"} Error`; throw new Error(`Failed to fetch from ${highlighter.info(url)}.\n${message}`); }); registryCache.set(url, fetchPromise); return fetchPromise; })); return results; } catch (error) { logger.error("\n"); handleError(error); return []; } } async function registryResolveItemsTree(names, config) { try { const index = await getRegistryIndex(); if (!index) return null; if (names.includes("index")) names.unshift("index"); let registryItems = await resolveRegistryItems(names, config); let result = await fetchRegistry(registryItems); const payload = z.array(registryItemSchema).parse(result); if (!payload) return null; if (names.includes("index")) { if (config.tailwind.baseColor) { const theme = await registryGetTheme(config.tailwind.baseColor, config); if (theme) payload.unshift(theme); } } payload.sort((a, b) => { if (a.type === "registry:theme") return -1; return 1; }); let tailwind = {}; payload.forEach((item) => { tailwind = deepmerge(tailwind, item.tailwind ?? {}); }); let cssVars = {}; payload.forEach((item) => { cssVars = deepmerge(cssVars, item.cssVars ?? {}); }); let css = {}; payload.forEach((item) => { css = deepmerge(css, item.css ?? {}); }); let docs = ""; payload.forEach((item) => { if (item.docs) docs += `${item.docs}\n`; }); return registryResolvedItemsTreeSchema.parse({ dependencies: deepmerge.all(payload.map((item) => item.dependencies ?? [])), devDependencies: deepmerge.all(payload.map((item) => item.devDependencies ?? [])), files: deepmerge.all(payload.map((item) => item.files ?? [])), tailwind, cssVars, css, docs }); } catch (error) { handleError(error); return null; } } async function resolveRegistryDependencies(url, config) { const visited = new Set(); const payload = []; const style = config.resolvedPaths?.cwd ? await getTargetStyleFromConfig(config.resolvedPaths.cwd, config.style) : config.style; async function resolveDependencies(itemUrl) { const url$1 = getRegistryUrl(isUrl(itemUrl) ? itemUrl : `styles/${style}/${itemUrl}.json`); if (visited.has(url$1)) return; visited.add(url$1); try { const [result] = await fetchRegistry([url$1]); const item = registryItemSchema.parse(result); payload.push(url$1); if (item.registryDependencies) for (const dependency of item.registryDependencies) await resolveDependencies(dependency); } catch (error) { console.error(`Error fetching or parsing registry item at ${itemUrl}:`, error); } } await resolveDependencies(url); return Array.from(new Set(payload)); } async function registryGetTheme(name, config) { const [baseColor, tailwindVersion] = await Promise.all([getRegistryBaseColor(name), getProjectTailwindVersionFromConfig(config)]); if (!baseColor) return null; const theme = { name, type: "registry:theme", tailwind: { config: { theme: { extend: { borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" }, colors: {} } } } }, cssVars: { theme: {}, light: { radius: "0.5rem" }, dark: {} } }; if (config.tailwind.cssVariables) { theme.tailwind.config.theme.extend.colors = { ...theme.tailwind.config.theme.extend.colors, ...buildTailwindThemeColorsFromCssVars(baseColor.cssVars.dark ?? {}) }; theme.cssVars = { theme: { ...baseColor.cssVars.theme, ...theme.cssVars.theme }, light: { ...baseColor.cssVars.light, ...theme.cssVars.light }, dark: { ...baseColor.cssVars.dark, ...theme.cssVars.dark } }; if (tailwindVersion === "v4" && baseColor.cssVarsV4) theme.cssVars = { theme: { ...baseColor.cssVarsV4.theme, ...theme.cssVars.theme }, light: { ...theme.cssVars.light, ...baseColor.cssVarsV4.light }, dark: { ...theme.cssVars.dark, ...baseColor.cssVarsV4.dark } }; } return theme; } function clearRegistryCache() { registryCache.clear(); } function getRegistryUrl(path$1) { if (isUrl(path$1)) { const url = new URL(path$1); if (url.pathname.match(/\/chat\/b\//) && !url.pathname.endsWith("/json")) url.pathname = `${url.pathname}/json`; return url.toString(); } return `${REGISTRY_URL}/${path$1}`; } function isUrl(path$1) { try { new URL(path$1); return true; } catch (error) { return false; } } async function resolveRegistryItems(names, config) { const registryDependencies = []; for (const name of names) { const itemRegistryDependencies = await resolveRegistryDependencies(name, config); registryDependencies.push(...itemRegistryDependencies); } return Array.from(new Set(registryDependencies)); } function getRegistryTypeAliasMap() { return new Map([ ["registry:ui", "ui"], ["registry:lib", "lib"], ["registry:hook", "hooks"], ["registry:composable", "composable"], ["registry:block", "components"], ["registry:component", "components"] ]); } function getRegistryParentMap(registryItems) { const map = new Map(); registryItems.forEach((item) => { if (!item.registryDependencies) return; item.registryDependencies.forEach((dependency) => { map.set(dependency, item); }); }); return map; } //#endregion export { BASE_COLORS, DEFAULT_COMPONENTS, DEFAULT_TAILWIND_CONFIG, DEFAULT_TAILWIND_CSS, DEFAULT_UTILS, _createSourceFile, _getQuoteChar, clearRegistryCache, fetchRegistry, fetchTree, findCommonRoot, findPackageRoot, getConfig, getItemTargetPath, getPackageInfo, getProjectConfig, getProjectInfo, getProjectTailwindVersionFromConfig, getRegistryBaseColor, getRegistryBaseColors, getRegistryIcons, getRegistryIndex, getRegistryItem, getRegistryParentMap, getRegistryStyles, getRegistryTypeAliasMap, getTSConfig, getWorkspaceConfig, handleError, highlighter, iconsSchema, isUrl, logger, rawConfigSchema, registryBaseColorSchema, registryGetTheme, registryIndexSchema, registryItemCssSchema, registryItemCssVarsSchema, registryItemFileSchema, registryItemSchema, registryItemTailwindSchema, registryItemTypeSchema, registryResolveItemsTree, registryResolvedItemsTreeSchema, registrySchema, resolveConfigPaths, resolveRegistryItems, resolveTree, spinner, stylesSchema, updateTailwindConfig }; //# sourceMappingURL=registry-ByrmygPr.js.map