UNPKG

@ngzard/ui

Version:

An alternative to shadcn/ui for angular

1,701 lines (1,661 loc) 48.8 kB
#!/usr/bin/env node // src/index.ts import { Command as Command3 } from "commander"; // src/commands/init.ts import * as commentJson from "comment-json"; import { Command } from "commander"; import { existsSync as existsSync2 } from "fs"; import prompts from "prompts"; import fs2 from "fs-extra"; import chalk2 from "chalk"; import path3 from "path"; import { z as z2 } from "zod"; // src/themes/themes.ts var availableThemes = ["neutral", "stone", "zinc", "gray", "slate"]; var tailwindConfiguration = ` @import 'tailwindcss'; @import 'lucide-static/font/lucide.css'; @plugin "tailwindcss-animate"; @custom-variant dark (&:is(.dark *)); `; var themeConfiguration = ` @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } } `; var neutral = ` ${tailwindConfiguration} :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); --muted: oklch(0.97 0 0); --muted-foreground: oklch(0.556 0 0); --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --ring: oklch(0.708 0 0); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); --sidebar-primary: oklch(0.205 0 0); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.97 0 0); --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); } .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.922 0 0); --primary-foreground: oklch(0.205 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); --muted-foreground: oklch(0.708 0 0); --accent: oklch(0.269 0 0); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.556 0 0); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.269 0 0); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); } ${themeConfiguration} `; var stone = ` ${tailwindConfiguration} :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.147 0.004 49.25); --card: oklch(1 0 0); --card-foreground: oklch(0.147 0.004 49.25); --popover: oklch(1 0 0); --popover-foreground: oklch(0.147 0.004 49.25); --primary: oklch(0.216 0.006 56.043); --primary-foreground: oklch(0.985 0.001 106.423); --secondary: oklch(0.97 0.001 106.424); --secondary-foreground: oklch(0.216 0.006 56.043); --muted: oklch(0.97 0.001 106.424); --muted-foreground: oklch(0.553 0.013 58.071); --accent: oklch(0.97 0.001 106.424); --accent-foreground: oklch(0.216 0.006 56.043); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.923 0.003 48.717); --input: oklch(0.923 0.003 48.717); --ring: oklch(0.709 0.01 56.259); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0.001 106.423); --sidebar-foreground: oklch(0.147 0.004 49.25); --sidebar-primary: oklch(0.216 0.006 56.043); --sidebar-primary-foreground: oklch(0.985 0.001 106.423); --sidebar-accent: oklch(0.97 0.001 106.424); --sidebar-accent-foreground: oklch(0.216 0.006 56.043); --sidebar-border: oklch(0.923 0.003 48.717); --sidebar-ring: oklch(0.709 0.01 56.259); } .dark { --background: oklch(0.147 0.004 49.25); --foreground: oklch(0.985 0.001 106.423); --card: oklch(0.216 0.006 56.043); --card-foreground: oklch(0.985 0.001 106.423); --popover: oklch(0.216 0.006 56.043); --popover-foreground: oklch(0.985 0.001 106.423); --primary: oklch(0.923 0.003 48.717); --primary-foreground: oklch(0.216 0.006 56.043); --secondary: oklch(0.268 0.007 34.298); --secondary-foreground: oklch(0.985 0.001 106.423); --muted: oklch(0.268 0.007 34.298); --muted-foreground: oklch(0.709 0.01 56.259); --accent: oklch(0.268 0.007 34.298); --accent-foreground: oklch(0.985 0.001 106.423); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.553 0.013 58.071); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.216 0.006 56.043); --sidebar-foreground: oklch(0.985 0.001 106.423); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0.001 106.423); --sidebar-accent: oklch(0.268 0.007 34.298); --sidebar-accent-foreground: oklch(0.985 0.001 106.423); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.553 0.013 58.071); } ${themeConfiguration} `; var zinc = ` ${tailwindConfiguration} :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.21 0.006 285.885); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); --ring: oklch(0.705 0.015 286.067); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.21 0.006 285.885); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.015 286.067); } .dark { --background: oklch(0.141 0.005 285.823); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.92 0.004 286.32); --primary-foreground: oklch(0.21 0.006 285.885); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); --muted-foreground: oklch(0.705 0.015 286.067); --accent: oklch(0.274 0.006 286.033); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.552 0.016 285.938); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.552 0.016 285.938); } ${themeConfiguration} `; var gray = ` ${tailwindConfiguration} :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.13 0.028 261.692); --card: oklch(1 0 0); --card-foreground: oklch(0.13 0.028 261.692); --popover: oklch(1 0 0); --popover-foreground: oklch(0.13 0.028 261.692); --primary: oklch(0.21 0.034 264.665); --primary-foreground: oklch(0.985 0.002 247.839); --secondary: oklch(0.967 0.003 264.542); --secondary-foreground: oklch(0.21 0.034 264.665); --muted: oklch(0.967 0.003 264.542); --muted-foreground: oklch(0.551 0.027 264.364); --accent: oklch(0.967 0.003 264.542); --accent-foreground: oklch(0.21 0.034 264.665); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.928 0.006 264.531); --input: oklch(0.928 0.006 264.531); --ring: oklch(0.707 0.022 261.325); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0.002 247.839); --sidebar-foreground: oklch(0.13 0.028 261.692); --sidebar-primary: oklch(0.21 0.034 264.665); --sidebar-primary-foreground: oklch(0.985 0.002 247.839); --sidebar-accent: oklch(0.967 0.003 264.542); --sidebar-accent-foreground: oklch(0.21 0.034 264.665); --sidebar-border: oklch(0.928 0.006 264.531); --sidebar-ring: oklch(0.707 0.022 261.325); } .dark { --background: oklch(0.13 0.028 261.692); --foreground: oklch(0.985 0.002 247.839); --card: oklch(0.21 0.034 264.665); --card-foreground: oklch(0.985 0.002 247.839); --popover: oklch(0.21 0.034 264.665); --popover-foreground: oklch(0.985 0.002 247.839); --primary: oklch(0.928 0.006 264.531); --primary-foreground: oklch(0.21 0.034 264.665); --secondary: oklch(0.278 0.033 256.848); --secondary-foreground: oklch(0.985 0.002 247.839); --muted: oklch(0.278 0.033 256.848); --muted-foreground: oklch(0.707 0.022 261.325); --accent: oklch(0.278 0.033 256.848); --accent-foreground: oklch(0.985 0.002 247.839); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.551 0.027 264.364); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.034 264.665); --sidebar-foreground: oklch(0.985 0.002 247.839); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0.002 247.839); --sidebar-accent: oklch(0.278 0.033 256.848); --sidebar-accent-foreground: oklch(0.985 0.002 247.839); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.551 0.027 264.364); } ${themeConfiguration} `; var slate = ` ${tailwindConfiguration} :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.129 0.042 264.695); --card: oklch(1 0 0); --card-foreground: oklch(0.129 0.042 264.695); --popover: oklch(1 0 0); --popover-foreground: oklch(0.129 0.042 264.695); --primary: oklch(0.208 0.042 265.755); --primary-foreground: oklch(0.984 0.003 247.858); --secondary: oklch(0.968 0.007 247.896); --secondary-foreground: oklch(0.208 0.042 265.755); --muted: oklch(0.968 0.007 247.896); --muted-foreground: oklch(0.554 0.046 257.417); --accent: oklch(0.968 0.007 247.896); --accent-foreground: oklch(0.208 0.042 265.755); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508); --ring: oklch(0.704 0.04 256.788); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.984 0.003 247.858); --sidebar-foreground: oklch(0.129 0.042 264.695); --sidebar-primary: oklch(0.208 0.042 265.755); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.968 0.007 247.896); --sidebar-accent-foreground: oklch(0.208 0.042 265.755); --sidebar-border: oklch(0.929 0.013 255.508); --sidebar-ring: oklch(0.704 0.04 256.788); } .dark { --background: oklch(0.129 0.042 264.695); --foreground: oklch(0.984 0.003 247.858); --card: oklch(0.208 0.042 265.755); --card-foreground: oklch(0.984 0.003 247.858); --popover: oklch(0.208 0.042 265.755); --popover-foreground: oklch(0.984 0.003 247.858); --primary: oklch(0.929 0.013 255.508); --primary-foreground: oklch(0.208 0.042 265.755); --secondary: oklch(0.279 0.041 260.031); --secondary-foreground: oklch(0.984 0.003 247.858); --muted: oklch(0.279 0.041 260.031); --muted-foreground: oklch(0.704 0.04 256.788); --accent: oklch(0.279 0.041 260.031); --accent-foreground: oklch(0.984 0.003 247.858); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.551 0.027 264.364); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.208 0.042 265.755); --sidebar-foreground: oklch(0.984 0.003 247.858); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.984 0.003 247.858); --sidebar-accent: oklch(0.279 0.041 260.031); --sidebar-accent-foreground: oklch(0.984 0.003 247.858); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.551 0.027 264.364); } ${themeConfiguration} `; // src/utils/themes.ts function getAvailableThemes() { return availableThemes; } function getThemeContent(themeName) { const content = (() => { switch (themeName) { case "neutral": return neutral; case "stone": return stone; case "zinc": return zinc; case "gray": return gray; case "slate": return slate; default: return neutral; } })(); return content.trim(); } function getThemeDisplayName(themeName) { const names = { neutral: "Neutral (Default)", stone: "Stone", zinc: "Zinc", gray: "Gray", slate: "Slate" }; return names[themeName] || themeName; } // src/utils/package-manager.ts import { detect } from "@antfu/ni"; import { existsSync } from "fs"; import path from "path"; async function getPackageManager(cwd = process.cwd()) { const agent = await detect({ cwd }); if (agent === "yarn@berry") return "yarn"; if (agent && ["npm", "yarn", "pnpm", "bun"].includes(agent)) { return agent; } const lockFiles = [ { file: "bun.lock", manager: "bun" }, { file: "pnpm-lock.yaml", manager: "pnpm" }, { file: "yarn.lock", manager: "yarn" }, { file: "package-lock.json", manager: "npm" } ]; for (const { file, manager } of lockFiles) { if (existsSync(path.join(cwd, file))) { return manager; } } return "npm"; } async function getInstallCommand(packageManager, isDev = false) { switch (packageManager) { case "yarn": return isDev ? ["add", "-D"] : ["add"]; case "pnpm": return isDev ? ["add", "-D"] : ["add"]; case "bun": return isDev ? ["add", "-d"] : ["add"]; case "npm": default: return isDev ? ["install", "-D"] : ["install"]; } } async function installPackages(packages, cwd, isDev = false, legacyPeerDeps = false) { const { execa: execa2 } = await import("execa"); const packageManager = await getPackageManager(cwd); const installCmd = await getInstallCommand(packageManager, isDev); const args = [...installCmd, ...packages]; if (legacyPeerDeps && packageManager === "npm") { args.push("--legacy-peer-deps"); } await execa2(packageManager, args, { cwd, stdio: "inherit" }); } // src/utils/templates.ts var UTILS = { cn: `import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } `, "merge-classes": `import { twMerge } from 'tailwind-merge'; import { ClassValue, clsx } from 'clsx'; export type { ClassValue }; export function mergeClasses(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function transform(value: boolean | string): boolean { return typeof value === 'string' ? value === '' : value; } `, number: `function clamp(value: number, [min, max]: [number, number]): number { return Math.min(max, Math.max(min, value)); } function roundToStep(value: number, min: number, step: number): number { return Math.round((value - min) / step) * step + min; } function convertValueToPercentage(value: number, min: number, max: number): number { return ((value - min) / (max - min)) * 100; } export { clamp, roundToStep, convertValueToPercentage }; ` }; var POSTCSS_CONFIG = `{ "plugins": { "@tailwindcss/postcss": {} } } `; // src/utils/get-project-info.ts import path2 from "path"; import fs from "fs-extra"; import { z } from "zod"; var packageJsonSchema = z.object({ name: z.string(), version: z.string().optional(), dependencies: z.record(z.string()).optional(), devDependencies: z.record(z.string()).optional() }); async function getProjectInfo(cwd) { const packageJsonPath = path2.join(cwd, "package.json"); if (!await fs.pathExists(packageJsonPath)) { throw new Error("No package.json found. Please run this command in your project root."); } const packageJson = packageJsonSchema.parse(await fs.readJson(packageJsonPath)); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; const hasAngular = !!deps["@angular/core"]; const hasTypeScript = !!deps["typescript"]; const hasTailwind = !!deps["tailwindcss"]; const hasNx = !!deps["nx"] || !!deps["@nx/workspace"]; const angularVersion = deps["@angular/core"]?.replace(/[^0-9.]/g, "") || null; return { framework: hasAngular ? "angular" : "unknown", hasTypeScript, hasTailwind, hasNx, angularVersion }; } // src/utils/logger.ts import chalk from "chalk"; import ora from "ora"; var logger = { error(...args) { console.log(chalk.red(...args)); }, warn(...args) { console.log(chalk.yellow(...args)); }, info(...args) { console.log(chalk.cyan(...args)); }, success(...args) { console.log(chalk.green(...args)); }, break() { console.log(""); } }; function spinner(text) { return ora({ text, spinner: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] } }); } // src/commands/init.ts var init = new Command().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.", process.cwd()).action(async (options) => { const cwd = path3.resolve(options.cwd); if (!existsSync2(cwd)) { logger.error(`The path ${cwd} does not exist. Please try again.`); process.exit(1); } const projectInfo = await getProjectInfo(cwd); if (projectInfo.framework !== "angular") { logger.error("This project does not appear to be an Angular project."); logger.error("Please run this command in an Angular project."); process.exit(1); } logger.info("Initializing ZardUI..."); logger.break(); const config = await promptForConfig(cwd, projectInfo); if (!options.yes) { const { proceed } = await prompts({ type: "confirm", name: "proceed", message: "Write configuration to components.json?", initial: true }); if (!proceed) { process.exit(0); } } const configSpinner = spinner("Writing configuration...").start(); await fs2.writeFile(path3.resolve(cwd, "components.json"), JSON.stringify(config, null, 2), "utf8"); configSpinner.succeed(); const dependenciesSpinner = spinner("Installing dependencies...").start(); await installDependencies(cwd, config); dependenciesSpinner.succeed(); if (!projectInfo.hasTailwind) { const tailwindSpinner = spinner("Setting up Tailwind CSS...").start(); await setupTailwind(cwd, config); tailwindSpinner.succeed(); } const utilsSpinner = spinner("Creating utils...").start(); await createUtils(cwd, config); utilsSpinner.succeed(); const tsconfigSpinner = spinner("Updating tsconfig.json...").start(); await updateTsConfig(cwd, config); tsconfigSpinner.succeed(); logger.break(); logger.success("ZardUI has been initialized successfully!"); logger.break(); const packageManager = await getPackageManager(cwd); const runCommand = packageManager === "npm" ? "npx" : packageManager === "yarn" ? "yarn dlx" : `${packageManager}x`; logger.info("You can now add components using:"); logger.info(chalk2.bold(` ${runCommand} @ngzard/ui add [component]`)); logger.break(); }); async function promptForConfig(cwd, projectInfo) { const highlight = (text) => chalk2.cyan(text); const options = await prompts([ { type: "select", name: "theme", message: `Choose a ${highlight("theme")} for your components:`, choices: getAvailableThemes().map((theme) => ({ title: getThemeDisplayName(theme), value: theme })), initial: 0 // neutral as default }, { type: "text", name: "tailwindCss", message: `Where is your ${highlight("global CSS")} file?`, initial: projectInfo.hasNx ? "apps/[app]/src/styles.css" : "src/styles.css" }, { type: "text", name: "components", message: `Configure the import alias for ${highlight("components")}:`, initial: projectInfo.hasNx ? "libs/ui/src/lib/components" : "src/app/shared/components" }, { type: "text", name: "utils", message: `Configure the import alias for ${highlight("utils")}:`, initial: projectInfo.hasNx ? "libs/ui/src/lib/utils" : "src/app/shared/utils" } ]); const cssPath = path3.join(cwd, options.tailwindCss); if (!existsSync2(cssPath)) { logger.error(`CSS file not found at: ${options.tailwindCss}`); logger.error("Please ensure your CSS file exists before continuing."); process.exit(1); } const existingContent = await fs2.readFile(cssPath, "utf8"); let shouldOverwrite = false; if (existingContent.trim().length > 0) { const { overwrite } = await prompts({ type: "confirm", name: "overwrite", message: `Your CSS file already has content. This will overwrite everything with ZardUI theme configuration. Continue?`, initial: false }); if (!overwrite) { logger.info("Installation cancelled."); process.exit(0); } shouldOverwrite = true; } const config = configSchema.parse({ style: "css", // Fixed to CSS for TailwindV4 tailwind: { css: options.tailwindCss, baseColor: options.theme, cssVariables: true // Always true for ZardUI theme }, aliases: { components: options.components, utils: options.utils }, theme: options.theme // Store selected theme }); return config; } var configSchema = z2.object({ style: z2.enum(["css"]), // Only CSS for TailwindV4 tailwind: z2.object({ css: z2.string(), baseColor: z2.string(), cssVariables: z2.boolean() }), aliases: z2.object({ components: z2.string(), utils: z2.string() }), theme: z2.string().optional() // Selected theme }); async function installDependencies(cwd, config) { const projectInfo = await getProjectInfo(cwd); let cdkVersion = "@angular/cdk"; if (projectInfo.angularVersion) { const majorVersion = parseInt(projectInfo.angularVersion.split(".")[0]); if (majorVersion === 19) { cdkVersion = "@angular/cdk@^19.0.0"; } else if (majorVersion === 18) { cdkVersion = "@angular/cdk@^18.0.0"; } else if (majorVersion === 17) { cdkVersion = "@angular/cdk@^17.0.0"; } } const deps = [cdkVersion, "class-variance-authority", "clsx", "tailwind-merge", "lucide-static"]; const devDeps = ["tailwindcss", "@tailwindcss/postcss", "postcss", "tailwindcss-animate"]; try { await installPackages(deps, cwd, false); } catch (error) { logger.warn("Installation failed, retrying with --legacy-peer-deps..."); await installPackages(deps, cwd, false, true); } try { await installPackages(devDeps, cwd, true); } catch (error) { await installPackages(devDeps, cwd, true, true); } } async function setupTailwind(cwd, config) { const postcssConfigPath = path3.join(cwd, ".postcssrc.json"); if (!existsSync2(postcssConfigPath)) { await fs2.writeFile(postcssConfigPath, POSTCSS_CONFIG, "utf8"); } else { const existingConfig = await fs2.readFile(postcssConfigPath, "utf8"); if (!existingConfig.includes("@tailwindcss/postcss")) { logger.info("Updating existing .postcssrc.json for Tailwind CSS v4"); await fs2.writeFile(postcssConfigPath, POSTCSS_CONFIG, "utf8"); } } const stylesPath = path3.join(cwd, config.tailwind.css); const selectedTheme = config.theme || "neutral"; const themeContent = getThemeContent(selectedTheme); await fs2.writeFile(stylesPath, themeContent, "utf8"); logger.info(`Applied ${getThemeDisplayName(selectedTheme)} theme configuration to your CSS file`); } async function createUtils(cwd, config) { const utilsPath = path3.join(cwd, config.aliases.utils); if (!existsSync2(utilsPath)) { await fs2.mkdir(utilsPath, { recursive: true }); } for (const [fileName, content] of Object.entries(UTILS)) { const filePath = path3.join(utilsPath, `${fileName}.ts`); if (!existsSync2(filePath)) { await fs2.writeFile(filePath, content.trim(), "utf8"); } } } async function updateTsConfig(cwd, config) { const tsconfigPath = path3.join(cwd, "tsconfig.json"); if (!existsSync2(tsconfigPath)) { logger.warn("tsconfig.json not found, skipping path configuration"); return; } try { const tsconfigContent = await fs2.readFile(tsconfigPath, "utf8"); const tsconfig = commentJson.parse(tsconfigContent); if (!tsconfig.compilerOptions) { tsconfig.compilerOptions = {}; } if (!tsconfig.compilerOptions.baseUrl) { tsconfig.compilerOptions.baseUrl = "./"; } if (!tsconfig.compilerOptions.paths) { tsconfig.compilerOptions.paths = {}; } const pathMappings = { "@shared/*": ["src/app/shared/*"] }; tsconfig.compilerOptions.paths = { ...tsconfig.compilerOptions.paths, ...pathMappings }; const updatedContent = commentJson.stringify(tsconfig, null, 2); await fs2.writeFile(tsconfigPath, updatedContent, "utf8"); } catch (error) { logger.warn("Failed to update tsconfig.json paths"); logger.error(error); } } // src/commands/add.ts import { existsSync as existsSync3, promises as fs4 } from "fs"; import { Command as Command2 } from "commander"; import prompts2 from "prompts"; import path5 from "path"; // src/utils/registry.ts var registry = [ { name: "core", files: [ { name: "directives/string-template-outlet/string-template-outlet.directive.ts", content: "" } ] }, { name: "button", files: [ { name: "button.component.ts", content: "" }, { name: "button.variants.ts", content: "" } ] }, { name: "card", registryDependencies: ["core"], files: [ { name: "card.component.ts", content: "" }, { name: "card.variants.ts", content: "" } ] }, { name: "badge", files: [ { name: "badge.component.ts", content: "" }, { name: "badge.variants.ts", content: "" } ] }, { name: "alert", files: [ { name: "alert.component.ts", content: "" }, { name: "alert.variants.ts", content: "" } ] }, { name: "avatar", files: [ { name: "avatar.component.ts", content: "" }, { name: "avatar.variants.ts", content: "" } ] }, { name: "checkbox", files: [ { name: "checkbox.component.ts", content: "" }, { name: "checkbox.variants.ts", content: "" } ] }, { name: "dialog", registryDependencies: ["button"], files: [ { name: "dialog.component.ts", content: "" }, { name: "dialog.component.html", content: "" }, { name: "dialog.service.ts", content: "" }, { name: "dialog-ref.ts", content: "" }, { name: "dialog.variants.ts", content: "" } ] }, { name: "dropdown", files: [ { name: "dropdown.component.ts", content: "" }, { name: "dropdown-item.component.ts", content: "" }, { name: "dropdown-label.component.ts", content: "" }, { name: "dropdown-menu-content.component.ts", content: "" }, { name: "dropdown-shortcut.component.ts", content: "" }, { name: "dropdown-trigger.directive.ts", content: "" }, { name: "dropdown.module.ts", content: "" }, { name: "dropdown.service.ts", content: "" }, { name: "dropdown.variants.ts", content: "" } ] }, { name: "input", files: [ { name: "input.directive.ts", content: "" }, { name: "input.variants.ts", content: "" } ] }, { name: "select", files: [ { name: "select.component.ts", content: "" }, { name: "select-item.component.ts", content: "" }, { name: "select.variants.ts", content: "" } ] }, { name: "switch", files: [ { name: "switch.component.ts", content: "" }, { name: "switch.variants.ts", content: "" } ] }, { name: "tabs", files: [ { name: "tabs.component.ts", content: "" }, { name: "tabs.variants.ts", content: "" } ] }, { name: "toggle", files: [ { name: "toggle.component.ts", content: "" }, { name: "toggle.variants.ts", content: "" } ] }, { name: "tooltip", files: [ { name: "tooltip.ts", content: "" }, { name: "tooltip-positions.ts", content: "" }, { name: "tooltip.variants.ts", content: "" } ] }, { name: "accordion", files: [ { name: "accordion.component.ts", content: "" }, { name: "accordion-item.component.ts", content: "" } ] }, { name: "breadcrumb", files: [ { name: "breadcrumb.component.ts", content: "" }, { name: "breadcrumb.module.ts", content: "" }, { name: "breadcrumb.variants.ts", content: "" } ] }, { name: "divider", files: [ { name: "divider.component.ts", content: "" }, { name: "divider.variants.ts", content: "" } ] }, { name: "loader", files: [ { name: "loader.component.ts", content: "" }, { name: "loader.variants.ts", content: "" } ] }, { name: "progress-bar", files: [ { name: "progress-bar.component.ts", content: "" }, { name: "progress-bar.variants.ts", content: "" } ] }, { name: "radio", files: [ { name: "radio.component.ts", content: "" }, { name: "radio.variants.ts", content: "" } ] }, { name: "slider", files: [ { name: "slider.component.ts", content: "" }, { name: "slider.variants.ts", content: "" } ] }, { name: "alert-dialog", registryDependencies: ["button"], files: [ { name: "alert-dialog.component.ts", content: "" }, { name: "alert-dialog.component.html", content: "" }, { name: "alert-dialog.service.ts", content: "" }, { name: "alert-dialog-ref.ts", content: "" }, { name: "alert-dialog.variants.ts", content: "" } ] }, { name: "calendar", registryDependencies: ["button", "select"], files: [ { name: "calendar.component.ts", content: "" }, { name: "calendar.variants.ts", content: "" } ] }, { name: "combobox", registryDependencies: ["input", "popover"], files: [ { name: "combobox.component.ts", content: "" }, { name: "combobox.variants.ts", content: "" } ] }, { name: "command", files: [ { name: "command.component.ts", content: "" }, { name: "command-input.component.ts", content: "" }, { name: "command-list.component.ts", content: "" }, { name: "command-empty.component.ts", content: "" }, { name: "command-option.component.ts", content: "" }, { name: "command-option-group.component.ts", content: "" }, { name: "command-divider.component.ts", content: "" }, { name: "command-json.component.ts", content: "" }, { name: "command.module.ts", content: "" }, { name: "command.variants.ts", content: "" } ] }, { name: "date-picker", registryDependencies: ["calendar", "input"], files: [ { name: "date-picker.component.ts", content: "" }, { name: "date-picker.variants.ts", content: "" } ] }, { name: "form", files: [ { name: "form.component.ts", content: "" }, { name: "form.module.ts", content: "" }, { name: "form.variants.ts", content: "" } ] }, { name: "pagination", registryDependencies: ["button"], files: [ { name: "pagination.component.ts", content: "" }, { name: "pagination.module.ts", content: "" }, { name: "pagination.variants.ts", content: "" } ] }, { name: "popover", files: [ { name: "popover.component.ts", content: "" }, { name: "popover.variants.ts", content: "" } ] }, { name: "resizable", files: [ { name: "resizable.component.ts", content: "" }, { name: "resizable-panel.component.ts", content: "" }, { name: "resizable-handle.component.ts", content: "" }, { name: "resizable.variants.ts", content: "" } ] }, { name: "segmented", files: [ { name: "segmented.component.ts", content: "" }, { name: "segmented.variants.ts", content: "" } ] }, { name: "skeleton", files: [ { name: "skeleton.component.ts", content: "" }, { name: "skeleton.variants.ts", content: "" } ] }, { name: "table", files: [ { name: "table.component.ts", content: "" }, { name: "table.module.ts", content: "" }, { name: "table.variants.ts", content: "" } ] }, { name: "toast", dependencies: ["ngx-sonner"], files: [ { name: "toast.component.ts", content: "" }, { name: "toast.variants.ts", content: "" } ] }, { name: "toggle-group", registryDependencies: ["toggle"], files: [ { name: "toggle-group.component.ts", content: "" }, { name: "toggle-group.variants.ts", content: "" } ] }, { name: "menu", files: [ { name: "menu.directive.ts", content: "" }, { name: "menu.variants.ts", content: "" }, { name: "menu-content.directive.ts", content: "" }, { name: "menu-item.directive.ts", content: "" }, { name: "menu-manager.service.ts", content: "" }, { name: "menu.module.ts", content: "" } ] } ]; function getRegistryComponent(name) { return registry.find((component) => component.name === name); } function getAllComponentNames() { return registry.map((component) => component.name); } // src/utils/config.ts import fs3 from "fs-extra"; import path4 from "path"; import { z as z3 } from "zod"; var configSchema2 = z3.object({ $schema: z3.string().optional(), style: z3.enum(["css"]).default("css"), // Only CSS for TailwindV4 tailwind: z3.object({ css: z3.string().default("src/styles.css"), baseColor: z3.string().default("slate"), cssVariables: z3.boolean().default(true) }).default({}), aliases: z3.object({ components: z3.string().default("src/app/shared/components"), utils: z3.string().default("src/app/shared/utils") }).default({}), theme: z3.string().optional() // Selected theme }); async function getConfig(cwd) { const configPath = path4.resolve(cwd, "components.json"); if (!await fs3.pathExists(configPath)) { return null; } try { const configJson = await fs3.readJson(configPath); return configSchema2.parse(configJson); } catch (error) { logger.error("Invalid configuration file"); throw error; } } async function resolveConfigPaths(cwd, config) { return { ...config, resolvedPaths: { tailwindCss: path4.resolve(cwd, config.tailwind.css), components: path4.resolve(cwd, config.aliases.components), utils: path4.resolve(cwd, config.aliases.utils) } }; } // src/utils/fetch-component.ts import { execa } from "execa"; var GITHUB_API = "https://api.github.com/repos/zard-ui/zardui/contents"; var GITHUB_RAW = "https://raw.githubusercontent.com/zard-ui/zardui/master"; async function fetchFromGitHubAPI(filePath) { try { const apiUrl = `${GITHUB_API}/${filePath}`; const { stdout } = await execa("curl", ["-s", "-H", "Accept: application/vnd.github.v3+json", "-H", "User-Agent: zard-cli", apiUrl]); const response = JSON.parse(stdout); if (response.message) { throw new Error(response.message); } if (response.content && response.encoding === "base64") { return Buffer.from(response.content, "base64").toString("utf8"); } throw new Error("Invalid response from GitHub API"); } catch (error) { await new Promise((resolve) => setTimeout(resolve, 3e3)); const rawUrl = `${GITHUB_RAW}/${filePath}`; const { stdout } = await execa("curl", ["-s", "-H", "User-Agent: zard-cli", rawUrl]); if (!stdout || stdout.includes("429: Too Many Requests") || stdout.includes("404: Not Found")) { throw new Error(`Failed to fetch from both API and raw URL: ${filePath}`); } return stdout; } } async function fetchComponentFromGithub(componentName, fileName, config) { try { const filePath = `libs/zard/src/lib/components/${componentName}/${fileName}`; const randomDelay = Math.random() * 2e3 + 1e3; await new Promise((resolve) => setTimeout(resolve, randomDelay)); const content = await fetchFromGitHubAPI(filePath); return transformContent(content, config); } catch (error) { throw new Error(`Failed to fetch component ${componentName}/${fileName}: ${error}`); } } function transformContent(content, config) { let transformed = content; transformed = transformed.replace(/from ['"]\.\.\/\.\.\/shared\/utils\/utils['"]/g, `from '@shared/utils/merge-classes'`); transformed = transformed.replace(/from ['"]\.\.\/\.\.\/shared\/utils\/number['"]/g, `from '@shared/utils/number'`); const componentImportRegex = /from ['"]\.\.\/([\w-]+)['"]/g; transformed = transformed.replace(componentImportRegex, `from '@shared/components/$1'`); transformed = transformed.replace(/import \{ ClassValue \} from ['"]class-variance-authority\/dist\/types['"]/g, `import { ClassValue } from 'clsx'`); transformed = transformed.replace(/import \{ ClassValue \} from ['"]class-variance-authority['"]/g, `import { ClassValue } from 'clsx'`); return transformed; } // src/commands/add.ts var add = new Command2().name("add").description("add a component to your project").argument("[components...]", "the components to add").option("-y, --yes", "skip confirmation prompt.", false).option("-o, --overwrite", "overwrite existing files.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-a, --all", "add all available components", false).option("-p, --path <path>", "the path to add the component to.").action(async (components, options) => { const cwd = path5.resolve(options.cwd); if (!existsSync3(cwd)) { logger.error(`The path ${cwd} does not exist. Please try again.`); process.exit(1); } const config = await getConfig(cwd); if (!config) { logger.error("Configuration not found. Please run `zard init` first."); process.exit(1); } const projectInfo = await getProjectInfo(cwd); if (projectInfo.framework !== "angular") { logger.error("This project does not appear to be an Angular project."); process.exit(1); } const resolvedConfig = await resolveConfigPaths(cwd, config); let selectedComponents = components; if (options.all) { selectedComponents = getAllComponentNames(); } else if (!components?.length) { const { components: selected } = await prompts2({ type: "multiselect", name: "components", message: "Which components would you like to add?", hint: "Space to select. A to toggle all. Enter to submit.", choices: getAllComponentNames().map((name) => ({ title: name, value: name })) }); selectedComponents = selected; } if (!selectedComponents?.length) { logger.warn("No components selected. Exiting."); process.exit(0); } const registryComponents = selectedComponents.map((name) => getRegistryComponent(name)).filter(Boolean); if (!registryComponents.length) { logger.error("Selected components not found in registry."); process.exit(1); } const dependenciesToInstall = /* @__PURE__ */ new Set(); const componentsToInstall = []; for (const component of registryComponents) { componentsToInstall.push(component); component.dependencies?.forEach((dep) => dependenciesToInstall.add(dep)); if (component.registryDependencies && !options.all) { for (const dep of component.registryDependencies) { const depComponent = getRegistryComponent(dep); if (depComponent && !componentsToInstall.find((c) => c.name === dep)) { const depTargetDir = options.path ? path5.resolve(cwd, options.path, dep) : path5.resolve(resolvedConfig.resolvedPaths.components, dep); if (!existsSync3(depTargetDir)) { componentsToInstall.push(depComponent); depComponent.dependencies?.forEach((d) => dependenciesToInstall.add(d)); } } } } } if (!options.yes) { const { proceed } = await prompts2({ type: "confirm", name: "proceed", message: `Ready to install ${componentsToInstall.length} component(s) and ${dependenciesToInstall.size} dependencies. Proceed?`, initial: true }); if (!proceed) { process.exit(0); } } if (dependenciesToInstall.size > 0) { const depsSpinner = spinner("Installing dependencies...").start(); await installPackages(Array.from(dependenciesToInstall), cwd, false); depsSpinner.succeed(); } for (const component of componentsToInstall) { const componentSpinner = spinner(`Installing ${component.name}...`).start(); try { const targetDir = options.path ? path5.resolve(cwd, options.path, component.name) : path5.resolve(resolvedConfig.resolvedPaths.components, component.name); await installComponent(component, targetDir, resolvedConfig, options.overwrite); componentSpinner.succeed(`Added ${component.name}`); } catch (error) { componentSpinner.fail(`Failed to install ${component.name}`); logger.error(error); } } logger.break(); logger.success("Done!"); }); async function installComponent(component, targetDir, config, overwrite) { if (!overwrite && existsSync3(targetDir)) { throw new Error(`Component ${component.name} already exists. Use --overwrite to overwrite.`); } await fs4.mkdir(targetDir, { recursive: true }); for (const file of component.files) { const content = await fetchComponentFromGithub(component.name, file.name, config); const filePath = path5.join(targetDir, file.name); const fileDir = path5.dirname(filePath); await fs4.mkdir(fileDir, { recursive: true }); await fs4.writeFile(filePath, content, "utf8"); } } // src/constants/app.constants.ts var APP_VERSION = "1.0.0-alpha.3"; // src/index.ts async function main() { const program = new Command3().name("ngzard").description("add beautiful Angular components to your apps").version(APP_VERSION, "-v, --version", "display the version number"); program.addCommand(init).addCommand(add); program.parse(); } main(); //# sourceMappingURL=index.js.map