shadcn-vue
Version:
Add components to your apps.
1,273 lines (1,260 loc) • 90 kB
JavaScript
#!/usr/bin/env node
import { BASE_COLORS, BUILTIN_REGISTRIES, DEFAULT_COMPONENTS, DEFAULT_TAILWIND_CONFIG, DEFAULT_TAILWIND_CSS, DEFAULT_UTILS, DEPRECATED_COMPONENTS, ICON_LIBRARIES, RegistryNotConfiguredError, _createSourceFile, _getQuoteChar, buildUrlAndHeadersForRegistryItem, clearRegistryContext, configWithDefaults, createConfig, fetchRegistryItems, fetchTree, findCommonRoot, findExistingEnvFile, findPackageRoot, getConfig, getItemTargetPath, getNewEnvKeys, getPackageInfo, getProjectConfig, getProjectInfo, getProjectTailwindVersionFromConfig, getRegistriesConfig, getRegistriesIndex, getRegistry, getRegistryBaseColor, getRegistryBaseColors, getRegistryIcons, getRegistryItems, getRegistryStyles, getShadcnRegistryIndex, getWorkspaceConfig, handleError, highlighter, isUniversalRegistryItem, logger, mergeEnvContent, parseRegistryAndItemFromString, resolveConfigPaths, resolveRegistryItems, resolveRegistryTree, resolveTree, spinner, transform, updateFiles, updateTailwindConfig } from "./registry-CVURNCtV.js";
import { rawConfigSchema, registryItemSchema, registrySchema } from "./schema-PrLX5K_R.js";
import { server } from "./mcp-ebpb1d4Z.js";
import { Command } from "commander";
import path, { isAbsolute, join, normalize, resolve, sep } from "pathe";
import prompts from "prompts";
import z$1, { z } from "zod";
import { existsSync, promises } from "fs";
import deepmerge from "deepmerge";
import fsExtra from "fs-extra";
import { glob } from "tinyglobby";
import consola from "consola";
import fs from "fs/promises";
import { tmpdir } from "os";
import { Project, ScriptKind, SyntaxKind } from "ts-morph";
import { randomBytes } from "crypto";
import postcss from "postcss";
import AtRule from "postcss/lib/at-rule";
import { addDependency, detectPackageManager } from "nypm";
import { diffLines } from "diff";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { x } from "tinyexec";
//#region src/utils/errors.ts
const MISSING_DIR_OR_EMPTY_PROJECT = "1";
const MISSING_CONFIG = "3";
const TAILWIND_NOT_CONFIGURED = "5";
const IMPORT_ALIAS_MISSING = "6";
const UNSUPPORTED_FRAMEWORK = "7";
const BUILD_MISSING_REGISTRY_FILE = "13";
//#endregion
//#region src/preflights/preflight-init.ts
async function preFlightInit(options) {
const errors = {};
if (!fsExtra.existsSync(options.cwd) || !fsExtra.existsSync(path.resolve(options.cwd, "package.json"))) {
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
return {
errors,
projectInfo: null
};
}
const projectSpinner = spinner(`Preflight checks.`, { silent: options.silent }).start();
if (fsExtra.existsSync(path.resolve(options.cwd, "components.json")) && !options.force) {
projectSpinner?.fail();
logger.break();
logger.error(`A ${highlighter.info("components.json")} file already exists at ${highlighter.info(options.cwd)}.\nTo start over, remove the ${highlighter.info("components.json")} file and run ${highlighter.info("init")} again.`);
logger.break();
process.exit(1);
}
projectSpinner?.succeed();
const frameworkSpinner = spinner(`Verifying framework.`, { silent: options.silent }).start();
const projectInfo = await getProjectInfo(options.cwd);
if (!projectInfo || projectInfo?.framework.name === "manual") {
errors[UNSUPPORTED_FRAMEWORK] = true;
frameworkSpinner?.fail();
logger.break();
if (projectInfo?.framework.links.installation) logger.error(`We could not detect a supported framework at ${highlighter.info(options.cwd)}.\nVisit ${highlighter.info(projectInfo?.framework.links.installation)} to manually configure your project.\nOnce configured, you can use the cli to add components.`);
logger.break();
process.exit(1);
}
frameworkSpinner?.succeed(`Verifying framework. Found ${highlighter.info(projectInfo.framework.label)}.`);
let tailwindSpinnerMessage = "Validating Tailwind CSS.";
if (projectInfo.tailwindVersion === "v4") tailwindSpinnerMessage = `Validating Tailwind CSS config. Found ${highlighter.info("v4")}.`;
const tailwindSpinner = spinner(tailwindSpinnerMessage, { silent: options.silent }).start();
if (projectInfo.tailwindVersion === "v3" && (!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile)) {
errors[TAILWIND_NOT_CONFIGURED] = true;
tailwindSpinner?.fail();
} else if (projectInfo.tailwindVersion === "v4" && !projectInfo?.tailwindCssFile) {
errors[TAILWIND_NOT_CONFIGURED] = true;
tailwindSpinner?.fail();
} else if (!projectInfo.tailwindVersion) {
errors[TAILWIND_NOT_CONFIGURED] = true;
tailwindSpinner?.fail();
} else tailwindSpinner?.succeed();
const tsConfigSpinner = spinner(`Validating import alias.`, { silent: options.silent }).start();
if (!projectInfo?.aliasPrefix) {
errors[IMPORT_ALIAS_MISSING] = true;
tsConfigSpinner?.fail();
} else tsConfigSpinner?.succeed();
if (Object.keys(errors).length > 0) {
if (errors[TAILWIND_NOT_CONFIGURED]) {
logger.break();
logger.error(`No Tailwind CSS configuration found at ${highlighter.info(options.cwd)}.`);
logger.error(`It is likely you do not have Tailwind CSS installed or have an invalid configuration.`);
logger.error(`Install Tailwind CSS then try again.`);
if (projectInfo?.framework.links.tailwind) logger.error(`Visit ${highlighter.info(projectInfo?.framework.links.tailwind)} to get started.`);
}
if (errors[IMPORT_ALIAS_MISSING]) {
logger.break();
logger.error(`No import alias found in your tsconfig.json file.`);
if (projectInfo?.framework.links.installation) logger.error(`Visit ${highlighter.info(projectInfo?.framework.links.installation)} to learn how to set an import alias.`);
}
logger.break();
process.exit(1);
}
return {
errors,
projectInfo
};
}
//#endregion
//#region src/utils/is-safe-target.ts
function isSafeTarget(targetPath, cwd) {
if (targetPath.includes("\0")) return false;
let decodedPath;
try {
decodedPath = targetPath;
let prevPath = "";
while (decodedPath !== prevPath && decodedPath.includes("%")) {
prevPath = decodedPath;
decodedPath = decodeURIComponent(decodedPath);
}
} catch {
return false;
}
const normalizedTarget = normalize(decodedPath);
const normalizedRoot = normalize(cwd);
const hasPathTraversal = (path$1) => {
return path$1.replace(/\[\.\.\..*?\]/g, "").includes("..");
};
if (hasPathTraversal(normalizedTarget) || hasPathTraversal(decodedPath) || hasPathTraversal(targetPath)) return false;
const cleanPath = (path$1) => path$1.replace(/\[\.\.\..*?\]/g, "");
const cleanTarget = cleanPath(targetPath);
const cleanDecoded = cleanPath(decodedPath);
if ([
/\.\.[/\\]/,
/[/\\]\.\./,
/\.\./,
/\.\.%/,
/\0/,
/[\x01-\x1F]/
].some((pattern) => pattern.test(cleanTarget) || pattern.test(cleanDecoded))) return false;
if ((targetPath.includes("~") || decodedPath.includes("~")) && (targetPath.includes("../") || decodedPath.includes("../"))) return false;
if (/^[a-z]:[/\\]/i.test(decodedPath)) {
if (process.platform === "win32") return decodedPath.toLowerCase().startsWith(cwd.toLowerCase());
return false;
}
const absoluteTarget = isAbsolute(normalizedTarget) ? normalizedTarget : resolve(normalizedRoot, normalizedTarget);
const safeRoot = normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep;
return absoluteTarget === normalizedRoot || absoluteTarget.startsWith(safeRoot);
}
//#endregion
//#region src/utils/updaters/update-css.ts
async function updateCss(css, config, options) {
if (!config.resolvedPaths.tailwindCss || !css || Object.keys(css).length === 0) return;
options = {
silent: false,
...options
};
const cssFilepath = config.resolvedPaths.tailwindCss;
const cssFilepathRelative = path.relative(config.resolvedPaths.cwd, cssFilepath);
const cssSpinner = spinner(`Updating ${highlighter.info(cssFilepathRelative)}`, { silent: options.silent }).start();
let output = await transformCss(await promises.readFile(cssFilepath, "utf8"), css);
await promises.writeFile(cssFilepath, output, "utf8");
cssSpinner.succeed();
}
async function transformCss(input, css) {
const result = await postcss([updateCssPlugin(css)]).process(input, { from: void 0 });
let output = result.css;
const root = result.root;
if (root.nodes && root.nodes.length > 0) {
const lastNode = root.nodes[root.nodes.length - 1];
if (lastNode.type === "atrule" && !lastNode.nodes && !output.trimEnd().endsWith(";")) output = `${output.trimEnd()};`;
}
output = output.replace(/\/\* ---break--- \*\//g, "");
output = output.replace(/(\n\s*\n)+/g, "\n\n");
output = output.trimEnd();
return output;
}
function updateCssPlugin(css) {
return {
postcssPlugin: "update-css",
Once(root) {
for (const [selector, properties] of Object.entries(css)) if (selector.startsWith("@")) {
const atRuleMatch = selector.match(/@([a-z-]+)\s*(.*)/i);
if (!atRuleMatch) continue;
const [, name$1, params] = atRuleMatch;
if (name$1 === "import") {
if (!root.nodes?.find((node) => node.type === "atrule" && node.name === "import" && node.params === params)) {
const importRule = postcss.atRule({
name: "import",
params,
raws: { semicolon: true }
});
const importNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "import");
if (importNodes && importNodes.length > 0) {
const lastImport = importNodes[importNodes.length - 1];
importRule.raws.before = "\n";
root.insertAfter(lastImport, importRule);
} else {
if (!root.nodes || root.nodes.length === 0) importRule.raws.before = "";
else importRule.raws.before = "";
root.prepend(importRule);
}
}
} else if (name$1 === "plugin") {
let quotedParams = params;
if (params && !params.startsWith("\"") && !params.startsWith("'")) quotedParams = `"${params}"`;
const normalizeParams = (p) => {
if (p.startsWith("\"") && p.endsWith("\"")) return p.slice(1, -1);
if (p.startsWith("'") && p.endsWith("'")) return p.slice(1, -1);
return p;
};
if (!root.nodes?.find((node) => {
if (node.type !== "atrule" || node.name !== "plugin") return false;
return normalizeParams(node.params) === normalizeParams(params);
})) {
const pluginRule = postcss.atRule({
name: "plugin",
params: quotedParams,
raws: {
semicolon: true,
before: "\n"
}
});
const importNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "import");
const pluginNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "plugin");
if (pluginNodes && pluginNodes.length > 0) {
const lastPlugin = pluginNodes[pluginNodes.length - 1];
root.insertAfter(lastPlugin, pluginRule);
} else if (importNodes && importNodes.length > 0) {
const lastImport = importNodes[importNodes.length - 1];
root.insertAfter(lastImport, pluginRule);
root.insertBefore(pluginRule, postcss.comment({ text: "---break---" }));
root.insertAfter(pluginRule, postcss.comment({ text: "---break---" }));
} else {
root.prepend(pluginRule);
root.insertBefore(pluginRule, postcss.comment({ text: "---break---" }));
root.insertAfter(pluginRule, postcss.comment({ text: "---break---" }));
}
}
} else if (typeof properties === "object" && Object.keys(properties).length === 0) {
if (!root.nodes?.find((node) => node.type === "atrule" && node.name === name$1 && node.params === params)) {
const newAtRule = postcss.atRule({
name: name$1,
params,
raws: { semicolon: true }
});
root.append(newAtRule);
root.insertBefore(newAtRule, postcss.comment({ text: "---break---" }));
}
} else if (name$1 === "keyframes") {
let themeInline = root.nodes?.find((node) => node.type === "atrule" && node.name === "theme" && node.params === "inline");
if (!themeInline) {
themeInline = postcss.atRule({
name: "theme",
params: "inline",
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(themeInline);
root.insertBefore(themeInline, postcss.comment({ text: "---break---" }));
}
const keyframesRule = postcss.atRule({
name: "keyframes",
params,
raws: {
semicolon: true,
between: " ",
before: "\n "
}
});
themeInline.append(keyframesRule);
if (typeof properties === "object") for (const [step, stepProps] of Object.entries(properties)) processRule(keyframesRule, step, stepProps);
} else if (name$1 === "utility") {
const utilityAtRule = root.nodes?.find((node) => node.type === "atrule" && node.name === name$1 && node.params === params);
if (!utilityAtRule) {
const atRule = postcss.atRule({
name: name$1,
params,
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(atRule);
root.insertBefore(atRule, postcss.comment({ text: "---break---" }));
if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) if (typeof value === "string") {
const decl = postcss.decl({
prop,
value,
raws: {
semicolon: true,
before: "\n "
}
});
atRule.append(decl);
} else if (typeof value === "object") processRule(atRule, prop, value);
}
} else if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) if (typeof value === "string") {
const existingDecl = utilityAtRule.nodes?.find((node) => node.type === "decl" && node.prop === prop);
const decl = postcss.decl({
prop,
value,
raws: {
semicolon: true,
before: "\n "
}
});
existingDecl ? existingDecl.replaceWith(decl) : utilityAtRule.append(decl);
} else if (typeof value === "object") processRule(utilityAtRule, prop, value);
}
} else if (name$1 === "property") processRule(root, selector, properties);
else processAtRule(root, name$1, params, properties);
} else processRule(root, selector, properties);
}
};
}
function processAtRule(root, name$1, params, properties) {
let atRule = root.nodes?.find((node) => node.type === "atrule" && node.name === name$1 && node.params === params);
if (!atRule) {
atRule = postcss.atRule({
name: name$1,
params,
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(atRule);
root.insertBefore(atRule, postcss.comment({ text: "---break---" }));
}
if (typeof properties === "object") for (const [childSelector, childProps] of Object.entries(properties)) if (childSelector.startsWith("@")) {
const nestedMatch = childSelector.match(/@([a-z-]+)\s*(.*)/i);
if (nestedMatch) {
const [, nestedName, nestedParams] = nestedMatch;
processAtRule(atRule, nestedName, nestedParams, childProps);
}
} else processRule(atRule, childSelector, childProps);
else if (typeof properties === "string") try {
const tempRule = postcss.parse(`.temp{${properties}}`).first;
if (tempRule && tempRule.nodes) {
const rule = postcss.rule({
selector: "temp",
raws: {
semicolon: true,
between: " ",
before: "\n "
}
});
tempRule.nodes.forEach((node) => {
if (node.type === "decl") {
const clone = node.clone();
clone.raws.before = "\n ";
rule.append(clone);
}
});
if (rule.nodes?.length) atRule.append(rule);
}
} catch (error) {
console.error("Error parsing at-rule content:", properties, error);
throw error;
}
}
function processRule(parent, selector, properties) {
let rule = parent.nodes?.find((node) => node.type === "rule" && node.selector === selector);
if (!rule) {
rule = postcss.rule({
selector,
raws: {
semicolon: true,
between: " ",
before: "\n "
}
});
parent.append(rule);
}
if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) if (prop.startsWith("@") && typeof value === "object" && value !== null && Object.keys(value).length === 0) {
const atRuleMatch = prop.match(/@([a-z-]+)\s*(.*)/i);
if (atRuleMatch) {
const [, atRuleName, atRuleParams] = atRuleMatch;
const atRule = postcss.atRule({
name: atRuleName,
params: atRuleParams,
raws: {
semicolon: true,
before: "\n "
}
});
rule.append(atRule);
}
} else if (typeof value === "string") {
const decl = postcss.decl({
prop,
value,
raws: {
semicolon: true,
before: "\n "
}
});
const existingDecl = rule.nodes?.find((node) => node.type === "decl" && node.prop === prop);
existingDecl ? existingDecl.replaceWith(decl) : rule.append(decl);
} else if (typeof value === "object") processRule(parent, prop.startsWith("&") ? selector.replace(/^([^:]+)/, `$1${prop.substring(1)}`) : prop, value);
} else if (typeof properties === "string") try {
const tempRule = postcss.parse(`.temp{${properties}}`).first;
if (tempRule && tempRule.nodes) tempRule.nodes.forEach((node) => {
if (node.type === "decl") {
const clone = node.clone();
clone.raws.before = "\n ";
rule?.append(clone);
}
});
} catch (error) {
console.error("Error parsing rule content:", selector, properties, error);
throw error;
}
}
//#endregion
//#region src/utils/updaters/update-css-vars.ts
async function updateCssVars(cssVars, config, options) {
if (!config.resolvedPaths.tailwindCss || !Object.keys(cssVars ?? {}).length) return;
options = {
cleanupDefaultNextStyles: false,
silent: false,
tailwindVersion: "v3",
overwriteCssVars: false,
initIndex: true,
...options
};
const cssFilepath = config.resolvedPaths.tailwindCss;
const cssFilepathRelative = path.relative(config.resolvedPaths.cwd, cssFilepath);
const cssVarsSpinner = spinner(`Updating CSS variables in ${highlighter.info(cssFilepathRelative)}`, { silent: options.silent }).start();
const output = await transformCssVars(await promises.readFile(cssFilepath, "utf8"), cssVars ?? {}, config, {
cleanupDefaultNextStyles: options.cleanupDefaultNextStyles,
tailwindVersion: options.tailwindVersion,
tailwindConfig: options.tailwindConfig,
overwriteCssVars: options.overwriteCssVars,
initIndex: options.initIndex
});
await promises.writeFile(cssFilepath, output, "utf8");
cssVarsSpinner.succeed();
}
async function transformCssVars(input, cssVars, config, options = {
cleanupDefaultNextStyles: false,
tailwindVersion: "v3",
tailwindConfig: void 0,
overwriteCssVars: false,
initIndex: true
}) {
options = {
cleanupDefaultNextStyles: false,
tailwindVersion: "v3",
tailwindConfig: void 0,
overwriteCssVars: false,
initIndex: true,
...options
};
let plugins = [updateCssVarsPlugin(cssVars)];
if (options.cleanupDefaultNextStyles) plugins.push(cleanupDefaultNextStylesPlugin());
if (options.tailwindVersion === "v4") {
plugins = [];
if (config.resolvedPaths?.cwd) {
const packageInfo = getPackageInfo(config.resolvedPaths.cwd);
if (!packageInfo?.dependencies?.["tailwindcss-animate"] && !packageInfo?.devDependencies?.["tailwindcss-animate"] && options.initIndex) plugins.push(addCustomImport({ params: "tw-animate-css" }));
}
plugins.push(addCustomVariant({ params: "dark (&:is(.dark *))" }));
if (options.cleanupDefaultNextStyles) plugins.push(cleanupDefaultNextStylesPlugin());
plugins.push(updateCssVarsPluginV4(cssVars, { overwriteCssVars: options.overwriteCssVars }));
plugins.push(updateThemePlugin(cssVars));
if (options.tailwindConfig) {
plugins.push(updateTailwindConfigPlugin(options.tailwindConfig));
plugins.push(updateTailwindConfigAnimationPlugin(options.tailwindConfig));
plugins.push(updateTailwindConfigKeyframesPlugin(options.tailwindConfig));
}
}
if (config.tailwind.cssVariables && options.initIndex) plugins.push(updateBaseLayerPlugin({ tailwindVersion: options.tailwindVersion }));
let output = (await postcss(plugins).process(input, { from: void 0 })).css;
output = output.replace(/\/\* ---break--- \*\//g, "");
if (options.tailwindVersion === "v4") output = output.replace(/(\n\s*\n)+/g, "\n\n");
return output;
}
function updateBaseLayerPlugin({ tailwindVersion }) {
return {
postcssPlugin: "update-base-layer",
Once(root) {
const requiredRules = [{
selector: "*",
apply: tailwindVersion === "v4" ? "border-border outline-ring/50" : "border-border"
}, {
selector: "body",
apply: "bg-background text-foreground"
}];
let baseLayer = root.nodes.find((node) => node.type === "atrule" && node.name === "layer" && node.params === "base" && requiredRules.every(({ selector, apply }) => node.nodes?.some((rule) => rule.type === "rule" && rule.selector === selector && rule.nodes.some((applyRule) => applyRule.type === "atrule" && applyRule.name === "apply" && applyRule.params === apply))));
if (!baseLayer) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(baseLayer);
root.insertBefore(baseLayer, postcss.comment({ text: "---break---" }));
}
requiredRules.forEach(({ selector, apply }) => {
if (!baseLayer?.nodes?.find((node) => node.type === "rule" && node.selector === selector)) baseLayer?.append(postcss.rule({
selector,
nodes: [postcss.atRule({
name: "apply",
params: apply,
raws: {
semicolon: true,
before: "\n "
}
})],
raws: {
semicolon: true,
between: " ",
before: "\n "
}
}));
});
}
};
}
function updateCssVarsPlugin(cssVars) {
return {
postcssPlugin: "update-css-vars",
Once(root) {
let baseLayer = root.nodes.find((node) => node.type === "atrule" && node.name === "layer" && node.params === "base");
if (!(baseLayer instanceof AtRule)) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
nodes: [],
raws: {
semicolon: true,
before: "\n",
between: " "
}
});
root.append(baseLayer);
root.insertBefore(baseLayer, postcss.comment({ text: "---break---" }));
}
if (baseLayer !== void 0) Object.entries(cssVars).forEach(([key, vars]) => {
const selector = key === "light" ? ":root" : `.${key}`;
addOrUpdateVars(baseLayer, selector, vars);
});
}
};
}
function removeConflictVars(root) {
const rootRule = root.nodes.find((node) => node.type === "rule" && node.selector === ":root");
if (rootRule) {
const propsToRemove = ["--background", "--foreground"];
rootRule.nodes.filter((node) => node.type === "decl" && propsToRemove.includes(node.prop)).forEach((node) => node.remove());
if (rootRule.nodes.length === 0) rootRule.remove();
}
}
function cleanupDefaultNextStylesPlugin() {
return {
postcssPlugin: "cleanup-default-next-styles",
Once(root) {
const bodyRule = root.nodes.find((node) => node.type === "rule" && node.selector === "body");
if (bodyRule) {
bodyRule.nodes.find((node) => node.type === "decl" && node.prop === "color" && ["rgb(var(--foreground-rgb))", "var(--foreground)"].includes(node.value))?.remove();
bodyRule.nodes.find((node) => {
return node.type === "decl" && node.prop === "background" && (node.value.startsWith("linear-gradient") || node.value === "var(--background)");
})?.remove();
bodyRule.nodes.find((node) => node.type === "decl" && node.prop === "font-family" && node.value === "Arial, Helvetica, sans-serif")?.remove();
if (bodyRule.nodes.length === 0) bodyRule.remove();
}
removeConflictVars(root);
const darkRootRule = root.nodes.find((node) => node.type === "atrule" && node.params === "(prefers-color-scheme: dark)");
if (darkRootRule) {
removeConflictVars(darkRootRule);
if (darkRootRule.nodes.length === 0) darkRootRule.remove();
}
}
};
}
function addOrUpdateVars(baseLayer, selector, vars) {
let ruleNode = baseLayer.nodes?.find((node) => node.type === "rule" && node.selector === selector);
if (!ruleNode) {
if (Object.keys(vars).length > 0) {
ruleNode = postcss.rule({
selector,
raws: {
between: " ",
before: "\n "
}
});
baseLayer.append(ruleNode);
}
}
Object.entries(vars).forEach(([key, value]) => {
const prop = `--${key.replace(/^--/, "")}`;
const newDecl = postcss.decl({
prop,
value,
raws: { semicolon: true }
});
const existingDecl = ruleNode?.nodes.find((node) => node.type === "decl" && node.prop === prop);
existingDecl ? existingDecl.replaceWith(newDecl) : ruleNode?.append(newDecl);
});
}
function updateCssVarsPluginV4(cssVars, options) {
return {
postcssPlugin: "update-css-vars-v4",
Once(root) {
Object.entries(cssVars).forEach(([key, vars]) => {
let selector = key === "light" ? ":root" : `.${key}`;
if (key === "theme") {
selector = "@theme";
const themeNode = upsertThemeNode(root);
Object.entries(vars).forEach(([key$1, value]) => {
const prop = `--${key$1.replace(/^--/, "")}`;
const newDecl = postcss.decl({
prop,
value,
raws: { semicolon: true }
});
const existingDecl = themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === prop);
if (options.overwriteCssVars) if (existingDecl) existingDecl.replaceWith(newDecl);
else themeNode?.append(newDecl);
else if (!existingDecl) themeNode?.append(newDecl);
});
return;
}
let ruleNode = root.nodes?.find((node) => node.type === "rule" && node.selector === selector);
if (!ruleNode && Object.keys(vars).length > 0) {
ruleNode = postcss.rule({
selector,
nodes: [],
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(ruleNode);
root.insertBefore(ruleNode, postcss.comment({ text: "---break---" }));
}
Object.entries(vars).forEach(([key$1, value]) => {
let prop = `--${key$1.replace(/^--/, "")}`;
if (prop === "--sidebar-background") prop = "--sidebar";
if (isLocalHSLValue(value)) value = `hsl(${value})`;
const newDecl = postcss.decl({
prop,
value,
raws: { semicolon: true }
});
const existingDecl = ruleNode?.nodes.find((node) => node.type === "decl" && node.prop === prop);
if (options.overwriteCssVars) if (existingDecl) existingDecl.replaceWith(newDecl);
else ruleNode?.append(newDecl);
else if (!existingDecl) ruleNode?.append(newDecl);
});
});
}
};
}
function updateThemePlugin(cssVars) {
return {
postcssPlugin: "update-theme",
Once(root) {
const variables = Array.from(new Set(Object.keys(cssVars).flatMap((key) => Object.keys(cssVars[key] || {}))));
if (!variables.length) return;
const themeNode = upsertThemeNode(root);
const themeVarNodes = themeNode.nodes?.filter((node) => node.type === "decl" && node.prop.startsWith("--"));
for (const variable of variables) {
const value = Object.values(cssVars).find((vars) => vars[variable])?.[variable];
if (!value) continue;
if (variable === "radius") {
for (const [key, value$1] of Object.entries({
sm: "calc(var(--radius) - 4px)",
md: "calc(var(--radius) - 2px)",
lg: "var(--radius)",
xl: "calc(var(--radius) + 4px)"
})) {
const cssVarNode$1 = postcss.decl({
prop: `--radius-${key}`,
value: value$1,
raws: { semicolon: true }
});
if (themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === cssVarNode$1.prop)) continue;
themeNode?.append(cssVarNode$1);
}
continue;
}
let prop = isLocalHSLValue(value) || isColorValue(value) ? `--color-${variable.replace(/^--/, "")}` : `--${variable.replace(/^--/, "")}`;
if (prop === "--color-sidebar-background") prop = "--color-sidebar";
let propValue = `var(--${variable})`;
if (prop === "--color-sidebar") propValue = "var(--sidebar)";
const cssVarNode = postcss.decl({
prop,
value: propValue,
raws: { semicolon: true }
});
if (!themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === cssVarNode.prop)) if (themeVarNodes?.length) themeNode?.insertAfter(themeVarNodes[themeVarNodes.length - 1], cssVarNode);
else themeNode?.append(cssVarNode);
}
}
};
}
function upsertThemeNode(root) {
let themeNode = root.nodes.find((node) => node.type === "atrule" && node.name === "theme" && node.params === "inline");
if (!themeNode) {
themeNode = postcss.atRule({
name: "theme",
params: "inline",
nodes: [],
raws: {
semicolon: true,
between: " ",
before: "\n"
}
});
root.append(themeNode);
root.insertBefore(themeNode, postcss.comment({ text: "---break---" }));
}
return themeNode;
}
function addCustomVariant({ params }) {
return {
postcssPlugin: "add-custom-variant",
Once(root) {
if (!root.nodes.find((node) => node.type === "atrule" && node.name === "custom-variant")) {
const importNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "import");
const variantNode = postcss.atRule({
name: "custom-variant",
params,
raws: {
semicolon: true,
before: "\n"
}
});
if (importNodes.length > 0) {
const lastImport = importNodes[importNodes.length - 1];
root.insertAfter(lastImport, variantNode);
} else root.insertAfter(root.nodes[0], variantNode);
root.insertBefore(variantNode, postcss.comment({ text: "---break---" }));
}
}
};
}
function addCustomImport({ params }) {
return {
postcssPlugin: "add-custom-import",
Once(root) {
const importNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "import");
const customVariantNode = root.nodes.find((node) => node.type === "atrule" && node.name === "custom-variant");
if (!importNodes.some((node) => node.params.replace(/["']/g, "") === params)) {
const importNode = postcss.atRule({
name: "import",
params: `"${params}"`,
raws: {
semicolon: true,
before: "\n"
}
});
if (importNodes.length > 0) {
const lastImport = importNodes[importNodes.length - 1];
root.insertAfter(lastImport, importNode);
} else if (customVariantNode) {
root.insertBefore(customVariantNode, importNode);
root.insertBefore(customVariantNode, postcss.comment({ text: "---break---" }));
} else {
root.prepend(importNode);
root.insertAfter(importNode, postcss.comment({ text: "---break---" }));
}
}
}
};
}
function updateTailwindConfigPlugin(tailwindConfig) {
return {
postcssPlugin: "update-tailwind-config",
Once(root) {
if (!tailwindConfig?.plugins) return;
const quote = getQuoteType(root) === "single" ? "'" : "\"";
const pluginNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "plugin");
const lastPluginNode = pluginNodes[pluginNodes.length - 1] || root.nodes[0];
for (const plugin of tailwindConfig.plugins) {
const pluginName = plugin.replace(/^require\(["']|["']\)$/g, "");
if (pluginNodes.some((node) => {
return node.params.replace(/["']/g, "") === pluginName;
})) continue;
const pluginNode = postcss.atRule({
name: "plugin",
params: `${quote}${pluginName}${quote}`,
raws: {
semicolon: true,
before: "\n"
}
});
root.insertAfter(lastPluginNode, pluginNode);
root.insertBefore(pluginNode, postcss.comment({ text: "---break---" }));
}
}
};
}
function updateTailwindConfigKeyframesPlugin(tailwindConfig) {
return {
postcssPlugin: "update-tailwind-config-keyframes",
Once(root) {
if (!tailwindConfig?.theme?.extend?.keyframes) return;
const themeNode = upsertThemeNode(root);
const existingKeyFrameNodes = themeNode.nodes?.filter((node) => node.type === "atrule" && node.name === "keyframes");
const keyframeValueSchema = z.record(z.string(), z.record(z.string(), z.string()));
for (const [keyframeName, keyframeValue] of Object.entries(tailwindConfig.theme.extend.keyframes)) {
if (typeof keyframeName !== "string") continue;
const parsedKeyframeValue = keyframeValueSchema.safeParse(keyframeValue);
if (!parsedKeyframeValue.success) continue;
if (existingKeyFrameNodes?.find((node) => node.type === "atrule" && node.name === "keyframes" && node.params === keyframeName)) continue;
const keyframeNode = postcss.atRule({
name: "keyframes",
params: keyframeName,
nodes: [],
raws: {
semicolon: true,
between: " ",
before: "\n "
}
});
for (const [key, values] of Object.entries(parsedKeyframeValue.data)) {
const rule = postcss.rule({
selector: key,
nodes: Object.entries(values).map(([key$1, value]) => postcss.decl({
prop: key$1,
value,
raws: {
semicolon: true,
before: "\n ",
between: ": "
}
})),
raws: {
semicolon: true,
between: " ",
before: "\n "
}
});
keyframeNode.append(rule);
}
themeNode.append(keyframeNode);
themeNode.insertBefore(keyframeNode, postcss.comment({ text: "---break---" }));
}
}
};
}
function updateTailwindConfigAnimationPlugin(tailwindConfig) {
return {
postcssPlugin: "update-tailwind-config-animation",
Once(root) {
if (!tailwindConfig?.theme?.extend?.animation) return;
const themeNode = upsertThemeNode(root);
const existingAnimationNodes = themeNode.nodes?.filter((node) => node.type === "decl" && node.prop.startsWith("--animate-"));
const parsedAnimationValue = z.record(z.string(), z.string()).safeParse(tailwindConfig.theme.extend.animation);
if (!parsedAnimationValue.success) return;
for (const [key, value] of Object.entries(parsedAnimationValue.data)) {
const prop = `--animate-${key}`;
if (existingAnimationNodes?.find((node) => node.prop === prop)) continue;
const animationNode = postcss.decl({
prop,
value,
raws: {
semicolon: true,
between: ": ",
before: "\n "
}
});
themeNode.append(animationNode);
}
}
};
}
function getQuoteType(root) {
if (root.nodes[0].toString().includes("'")) return "single";
return "double";
}
function isLocalHSLValue(value) {
if (value.startsWith("hsl") || value.startsWith("rgb") || value.startsWith("#") || value.startsWith("oklch")) return false;
const chunks = value.split(" ");
return chunks.length === 3 && chunks.slice(1, 3).every((chunk) => chunk.includes("%"));
}
function isColorValue(value) {
return value.startsWith("hsl") || value.startsWith("rgb") || value.startsWith("#") || value.startsWith("oklch") || value.startsWith("var(--color-");
}
//#endregion
//#region src/utils/updaters/update-dependencies.ts
async function updateDependencies(dependencies$1, devDependencies$1, config, options) {
dependencies$1 = Array.from(new Set(dependencies$1));
devDependencies$1 = Array.from(new Set(devDependencies$1));
if (!dependencies$1?.length && !devDependencies$1?.length) return;
options = {
silent: false,
...options
};
const dependenciesSpinner = spinner(`Installing dependencies.`, { silent: options.silent })?.start();
dependenciesSpinner?.start();
if (dependencies$1?.length) await addDependency(dependencies$1, {
cwd: config.resolvedPaths.cwd,
silent: true,
dev: false
});
if (devDependencies$1?.length) await addDependency(devDependencies$1, {
cwd: config.resolvedPaths.cwd,
silent: true,
dev: true
});
dependenciesSpinner?.succeed();
}
//#endregion
//#region src/utils/updaters/update-env-vars.ts
async function updateEnvVars(envVars, config, options) {
if (!envVars || Object.keys(envVars).length === 0) return {
envVarsAdded: [],
envFileUpdated: null,
envFileCreated: null
};
options = {
silent: false,
...options
};
const envSpinner = spinner(`Adding environment variables.`, { silent: options.silent })?.start();
const projectRoot = config.resolvedPaths.cwd;
let envFilePath = path.join(projectRoot, ".env.local");
const existingEnvFile = findExistingEnvFile(projectRoot);
if (existingEnvFile) envFilePath = existingEnvFile;
const envFileExists = existsSync(envFilePath);
const envFileName = path.basename(envFilePath);
const newEnvContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
let envVarsAdded = [];
let envFileUpdated = null;
let envFileCreated = null;
if (envFileExists) {
const existingContent = await promises.readFile(envFilePath, "utf-8");
const mergedContent = mergeEnvContent(existingContent, newEnvContent);
envVarsAdded = getNewEnvKeys(existingContent, newEnvContent);
if (envVarsAdded.length > 0) {
await promises.writeFile(envFilePath, mergedContent, "utf-8");
envFileUpdated = path.relative(projectRoot, envFilePath);
envSpinner?.succeed(`Added the following variables to ${highlighter.info(envFileName)}:`);
if (!options.silent) for (const key of envVarsAdded) logger.log(` ${highlighter.success("+")} ${key}`);
} else envSpinner?.stop();
} else {
await promises.writeFile(envFilePath, `${newEnvContent}\n`, "utf-8");
envFileCreated = path.relative(projectRoot, envFilePath);
envVarsAdded = Object.keys(envVars);
envSpinner?.succeed(`Added the following variables to ${highlighter.info(envFileName)}:`);
if (!options.silent) for (const key of envVarsAdded) logger.log(` ${highlighter.success("+")} ${key}`);
}
if (!options.silent && envVarsAdded.length > 0) logger.break();
return {
envVarsAdded,
envFileUpdated,
envFileCreated
};
}
//#endregion
//#region src/utils/add-components.ts
async function addComponents(components, config, options) {
options = {
overwrite: false,
silent: false,
isNewProject: false,
baseStyle: true,
...options
};
const workspaceConfig = await getWorkspaceConfig(config);
if (workspaceConfig && workspaceConfig.ui && workspaceConfig.ui.resolvedPaths.cwd !== config.resolvedPaths.cwd) return await addWorkspaceComponents(components, config, workspaceConfig, {
...options,
isRemote: components?.length === 1 && !!components[0].match(/\/chat\/b\//)
});
return await addProjectComponents(components, config, options);
}
async function addProjectComponents(components, config, options) {
if (!options.baseStyle && !components.length) return;
const registrySpinner = spinner(`Checking registry.`, { silent: options.silent })?.start();
const tree = await resolveRegistryTree(components, configWithDefaults(config));
if (!tree) {
registrySpinner?.fail();
return handleError(/* @__PURE__ */ new Error("Failed to fetch components from registry."));
}
try {
validateFilesTarget(tree.files ?? [], config.resolvedPaths.cwd);
} catch (error) {
registrySpinner?.fail();
return handleError(error);
}
registrySpinner?.succeed();
const tailwindVersion = await getProjectTailwindVersionFromConfig(config);
await updateTailwindConfig(tree.tailwind?.config, config, {
silent: options.silent,
tailwindVersion
});
const overwriteCssVars = await shouldOverwriteCssVars(components, config);
await updateCssVars(tree.cssVars, config, {
cleanupDefaultNextStyles: options.isNewProject,
silent: options.silent,
tailwindVersion,
tailwindConfig: tree.tailwind?.config,
overwriteCssVars,
initIndex: options.baseStyle
});
await updateCss(tree.css, config, { silent: options.silent });
await updateEnvVars(tree.envVars, config, { silent: options.silent });
await updateDependencies(tree.dependencies, tree.devDependencies, config, { silent: options.silent });
await updateFiles(tree.files, config, {
overwrite: options.overwrite,
silent: options.silent,
path: options.path
});
if (tree.docs) logger.info(tree.docs);
}
async function addWorkspaceComponents(components, config, workspaceConfig, options) {
if (!options.baseStyle && !components.length) return;
const registrySpinner = spinner(`Checking registry.`, { silent: options.silent })?.start();
const tree = await resolveRegistryTree(components, configWithDefaults(config));
if (!tree) {
registrySpinner?.fail();
return handleError(/* @__PURE__ */ new Error("Failed to fetch components from registry."));
}
try {
validateFilesTarget(tree.files ?? [], config.resolvedPaths.cwd);
} catch (error) {
registrySpinner?.fail();
return handleError(error);
}
registrySpinner?.succeed();
const filesCreated = [];
const filesUpdated = [];
const filesSkipped = [];
const rootSpinner = spinner(`Installing components.`)?.start();
const mainTargetConfig = workspaceConfig.ui;
const tailwindVersion = await getProjectTailwindVersionFromConfig(mainTargetConfig);
const workspaceRoot = findCommonRoot(config.resolvedPaths.cwd, mainTargetConfig.resolvedPaths.ui);
if (tree.tailwind?.config) {
await updateTailwindConfig(tree.tailwind?.config, mainTargetConfig, {
silent: true,
tailwindVersion
});
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindConfig));
}
if (tree.cssVars) {
const overwriteCssVars = await shouldOverwriteCssVars(components, config);
await updateCssVars(tree.cssVars, mainTargetConfig, {
silent: true,
tailwindVersion,
tailwindConfig: tree.tailwind?.config,
overwriteCssVars
});
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindCss));
}
if (tree.css) {
await updateCss(tree.css, mainTargetConfig, { silent: true });
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindCss));
}
if (tree.envVars) await updateEnvVars(tree.envVars, mainTargetConfig, { silent: true });
await updateDependencies(tree.dependencies, tree.devDependencies, mainTargetConfig, { silent: true });
const filesByType = /* @__PURE__ */ new Map();
for (const file of tree.files ?? []) {
const type$1 = file.type || "registry:ui";
if (!filesByType.has(type$1)) filesByType.set(type$1, []);
filesByType.get(type$1).push(file);
}
for (const type$1 of Array.from(filesByType.keys())) {
const typeFiles = filesByType.get(type$1);
let targetConfig = type$1 === "registry:ui" ? workspaceConfig.ui : config;
const typeWorkspaceRoot = findCommonRoot(config.resolvedPaths.cwd, targetConfig.resolvedPaths.ui || targetConfig.resolvedPaths.cwd);
const packageRoot = await findPackageRoot(typeWorkspaceRoot, targetConfig.resolvedPaths.cwd) ?? targetConfig.resolvedPaths.cwd;
const files$1 = await updateFiles(typeFiles, targetConfig, {
overwrite: options.overwrite,
silent: true,
rootSpinner,
isRemote: options.isRemote,
isWorkspace: true,
path: options.path
});
filesCreated.push(...files$1.filesCreated.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
filesUpdated.push(...files$1.filesUpdated.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
filesSkipped.push(...files$1.filesSkipped.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
}
rootSpinner?.succeed();
filesCreated.sort();
filesUpdated.sort();
filesSkipped.sort();
if (!(filesCreated.length || filesUpdated.length) && !filesSkipped.length) spinner(`No files updated.`, { silent: options.silent })?.info();
if (filesCreated.length) {
spinner(`Created ${filesCreated.length} ${filesCreated.length === 1 ? "file" : "files"}:`, { silent: options.silent })?.succeed();
for (const file of filesCreated) logger.log(` - ${file}`);
}
if (filesUpdated.length) {
spinner(`Updated ${filesUpdated.length} ${filesUpdated.length === 1 ? "file" : "files"}:`, { silent: options.silent })?.info();
for (const file of filesUpdated) logger.log(` - ${file}`);
}
if (filesSkipped.length) {
spinner(`Skipped ${filesSkipped.length} ${filesUpdated.length === 1 ? "file" : "files"}: (use --overwrite to overwrite)`, { silent: options.silent })?.info();
for (const file of filesSkipped) logger.log(` - ${file}`);
}
if (tree.docs) logger.info(tree.docs);
}
async function shouldOverwriteCssVars(components, config) {
const result = await getRegistryItems(components, { config });
return z.array(registryItemSchema).parse(result).some((component) => component.type === "registry:theme" || component.type === "registry:style");
}
function validateFilesTarget(files$1, cwd) {
for (const file of files$1) {
if (!file?.target) continue;
if (!isSafeTarget(file.target, cwd)) throw new Error(`We found an unsafe file path "${file.target} in the registry item. Installation aborted.`);
}
}
//#endregion
//#region src/utils/env-loader.ts
async function loadEnvFiles(cwd = process.cwd()) {
try {
const { config } = await import("@dotenvx/dotenvx");
for (const envFile of [
".env.local",
".env.development.local",
".env.development",
".env"
]) {
const envPath = join(cwd, envFile);
if (existsSync(envPath)) config({
path: envPath,
overload: false,
quiet: true
});
}
} catch (error) {
logger.warn("Failed to load env files:", error);
}
}
//#endregion
//#region src/utils/file-helper.ts
const FILE_BACKUP_SUFFIX = ".bak";
function createFileBackup(filePath) {
if (!fsExtra.existsSync(filePath)) return null;
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
try {
fsExtra.renameSync(filePath, backupPath);
return backupPath;
} catch (error) {
console.error(`Failed to create backup of ${filePath}: ${error}`);
return null;
}
}
function restoreFileBackup(filePath) {
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
if (!fsExtra.existsSync(backupPath)) return false;
try {
fsExtra.renameSync(backupPath, filePath);
return true;
} catch (error) {
console.error(`Warning: Could not restore backup file ${backupPath}: ${error}`);
return false;
}
}
function deleteFileBackup(filePath) {
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
if (!fsExtra.existsSync(backupPath)) return false;
try {
fsExtra.unlinkSync(backupPath);
return true;
} catch (error) {
return false;
}
}
//#endregion
//#region src/registry/namespaces.ts
async function resolveRegistryNamespaces(components, config) {
const discoveredNamespaces = /* @__PURE__ */ new Set();
const visitedItems = /* @__PURE__ */ new Set();
const itemsToProcess = [...components];
while (itemsToProcess.length > 0) {
const currentItem = itemsToProcess.shift();
if (visitedItems.has(currentItem)) continue;
visitedItems.add(currentItem);
const { registry } = parseRegistryAndItemFromString(currentItem);
if (registry && !BUILTIN_REGISTRIES[registry]) discoveredNamespaces.add(registry);
try {
const [item] = await fetchRegistryItems([currentItem], config, { useCache: true });
if (item?.registryDependencies) for (const dep of item.registryDependencies) {
const { registry: depRegistry } = parseRegistryAndItemFromString(dep);
if (depRegistry && !BUILTIN_REGISTRIES[depRegistry]) discoveredNamespaces.add(depRegistry);
if (!visitedItems.has(dep)) itemsToProcess.push(dep);
}
} catch (error) {
if (error instanceof RegistryNotConfiguredError) {
const { registry: registry$1 } = parseRegistryAndItemFromString(currentItem);
if (registry$1 && !BUILTIN_REGISTRIES[registry$1]) discoveredNamespaces.add(registry$1);
continue;
}
continue;
}
}
return Array.from(discoveredNamespaces);
}
//#endregion
//#region src/utils/registries.ts
async function ensureRegistriesInConfig(components, config, options = {}) {
options = {
silent: false,
writeFile: true,
...options
};
const missingRegistries = (await resolveRegistryNamespaces(components, config)).filter((registry) => !config.registries?.[registry] && !Object.keys(BUILTIN_REGISTRIES).includes(registry));
if (missingRegistries.length === 0) return {
config,
newRegistries: []
};
const registryIndex = await getRegistriesIndex({ useCache: process.env.NODE_ENV !== "development" });
if (!registryIndex) return {
config,
newRegistries: []
};
const foundRegistries = {};
for (const registry of missingRegistries) if (registryIndex[registry]) foundRegistries[registry] = registryIndex[registry];
if (Object.keys(foundRegistries).length === 0) return {
config,
newRegistries: []
};
const existingRegistries = Object.fromEntries(Object.entries(config.registries || {}).filter(([key]) => !Object.keys(BUILTIN_REGISTRIES).includes(key)));
const newConfigWithRegistries = {
...config,
registries: {
...existingRegistries,
...foundRegistries
}
};
if (options.writeFile) {
const { resolvedPaths,...configWithoutResolvedPaths } = newConfigWithRegistries;
const configSpinner = spinner("Updating components.json.", { silent: options.silent }).start();
const updatedConfig = rawConfigSchema.parse(configWithoutResolvedPaths);
await fsExtra.writeFile(path.resolve(config.resolvedPaths.cwd, "components.json"), `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
configSpinner.succeed();
}
return {
config: newConfigWithRegistries,
newRegistries: Object.keys(foundRegistries)
};
}
//#endregion
//#region src/utils/updaters/update-tailwind-content.ts
async function updateTailwindContent(content, config, options) {
if (!content) return;
options = {
silent: false,
...options
};
const tailwindFileRelativePath = path.relative(config.resolvedPaths.cwd, config.resolvedPaths.tailwindConfig);
const tailwindSpinner = spinner(`Updating ${highlighter.info(tailwindFileRelativePath)}`, { silent: options.silent }).start();
const output = await transformTailwindContent(await promises.readFile(config.resolvedPaths.tailwindConfig, "utf8"), content, config);
await promises.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8");
tailwindSpinner?.succeed();
}
async function transformTailwindContent(input, content, config) {
const sourceFile = await _createSourceFile(input, config);
const configObject = sourceFile.getDescendantsOfKi