UNPKG

shadcn-gdce

Version:

Add components to your apps.

1,324 lines (1,297 loc) 44.1 kB
#!/usr/bin/env node // src/index.ts import process6 from "node:process"; // src/commands/add.ts import { existsSync as existsSync2, promises as fs, rmSync } from "node:fs"; import process3 from "node:process"; // src/utils/get-config.ts import { existsSync } from "node:fs"; // src/utils/resolve-import.ts import { createMatchPath } from "tsconfig-paths"; function resolveImport(importPath, config) { return createMatchPath(config.absoluteBaseUrl, config.paths)( importPath, void 0, () => true, [".ts", ".tsx", ".vue"] ); } // src/utils/get-config.ts import { loadConfig as c12LoadConfig } from "c12"; import path from "pathe"; import { loadConfig } from "tsconfig-paths"; import { z } from "zod"; var DEFAULT_COMPONENTS = "@/components"; var DEFAULT_UTILS = "@/lib/utils"; var DEFAULT_TYPESCRIPT_CONFIG = "./tsconfig.json"; var DEFAULT_TAILWIND_CONFIG = "tailwind.config.js"; var TAILWIND_CSS_PATH = { nuxt: "assets/css/tailwind.css", vite: "src/assets/index.css", laravel: "resources/css/app.css", astro: "src/styles/globals.css" }; var rawConfigSchema = z.object({ $schema: z.string().optional(), style: z.string(), typescript: z.boolean().default(true), tsConfigPath: z.string().default(DEFAULT_TYPESCRIPT_CONFIG), tailwind: z.object({ config: z.string(), css: z.string(), baseColor: z.string(), cssVariables: z.boolean().default(true), prefix: z.string().optional() }), framework: z.string().default("Vite"), aliases: z.object({ components: z.string(), utils: z.string(), ui: z.string().default("").optional() }) }).strict(); var configSchema = rawConfigSchema.extend({ resolvedPaths: z.object({ tailwindConfig: z.string(), tailwindCss: z.string(), utils: z.string(), components: z.string(), ui: z.string() }) }); async function getConfig(cwd) { const config = await getRawConfig(cwd); if (!config) return null; return await resolveConfigPaths(cwd, config); } async function resolveConfigPaths(cwd, config) { let tsConfig; let tsConfigPath = path.resolve( cwd, config.tsConfigPath ); if (config.typescript) { tsConfig = loadConfig(tsConfigPath); if ("paths" in tsConfig && Object.keys(tsConfig.paths).length === 0) { tsConfigPath = path.resolve(cwd, "./tsconfig.app.json"); if (existsSync(tsConfigPath)) tsConfig = loadConfig(tsConfigPath); } } else { tsConfigPath = config.tsConfigPath.includes("tsconfig.json") ? path.resolve(cwd, "./jsconfig.json") : path.resolve(cwd, config.tsConfigPath); tsConfig = loadConfig(tsConfigPath); } if (tsConfig.resultType === "failed") { throw new Error( `Failed to load ${tsConfigPath}. ${tsConfig.message ?? ""}`.trim() ); } return configSchema.parse({ ...config, resolvedPaths: { tailwindConfig: path.resolve(cwd, config.tailwind.config), tailwindCss: path.resolve(cwd, config.tailwind.css), utils: resolveImport(config.aliases.utils, tsConfig), components: resolveImport(config.aliases.components, tsConfig), ui: config.aliases.ui ? resolveImport(config.aliases.ui, tsConfig) : resolveImport(config.aliases.components, tsConfig) } }); } async function getRawConfig(cwd) { try { const configResult = await c12LoadConfig({ name: "components", configFile: "components.json", cwd }); if (!configResult.config || Object.keys(configResult.config).length === 0) return null; return rawConfigSchema.parse(configResult.config); } catch (error) { throw new Error(`Invalid configuration found in ${cwd}/components.json.`); } } // src/utils/handle-error.ts import { consola } from "consola"; function handleError(error) { if (typeof error === "string") { consola.error(error); process.exit(1); } if (error instanceof Error) { consola.error(error.message); process.exit(1); } consola.error("Something went wrong. Please try again."); process.exit(1); } // src/utils/registry/index.ts import process2 from "node:process"; // src/utils/registry/schema.ts import { z as z2 } from "zod"; var registryItemSchema = z2.object({ name: z2.string(), dependencies: z2.array(z2.string()).optional(), devDependencies: z2.array(z2.string()).optional(), registryDependencies: z2.array(z2.string()).optional(), files: z2.array(z2.string()), type: z2.enum(["components:ui", "components:component", "components:example"]) }); var registryIndexSchema = z2.array(registryItemSchema); var registryItemWithContentSchema = registryItemSchema.extend({ files: z2.array( z2.object({ name: z2.string(), content: z2.string() }) ) }); var registryWithContentSchema = z2.array(registryItemWithContentSchema); var stylesSchema = z2.array( z2.object({ name: z2.string(), label: z2.string() }) ); var registryBaseColorSchema = z2.object({ inlineColors: z2.object({ light: z2.record(z2.string(), z2.string()), dark: z2.record(z2.string(), z2.string()) }), cssVars: z2.object({ light: z2.record(z2.string(), z2.string()), dark: z2.record(z2.string(), z2.string()) }), inlineColorsTemplate: z2.string(), cssVarsTemplate: z2.string() }); // src/utils/registry/index.ts import consola2 from "consola"; import { ofetch } from "ofetch"; import path2 from "pathe"; import { ProxyAgent } from "undici"; var baseUrl = process2.env.COMPONENTS_REGISTRY_URL ?? "https://apps.customs.gov.kh/shadcn-gdce"; var agent = process2.env.https_proxy ? new ProxyAgent(process2.env.https_proxy) : void 0; async function getRegistryIndex() { try { const [result] = await fetchRegistry(["index.json"]); return registryIndexSchema.parse(result); } catch (error) { throw new Error("Failed to fetch components from registry."); } } async function getRegistryStyles() { try { const [result] = await fetchRegistry(["styles/index.json"]); return stylesSchema.parse(result); } catch (error) { throw new Error("Failed to fetch styles from registry."); } } function getRegistryBaseColors() { return [ { name: "customs", label: "Customs" }, { name: "trader", label: "Trader" } ]; } async function getRegistryBaseColor(baseColor) { try { const [result] = await fetchRegistry([`colors/${baseColor}.json`]); return registryBaseColorSchema.parse(result); } catch (error) { throw new Error("Failed to fetch base color from registry."); } } async function resolveTree(index, names) { const tree = []; for (const name of names) { const entry = index.find((entry2) => entry2.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, index2, self) => self.findIndex((c) => c.name === component.name) === index2 ); } async function fetchTree(style, tree) { try { const paths = tree.map((item) => `styles/${style}/${item.name}.json`); const result = await fetchRegistry(paths); return registryWithContentSchema.parse(result); } catch (error) { throw new Error("Failed to fetch tree from registry."); } } function getItemTargetPath(config, item, override) { if (override) return override; if (item.type === "components:ui" && config.aliases.ui) return config.resolvedPaths.ui; const [parent, type] = item.type.split(":"); if (!(parent in config.resolvedPaths)) return null; return path2.join( config.resolvedPaths[parent], type ); } async function fetchRegistry(paths) { try { const results = await Promise.all( paths.map(async (path8) => { const response = await ofetch(`${baseUrl}/registry/${path8}`, { dispatcher: agent }); return response; }) ); return results; } catch (error) { consola2.error(error); throw new Error(`Failed to fetch registry from ${baseUrl}.`); } } // src/utils/transformers/transform-css-vars.ts function transformCssVars(opts) { return { type: "codemod", name: "add prefix to tailwind classes", transform({ scriptASTs, sfcAST, utils: { traverseScriptAST, traverseTemplateAST } }) { let transformCount = 0; const { baseColor, config } = opts; if (config.tailwind?.cssVariables || !baseColor?.inlineColors) return transformCount; for (const scriptAST of scriptASTs) { traverseScriptAST(scriptAST, { visitLiteral(path8) { if (path8.parent.value.type !== "ImportDeclaration" && typeof path8.node.value === "string") { path8.node.value = applyColorMapping(path8.node.value.replace(/"/g, ""), baseColor.inlineColors); transformCount++; } return this.traverse(path8); } }); } if (sfcAST) { traverseTemplateAST(sfcAST, { enterNode(node) { if (node.type === "Literal" && typeof node.value === "string") { if (!["BinaryExpression", "Property"].includes(node.parent?.type ?? "")) { node.value = applyColorMapping(node.value.replace(/"/g, ""), baseColor.inlineColors); transformCount++; } } else if (node.type === "VLiteral" && typeof node.value === "string") { if (node.parent.key.name === "class") { node.value = `"${applyColorMapping(node.value.replace(/"/g, ""), baseColor.inlineColors)}"`; transformCount++; } } }, leaveNode() { } }); } return transformCount; } }; } function splitClassName(className) { if (!className.includes("/") && !className.includes(":")) return [null, className, null]; const parts = []; const [rest, alpha] = className.split("/"); if (!rest.includes(":")) return [null, rest, alpha]; const split = rest.split(":"); const name = split.pop(); const variant = split.join(":"); parts.push(variant ?? null, name ?? null, alpha ?? null); return parts; } var PREFIXES = ["bg-", "text-", "border-", "ring-offset-", "ring-"]; function applyColorMapping(input, mapping) { if (input.includes(" border ")) input = input.replace(" border ", " border border-border "); const classNames = input.split(" "); const lightMode = /* @__PURE__ */ new Set(); const darkMode = /* @__PURE__ */ new Set(); for (const className of classNames) { const [variant, value, modifier] = splitClassName(className); const prefix = PREFIXES.find((prefix2) => value?.startsWith(prefix2)); if (!prefix) { if (!lightMode.has(className)) lightMode.add(className); continue; } const needle = value?.replace(prefix, ""); if (needle && needle in mapping.light) { lightMode.add( [variant, `${prefix}${mapping.light[needle]}`].filter(Boolean).join(":") + (modifier ? `/${modifier}` : "") ); darkMode.add( ["dark", variant, `${prefix}${mapping.dark[needle]}`].filter(Boolean).join(":") + (modifier ? `/${modifier}` : "") ); continue; } if (!lightMode.has(className)) lightMode.add(className); } return [...Array.from(lightMode), ...Array.from(darkMode)].join(" ").trim(); } // src/utils/transformers/transform-import.ts function transformImport(opts) { return { type: "codemod", name: "modify import based on user config", transform({ scriptASTs, utils: { traverseScriptAST } }) { const transformCount = 0; const { config } = opts; for (const scriptAST of scriptASTs) { traverseScriptAST(scriptAST, { visitImportDeclaration(path8) { if (typeof path8.node.source.value === "string") { const sourcePath = path8.node.source.value; if (sourcePath.startsWith("@/lib/registry/")) { if (config.aliases.ui) { path8.node.source.value = sourcePath.replace(/^@\/lib\/registry\/[^/]+\/ui/, config.aliases.ui); } else { path8.node.source.value = sourcePath.replace(/^@\/lib\/registry\/[^/]+/, config.aliases.components); } } if (sourcePath === "@/lib/utils") { const namedImports = path8.node.specifiers?.map((node) => node.local?.name ?? "") ?? []; const cnImport = namedImports.find((i) => i === "cn"); if (cnImport) { path8.node.source.value = sourcePath.replace(/^@\/lib\/utils/, config.aliases.utils); } } } return this.traverse(path8); } }); } return transformCount; } }; } // src/utils/transformers/transform-sfc.ts import { transform } from "@unovue/detypes"; async function transformSFC(opts) { if (opts.config?.typescript) return opts.raw; return await transformByDetype(opts.raw, opts.filename).then((res) => res); } async function transformByDetype(content, filename) { return await transform(content, filename, { removeTsComments: true, prettierOptions: { proseWrap: "never" } }); } // src/utils/transformers/transform-tw-prefix.ts function transformTwPrefix(opts) { return { type: "codemod", name: "add prefix to tailwind classes", transform({ scriptASTs, sfcAST, utils: { traverseScriptAST, traverseTemplateAST, astHelpers } }) { let transformCount = 0; const { config } = opts; const CLASS_IDENTIFIER = ["class", "classes"]; if (!config.tailwind?.prefix) return transformCount; for (const scriptAST of scriptASTs) { traverseScriptAST(scriptAST, { visitCallExpression(path8) { if (path8.node.callee.type === "Identifier" && path8.node.callee.name === "cva") { const nodes = path8.node.arguments; nodes.forEach((node) => { if (node.type === "Literal" && typeof node.value === "string") { node.value = applyPrefix(node.value, config.tailwind.prefix); transformCount++; } else if (node.type === "ObjectExpression") { node.properties.forEach((node2) => { if (node2.type === "Property" && node2.key.type === "Identifier" && node2.key.name === "variants") { const nodes2 = astHelpers.findAll(node2, { type: "Literal" }); nodes2.forEach((node3) => { if (typeof node3.value === "string") { node3.value = applyPrefix(node3.value, config.tailwind.prefix); transformCount++; } }); } }); } }); } return this.traverse(path8); } }); } if (sfcAST) { traverseTemplateAST(sfcAST, { enterNode(node) { if (node.type === "VAttribute" && node.key.type === "VDirectiveKey") { if (node.key.argument?.type === "VIdentifier") { if (CLASS_IDENTIFIER.includes(node.key.argument.name)) { const nodes = astHelpers.findAll(node, { type: "Literal" }); nodes.forEach((node2) => { if (!["BinaryExpression", "Property"].includes(node2.parent?.type ?? "") && typeof node2.value === "string") { node2.value = applyPrefix(node2.value, config.tailwind.prefix); transformCount++; } }); } } } else if (node.type === "VLiteral" && typeof node.value === "string") { if (CLASS_IDENTIFIER.includes(node.parent.key.name)) { node.value = `"${applyPrefix(node.value.replace(/"/g, ""), config.tailwind.prefix)}"`; transformCount++; } } }, leaveNode() { } }); } return transformCount; } }; } function applyPrefix(input, prefix = "") { const classNames = input.split(" "); const prefixed = []; for (const className of classNames) { const [variant, value, modifier] = splitClassName(className); if (variant) { modifier ? prefixed.push(`${variant}:${prefix}${value}/${modifier}`) : prefixed.push(`${variant}:${prefix}${value}`); } else { modifier ? prefixed.push(`${prefix}${value}/${modifier}`) : prefixed.push(`${prefix}${value}`); } } return prefixed.join(" "); } function applyPrefixesCss(css, prefix) { const lines = css.split("\n"); for (const line of lines) { if (line.includes("@apply")) { const originalTWCls = line.replace("@apply", "").trim(); const prefixedTwCls = applyPrefix(originalTWCls, prefix); css = css.replace(originalTWCls, prefixedTwCls); } } return css; } // src/utils/transformers/index.ts import { transform as metaTransform } from "vue-metamorph"; async function transform2(opts) { const source = await transformSFC(opts); return metaTransform(source, opts.filename, [ transformImport(opts), transformCssVars(opts), transformTwPrefix(opts) ]).code; } // src/commands/add.ts import { Command } from "commander"; import { consola as consola3 } from "consola"; import { colors } from "consola/utils"; import { addDependency, addDevDependency } from "nypm"; import ora from "ora"; import path3 from "pathe"; import prompts from "prompts"; import { z as z3 } from "zod"; var addOptionsSchema = z3.object({ components: z3.array(z3.string()).optional(), yes: z3.boolean(), overwrite: z3.boolean(), cwd: z3.string(), all: z3.boolean(), path: z3.string().optional() }); var add = new Command().name("add").description("add a component to your project").argument("[components...]", "the components to add").option("-y, --yes", "skip confirmation prompt.", true).option("-o, --overwrite", "overwrite existing files.", false).option( "-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process3.cwd() ).option("-a, --all", "add all available components", false).option("-p, --path <path>", "the path to add the component to.").action(async (components, opts) => { try { const options = addOptionsSchema.parse({ components, ...opts }); const cwd = path3.resolve(options.cwd); if (!existsSync2(cwd)) { consola3.error(`The path ${cwd} does not exist. Please try again.`); process3.exit(1); } const config = await getConfig(cwd); if (!config) { consola3.warn(`Configuration is missing. Please run ${colors.green("init")} to create a components.json file.`); process3.exit(1); } const registryIndex = await getRegistryIndex(); let selectedComponents = options.all ? registryIndex.map((entry) => entry.name) : options.components; if (!options.components?.length && !options.all) { const { components: components2 } = await prompts({ type: "multiselect", name: "components", message: "Which components would you like to add?", hint: "Space to select. A to toggle all. Enter to submit.", instructions: false, choices: registryIndex.map((entry) => ({ title: entry.name, value: entry.name, selected: options.all ? true : options.components?.includes(entry.name) })) }); selectedComponents = components2; } if (!selectedComponents?.length) { consola3.warn("No components selected. Exiting."); process3.exit(0); } const tree = await resolveTree(registryIndex, selectedComponents); const payload = await fetchTree(config.style, tree); const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); if (!payload.length) { consola3.warn("Selected components not found. Exiting."); process3.exit(0); } if (!options.yes) { const { proceed } = await prompts({ type: "confirm", name: "proceed", message: "Ready to install components and dependencies. Proceed?", initial: true }); if (!proceed) process3.exit(0); } const spinner = ora("Installing components...").start(); for (const item of payload) { spinner.text = `Installing ${item.name}...`; const targetDir = getItemTargetPath( config, item, options.path ? path3.resolve(cwd, options.path) : void 0 ); if (!targetDir) continue; if (!existsSync2(targetDir)) await fs.mkdir(targetDir, { recursive: true }); const existingComponent = item.files.filter( (file) => existsSync2(path3.resolve(targetDir, item.name, file.name)) ); if (existingComponent.length && !options.overwrite) { if (selectedComponents.includes(item.name)) { spinner.stop(); const { overwrite } = await prompts({ type: "confirm", name: "overwrite", message: `Component ${item.name} already exists. Would you like to overwrite?`, initial: false }); if (!overwrite) { consola3.info( `Skipped ${item.name}. To overwrite, run with the ${colors.green( "--overwrite" )} flag.` ); continue; } spinner.start(`Installing ${item.name}...`); } else { continue; } } await Promise.allSettled( [ item.dependencies?.length && await addDependency(item.dependencies, { cwd, silent: true }), item.devDependencies?.length && await addDevDependency(item.devDependencies, { cwd, silent: true }) ] ); const componentDir = path3.resolve(targetDir, item.name); if (!existsSync2(componentDir)) await fs.mkdir(componentDir, { recursive: true }); const files = item.files.map((file) => ({ ...file, path: path3.resolve( targetDir, item.name, file.name ) })); if (!config.typescript) { for (const file of files) await fs.writeFile(file.path, file.content); } for (const file of files) { const content = await transform2({ filename: file.path, raw: file.content, config, baseColor }); let filePath = file.path; if (!config.typescript) { if (file.path.endsWith(".ts")) { if (existsSync2(file.path)) rmSync(file.path); } filePath = file.path.replace(/\.ts$/, ".js"); } await fs.writeFile(filePath, content); } } spinner.succeed("Done."); } catch (error) { handleError(error); } }); // src/commands/diff.ts import { existsSync as existsSync3, promises as fs2 } from "node:fs"; import process4 from "node:process"; import { Command as Command2 } from "commander"; import { consola as consola4 } from "consola"; import { colors as colors2 } from "consola/utils"; import { diffLines } from "diff"; import path4 from "pathe"; import { z as z4 } from "zod"; var updateOptionsSchema = z4.object({ component: z4.string().optional(), yes: z4.boolean(), cwd: z4.string(), path: z4.string().optional() }); var diff = new Command2().name("diff").description("check for updates against the registry").argument("[component]", "the component name").option("-y, --yes", "skip confirmation prompt.", false).option( "-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process4.cwd() ).action(async (name, opts) => { try { const options = updateOptionsSchema.parse({ component: name, ...opts }); const cwd = path4.resolve(options.cwd); if (!existsSync3(cwd)) { consola4.error(`The path ${cwd} does not exist. Please try again.`); process4.exit(1); } const config = await getConfig(cwd); if (!config) { consola4.warn( `Configuration is missing. Please run ${colors2.green( "init" )} to create a components.json file.` ); process4.exit(1); } const registryIndex = await getRegistryIndex(); if (!options.component) { const targetDir = config.resolvedPaths.components; const projectComponents = registryIndex.filter((item) => { for (const file of item.files) { const filePath = path4.resolve(targetDir, file); if (existsSync3(filePath)) return true; } return false; }); const componentsWithUpdates = []; for (const component2 of projectComponents) { const changes2 = await diffComponent(component2, config); if (changes2.length) { componentsWithUpdates.push({ name: component2.name, changes: changes2 }); } } if (!componentsWithUpdates.length) { consola4.info("No updates found."); process4.exit(0); } consola4.info("The following components have updates available:"); for (const component2 of componentsWithUpdates) { consola4.info(`- ${component2.name}`); for (const change of component2.changes) consola4.info(` - ${change.filePath}`); } consola4.log(""); consola4.info( `Run ${colors2.green("diff <component>")} to see the changes.` ); process4.exit(0); } const component = registryIndex.find( (item) => item.name === options.component ); if (!component) { consola4.error( `The component ${colors2.green(options.component)} does not exist.` ); process4.exit(1); } const changes = await diffComponent(component, config); if (!changes.length) { consola4.info(`No updates found for ${options.component}.`); process4.exit(0); } for (const change of changes) { consola4.info(`- ${change.filePath}`); printDiff(change.patch); consola4.log(""); } } catch (error) { handleError(error); } }); async function diffComponent(component, config) { const payload = await fetchTree(config.style, [component]); const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); const changes = []; for (const item of payload) { const targetDir = await getItemTargetPath(config, item); if (!targetDir) continue; for (const file of item.files) { const filePath = path4.resolve(targetDir, file.name); if (!existsSync3(filePath)) continue; const fileContent = await fs2.readFile(filePath, "utf8"); const registryContent = await transform2({ filename: file.name, raw: file.content, config, baseColor }); const patch = diffLines(registryContent, fileContent); if (patch.length > 1) { changes.push({ file: file.name, filePath, patch }); } } } return changes; } function printDiff(diff2) { diff2.forEach((part) => { if (part) { if (part.added) return process4.stdout.write(colors2.green(part.value)); if (part.removed) return process4.stdout.write(colors2.red(part.value)); return process4.stdout.write(part.value); } }); } // src/commands/init.ts import { existsSync as existsSync5, promises as fs4 } from "node:fs"; import process5 from "node:process"; import { Command as Command3 } from "commander"; import { consola as consola5 } from "consola"; import { colors as colors3 } from "consola/utils"; import { template } from "lodash-es"; import { addDependency as addDependency2 } from "nypm"; import ora2 from "ora"; import path6 from "pathe"; import prompts2 from "prompts"; import { z as z5 } from "zod"; // src/utils/get-project-info.ts import { existsSync as existsSync4 } from "node:fs"; import fs3 from "fs-extra"; import path5 from "pathe"; import { readPackageJSON } from "pkg-types"; async function getProjectInfo() { const info = { tsconfig: null, isNuxt: false, shadcnNuxt: void 0, isVueVite: false, srcDir: false, componentsUiDir: false, srcComponentsUiDir: false }; try { const tsconfig = await getTsConfig(); const isNuxt = existsSync4(path5.resolve("./nuxt.config.js")) || existsSync4(path5.resolve("./nuxt.config.ts")); const shadcnNuxt = isNuxt ? await getShadcnNuxtInfo() : void 0; return { tsconfig, isNuxt, shadcnNuxt, isVueVite: existsSync4(path5.resolve("./vite.config.js")) || existsSync4(path5.resolve("./vite.config.ts")), srcDir: existsSync4(path5.resolve("./src")), srcComponentsUiDir: existsSync4(path5.resolve("./src/components/ui")), componentsUiDir: existsSync4(path5.resolve("./components/ui")) }; } catch (error) { return info; } } async function getShadcnNuxtInfo() { let nuxtModule; try { nuxtModule = await readPackageJSON("shadcn-gdce-nuxt"); } catch (error) { nuxtModule = void 0; } return nuxtModule; } async function getTsConfig() { try { const tsconfigPath = path5.join("tsconfig.json"); const tsconfig = await fs3.readJSON(tsconfigPath); if (!tsconfig) throw new Error("tsconfig.json is missing"); return tsconfig; } catch (error) { return null; } } // src/utils/templates.ts var UTILS = `import type { ClassValue } from 'clsx' import { clsx } from 'clsx' import { twMerge } from 'tailwind-merge' import type { Ref } from 'vue' import type { Updater } from '@tanstack/vue-table' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) { ref.value = typeof updaterOrValue === 'function' ? updaterOrValue(ref.value) : updaterOrValue } `; var TAILWIND_CONFIG = `const animate = require("tailwindcss-animate") /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], content: [ './pages/**/*.{<%- extension %>,<%- extension %>x,vue}', './components/**/*.{<%- extension %>,<%- extension %>x,vue}', './app/**/*.{<%- extension %>,<%- extension %>x,vue}', './src/**/*.{<%- extension %>,<%- extension %>x,vue}', ], prefix: "<%- prefix %>", theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [animate], }`; var TAILWIND_CONFIG_WITH_VARIABLES = `const animate = require("tailwindcss-animate") /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], safelist: ["dark"], prefix: "<%- prefix %>", <% if (framework === 'vite' || framework === 'nuxt') { %> content: [ './pages/**/*.{<%- extension %>,<%- extension %>x,vue}', './components/**/*.{<%- extension %>,<%- extension %>x,vue}', './app/**/*.{<%- extension %>,<%- extension %>x,vue}', './src/**/*.{<%- extension %>,<%- extension %>x,vue}', ], <% } else if (framework === 'laravel') { %> content: [ "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php", "./storage/framework/views/*.php", "./resources/views/**/*.blade.php", "./resources/js/**/*.{<%- extension %>,<%- extension %>x,vue}", ], <% } else if (framework === 'astro') { %> content: [ './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', ], <% } %> theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, sidebar: { 'DEFAULT': 'hsl(var(--sidebar-background))', 'foreground': 'hsl(var(--sidebar-foreground))', 'primary': 'hsl(var(--sidebar-primary))', 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', 'accent': 'hsl(var(--sidebar-accent))', 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', 'border': 'hsl(var(--sidebar-border))', 'ring': 'hsl(var(--sidebar-ring))', }, }, borderRadius: { xl: "calc(var(--radius) + 4px)", lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, "collapsible-down": { from: { height: 0 }, to: { height: 'var(--radix-collapsible-content-height)' }, }, "collapsible-up": { from: { height: 'var(--radix-collapsible-content-height)' }, to: { height: 0 }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "collapsible-down": "collapsible-down 0.2s ease-in-out", "collapsible-up": "collapsible-up 0.2s ease-in-out", }, }, }, plugins: [animate], }`; // src/utils/transformers/transform-cjs-to-esm.ts function transformCJSToESM(filename, code) { if (filename.endsWith(".mjs")) { return code.replace(/const\s(\w+)\s*=\s*require\((.*)\);?/g, "import $1 from $2").replace(/module\.exports = /g, "export default "); } return code; } // src/commands/init.ts var PROJECT_DEPENDENCIES = { base: [ "tailwindcss-animate", "class-variance-authority", "clsx", "tailwind-merge", "radix-vue" ] }; var initOptionsSchema = z5.object({ cwd: z5.string(), yes: z5.boolean() }); var init = new Command3().name("init").description("initialize your project and install dependencies").option("-y, --yes", "skip confirmation prompt.", false).option( "-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process5.cwd() ).action(async (opts) => { try { const options = initOptionsSchema.parse(opts); const cwd = path6.resolve(options.cwd); if (!existsSync5(cwd)) { consola5.error(`The path ${cwd} does not exist. Please try again.`); process5.exit(1); } const existingConfig = await getConfig(cwd); const config = await promptForConfig(cwd, existingConfig, options.yes); await runInit(cwd, config); consola5.log(""); consola5.info( `${colors3.green("Success!")} Project initialization completed.` ); consola5.log(""); } catch (error) { handleError(error); } }); async function promptForConfig(cwd, defaultConfig = null, skip = false) { const highlight = (text) => colors3.cyan(text); const styles = await getRegistryStyles(); const baseColors = await getRegistryBaseColors(); const options = await prompts2([ { type: "toggle", name: "typescript", message: `Would you like to use ${highlight("TypeScript")}? ${colors3.gray("(recommended)")}?`, initial: defaultConfig?.typescript ?? true, active: "yes", inactive: "no" }, { type: "select", name: "framework", message: `Which ${highlight("framework")} are you using?`, choices: [ // { title: 'Vite', value: 'vite' }, { title: "Nuxt", value: "nuxt" }, { title: "Laravel", value: "laravel" } // { title: 'Astro', value: 'astro' }, ] }, { type: "select", name: "style", message: `Which ${highlight("style")} would you like to use?`, choices: styles.map((style) => ({ title: style.label, value: style.name })) }, { type: "select", name: "tailwindBaseColor", message: `Which color would you like to use as ${highlight( "base color" )}?`, choices: baseColors.map((color) => ({ title: color.label, value: color.name })) }, { type: "text", name: "tsConfigPath", message: (prev, values) => `Where is your ${highlight(values.typescript ? "tsconfig.json" : "jsconfig.json")} file?`, initial: (prev, values) => { const prefix = values.framework === "nuxt" ? ".nuxt/" : "./"; const path8 = values.typescript ? "tsconfig.json" : "jsconfig.json"; return prefix + path8; } }, { type: "text", name: "tailwindCss", message: `Where is your ${highlight("global CSS")} file? ${colors3.gray("(this file will be overwritten)")}`, initial: (prev, values) => defaultConfig?.tailwind.css ?? TAILWIND_CSS_PATH[values.framework] }, { type: "toggle", name: "tailwindCssVariables", message: `Would you like to use ${highlight( "CSS variables" )} for colors?`, initial: defaultConfig?.tailwind.cssVariables ?? true, active: "yes", inactive: "no" }, { type: "text", name: "tailwindPrefix", message: `Are you using a custom ${highlight( "tailwind prefix eg. tw-" )}? (Leave blank if not)`, initial: "" }, { type: "text", name: "tailwindConfig", message: `Where is your ${highlight("tailwind.config")} located? ${colors3.gray("(this file will be overwritten)")}`, initial: (prev, values) => { if (defaultConfig?.tailwind.config) return defaultConfig?.tailwind.config; if (values.framework === "astro") return "tailwind.config.mjs"; else return DEFAULT_TAILWIND_CONFIG; } }, { type: "text", name: "components", message: `Configure the import alias for ${highlight("components")}:`, initial: defaultConfig?.aliases.components ?? DEFAULT_COMPONENTS }, { type: "text", name: "utils", message: `Configure the import alias for ${highlight("utils")}:`, initial: defaultConfig?.aliases.utils ?? DEFAULT_UTILS } ]); const config = rawConfigSchema.parse({ $schema: "https://apps.customs.gov.kh/shadcn-gdce/schema.json", style: options.style, typescript: options.typescript, tsConfigPath: options.tsConfigPath, framework: options.framework, tailwind: { config: options.tailwindConfig, css: options.tailwindCss, baseColor: options.tailwindBaseColor, cssVariables: options.tailwindCssVariables, prefix: options.tailwindPrefix }, aliases: { utils: options.utils, components: options.components } }); if (!skip) { const { proceed } = await prompts2({ type: "confirm", name: "proceed", message: `Write configuration to ${highlight("components.json")}. Proceed?`, initial: true }); if (!proceed) process5.exit(0); } consola5.log(""); const spinner = ora2("Writing components.json...").start(); const targetPath = path6.resolve(cwd, "components.json"); await fs4.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8"); spinner.succeed(); return await resolveConfigPaths(cwd, config); } async function runInit(cwd, config) { const spinner = ora2("Initializing project...")?.start(); const { isNuxt, shadcnNuxt } = await getProjectInfo(); if (isNuxt) { consola5.log(""); shadcnNuxt ? consola5.info(`Detected a Nuxt project with 'shadcn-gdce-nuxt' v${shadcnNuxt.version}...`) : consola5.warn(`Detected a Nuxt project without 'shadcn-gdce-nuxt' module. It's recommended to install it.`); } for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) { let dirname = path6.extname(resolvedPath) ? path6.dirname(resolvedPath) : resolvedPath; if (key === "utils" && resolvedPath.endsWith("/utils")) { dirname = dirname.replace(/\/utils$/, ""); } if (!existsSync5(dirname)) await fs4.mkdir(dirname, { recursive: true }); } const extension = config.typescript ? "ts" : "js"; await fs4.writeFile( config.resolvedPaths.tailwindConfig, transformCJSToESM( config.resolvedPaths.tailwindConfig, config.tailwind.cssVariables ? template(TAILWIND_CONFIG_WITH_VARIABLES)({ extension, framework: config.framework, prefix: config.tailwind.prefix }) : template(TAILWIND_CONFIG)({ extension, framework: config.framework, prefix: config.tailwind.prefix }) ), "utf8" ); const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); if (baseColor) { await fs4.writeFile( config.resolvedPaths.tailwindCss, config.tailwind.cssVariables ? config.tailwind.prefix ? applyPrefixesCss(baseColor.cssVarsTemplate, config.tailwind.prefix) : baseColor.cssVarsTemplate : baseColor.inlineColorsTemplate, "utf8" ); } await fs4.writeFile( `${config.resolvedPaths.utils}.${extension}`, extension === "ts" ? UTILS : await transformByDetype(UTILS, ".ts"), "utf8" ); spinner?.succeed(); const dependenciesSpinner = ora2("Installing dependencies...")?.start(); const iconsDep = config.style === "new-york" ? ["@radix-icons/vue"] : ["lucide-vue-next"]; const deps = PROJECT_DEPENDENCIES.base.concat(iconsDep).filter(Boolean); await addDependency2(deps, { cwd, silent: true }); dependenciesSpinner?.succeed(); } // src/utils/get-package-info.ts import { fileURLToPath } from "node:url"; import fs5 from "fs-extra"; import path7 from "pathe"; function getPackageInfo() { const packageJsonPath = getPackageFilePath("../package.json"); return fs5.readJSONSync(packageJsonPath); } function getPackageFilePath(filePath) { const distPath = fileURLToPath(new URL(".", import.meta.url)); return path7.resolve(distPath, filePath); } // src/index.ts import { Command as Command4 } from "commander"; process6.on("SIGINT", () => process6.exit(0)); process6.on("SIGTERM", () => process6.exit(0)); async function main() { const packageInfo = await getPackageInfo(); const program = new Command4().name("shadcn-gdce").description("add components and dependencies to your project").version( packageInfo.version || "1.0.0", "-v, --version", "display the version number" ); program.addCommand(init).addCommand(add).addCommand(diff); program.parse(); } main(); //# sourceMappingURL=index.js.map