@ngzard/ui
Version:
An alternative to shadcn/ui for angular
1,701 lines (1,661 loc) • 48.8 kB
JavaScript
// 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