@moontra/moonui-cli
Version:
CLI tool for MoonUI component library
1,579 lines (1,539 loc) • 118 kB
JavaScript
#!/usr/bin/env node
import {
AuthService,
DeviceService,
__require,
__toESM,
require_package
} from "./chunk-I7KX22G5.mjs";
// src/index.ts
import { Command as Command3 } from "commander";
import chalk10 from "chalk";
import figlet from "figlet";
// src/commands/init.ts
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import ora from "ora";
import { execSync } from "child_process";
async function initCommand(options = {}) {
const spinner = ora("Initializing MoonUI in your project...").start();
try {
const cwd = process.cwd();
const packageJsonPath = path.join(cwd, "package.json");
if (!fs.existsSync(packageJsonPath)) {
spinner.fail(chalk.red("No package.json found in the current directory."));
console.log(chalk.yellow("Please run this command in a JavaScript/TypeScript project root."));
return;
}
const configFileName = "moonui.config.js";
const configPath = path.join(cwd, configFileName);
if (fs.existsSync(configPath) && !options.force) {
spinner.fail(chalk.red(`${configFileName} already exists.`));
console.log(chalk.yellow(`Use --force to overwrite the existing configuration.`));
return;
}
const configContent = `module.exports = {
// MoonUI Theme Configuration
theme: {
// Option 1: Use a preset theme
preset: 'default', // 'default' | 'corporate' | 'creative' | 'nature' | 'minimal' | 'ocean'
// Option 2: Define custom theme (uncomment to use)
// custom: {
// colors: {
// background: "0 0% 100%",
// foreground: "222.2 84% 4.9%",
// primary: "222.2 47.4% 11.2%",
// "primary-foreground": "210 40% 98%",
// secondary: "210 40% 96.1%",
// "secondary-foreground": "222.2 47.4% 11.2%",
// accent: "210 40% 96.1%",
// "accent-foreground": "222.2 47.4% 11.2%",
// destructive: "0 84.2% 60.2%",
// "destructive-foreground": "210 40% 98%",
// muted: "210 40% 96.1%",
// "muted-foreground": "215.4 16.3% 46.9%",
// border: "214.3 31.8% 91.4%",
// input: "214.3 31.8% 91.4%",
// ring: "222.2 84% 4.9%",
// card: "0 0% 100%",
// "card-foreground": "222.2 84% 4.9%",
// popover: "0 0% 100%",
// "popover-foreground": "222.2 84% 4.9%",
// },
// darkMode: {
// background: "222.2 84% 4.9%",
// foreground: "210 40% 98%",
// primary: "210 40% 98%",
// "primary-foreground": "222.2 47.4% 11.2%",
// // ... other dark mode colors
// },
// radius: 0.5, // Border radius in rem
// },
},
// Tailwind CSS configuration
tailwind: {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
darkMode: 'class', // or 'media'
},
// Component default settings
components: {
// Configure default props and variants for components
Button: {
defaultVariant: 'default',
defaultSize: 'md',
},
Card: {
defaultVariant: 'default',
},
// Add more component defaults as needed
},
// Path settings
paths: {
// Where to output the components
components: './src/components/ui',
// Where to output the utilities
utils: './src/lib',
// Where to output the generated theme CSS
styles: './src/styles',
},
// Build settings
build: {
// Automatically generate theme CSS on build
generateThemeCSS: true,
// CSS output file name
themeCSSFile: 'moonui-theme.css',
},
}`;
fs.writeFileSync(configPath, configContent);
const componentsDir = path.join(cwd, "src", "components", "ui");
const libDir = path.join(cwd, "src", "lib");
fs.ensureDirSync(componentsDir);
fs.ensureDirSync(libDir);
const utilsPath = path.join(libDir, "utils.ts");
const utilsContent = `import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* A utility function to merge Tailwind CSS classes conditionally
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}`;
fs.writeFileSync(utilsPath, utilsContent);
spinner.text = "Installing required dependencies...";
const dependencies = [
"@moontra/moonui@latest",
// Her zaman en son versiyon
"clsx",
"tailwind-merge",
"class-variance-authority"
];
const devDependencies = [
"tailwindcss",
"postcss",
"autoprefixer"
];
try {
const hasYarnLock = fs.existsSync(path.join(cwd, "yarn.lock"));
const packageManager = hasYarnLock ? "yarn" : "npm";
if (packageManager === "yarn") {
execSync(`yarn add ${dependencies.join(" ")}`, { stdio: "ignore" });
execSync(`yarn add -D ${devDependencies.join(" ")}`, { stdio: "ignore" });
} else {
execSync(`npm install --save ${dependencies.join(" ")}`, { stdio: "ignore" });
execSync(`npm install --save-dev ${devDependencies.join(" ")}`, { stdio: "ignore" });
}
} catch (error) {
spinner.warn(chalk.yellow("Failed to automatically install dependencies."));
console.log(chalk.yellow("Please install the following dependencies manually:"));
console.log(chalk.cyan(`
Dependencies:
- ${dependencies.join("\n- ")}`));
console.log(chalk.cyan(`
Dev Dependencies:
- ${devDependencies.join("\n- ")}`));
}
spinner.succeed(chalk.green("MoonUI initialized successfully!"));
console.log("\nNext steps:");
console.log(chalk.cyan("1. Add components: ") + "moonui add button card input");
console.log(chalk.cyan("2. Configure your theme: ") + "Edit moonui.config.js");
console.log(chalk.cyan("3. Check documentation: ") + "https://moonui.dev/docs");
} catch (error) {
spinner.fail(chalk.red("Failed to initialize MoonUI."));
console.error(error);
}
}
// src/commands/add.ts
import fs6 from "fs-extra";
import path6 from "path";
import chalk4 from "chalk";
import ora2 from "ora";
import prompts2 from "prompts";
import os from "os";
// src/utils/components.ts
async function getComponentCategories() {
return [
{
name: "Form",
description: "Components for user input and forms",
components: [
{
id: "button",
name: "Button",
description: "A button component with various styles, sizes, and states",
dependencies: []
},
{
id: "input",
name: "Input",
description: "A text input component with validation and state handling",
dependencies: []
},
{
id: "checkbox",
name: "Checkbox",
description: "A checkbox input component for boolean selections",
dependencies: []
},
{
id: "switch",
name: "Switch",
description: "A toggle switch component for boolean state",
dependencies: []
},
{
id: "select",
name: "Select",
description: "A dropdown select component for choosing from options",
dependencies: []
},
{
id: "radio-group",
name: "RadioGroup",
description: "A group of radio buttons for selecting one option",
dependencies: ["radio"]
},
{
id: "textarea",
name: "Textarea",
description: "A multi-line text input area",
dependencies: []
},
{
id: "label",
name: "Label",
description: "A form label component",
dependencies: []
}
]
},
{
name: "Layout",
description: "Components for page layout and structure",
components: [
{
id: "card",
name: "Card",
description: "A container component with header, content, and footer sections",
dependencies: []
},
{
id: "dialog",
name: "Dialog",
description: "A modal dialog component for displaying content over the page",
dependencies: []
},
{
id: "accordion",
name: "Accordion",
description: "A collapsible content panel",
dependencies: []
},
{
id: "sheet",
name: "Sheet",
description: "A slide-out panel from any edge of the screen",
dependencies: []
},
{
id: "popover",
name: "Popover",
description: "A small overlay that appears on user interaction",
dependencies: []
},
{
id: "hover-card",
name: "HoverCard",
description: "A card that appears when hovering over a trigger element",
dependencies: []
},
{
id: "separator",
name: "Separator",
description: "A horizontal or vertical separator line",
dependencies: []
},
{
id: "aspect-ratio",
name: "AspectRatio",
description: "A component to maintain a specific aspect ratio",
dependencies: []
}
]
},
{
name: "Navigation",
description: "Components for navigation and menus",
components: [
{
id: "tabs",
name: "Tabs",
description: "A tabbed interface for switching between content sections",
dependencies: []
},
{
id: "breadcrumb",
name: "Breadcrumb",
description: "A breadcrumb navigation component",
dependencies: []
},
{
id: "dropdown-menu",
name: "DropdownMenu",
description: "A dropdown menu component for navigation or actions",
dependencies: []
},
{
id: "navigation-menu",
name: "NavigationMenu",
description: "A responsive navigation menu component",
dependencies: []
},
{
id: "menubar",
name: "Menubar",
description: "A horizontal menu bar component",
dependencies: []
},
{
id: "pagination",
name: "Pagination",
description: "A component for paginating through content",
dependencies: []
}
]
},
{
name: "Data Display",
description: "Components for displaying data",
components: [
{
id: "badge",
name: "Badge",
description: "A badge component for labels and counts",
dependencies: []
},
{
id: "table",
name: "Table",
description: "A table component for displaying data in rows and columns",
dependencies: []
},
{
id: "avatar",
name: "Avatar",
description: "An avatar component for user or entity representation",
dependencies: []
},
{
id: "progress",
name: "Progress",
description: "A progress indicator component",
dependencies: []
},
{
id: "skeleton",
name: "Skeleton",
description: "A placeholder loading state component",
dependencies: []
},
{
id: "alert",
name: "Alert",
description: "An alert component for important messages",
dependencies: []
},
{
id: "toast",
name: "Toast",
description: "A toast notification component",
dependencies: []
},
{
id: "tooltip",
name: "Tooltip",
description: "A tooltip component for displaying information on hover",
dependencies: []
}
]
},
{
name: "Premium",
description: "Premium components available with MoonUI Pro",
components: [
{
id: "data-table",
name: "DataTable",
description: "Advanced data table with sorting, filtering, and pagination",
dependencies: ["@tanstack/react-table"]
},
{
id: "advanced-chart",
name: "AdvancedChart",
description: "Interactive charts with multiple types and customization options",
dependencies: ["recharts"]
},
{
id: "file-upload",
name: "FileUpload",
description: "Advanced file upload component with drag-and-drop support",
dependencies: []
},
{
id: "rich-text-editor",
name: "RichTextEditor",
description: "Rich text editor with formatting toolbar and customization",
dependencies: []
},
{
id: "calendar",
name: "Calendar",
description: "Interactive calendar component with event management",
dependencies: []
},
{
id: "kanban",
name: "Kanban",
description: "Drag-and-drop kanban board for project management",
dependencies: []
},
{
id: "timeline",
name: "Timeline",
description: "Timeline component for displaying chronological events",
dependencies: []
},
{
id: "dashboard",
name: "Dashboard",
description: "Dashboard component with metrics and widgets",
dependencies: []
}
]
}
];
}
// src/utils/config.ts
import fs2 from "fs-extra";
import path2 from "path";
async function getConfig(projectPath) {
try {
const configPath = path2.join(projectPath, "moonui.config.js");
if (!fs2.existsSync(configPath)) {
return null;
}
return {
tailwind: {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: "class"
},
theme: {
extend: {
colors: {
primary: "hsl(var(--primary))",
secondary: "hsl(var(--secondary))"
}
}
},
components: {
Button: {
defaultVariant: "default",
defaultSize: "md"
}
},
paths: {
components: "./src/components/ui",
utils: "./src/lib"
}
};
} catch (error) {
console.error("Error reading MoonUI config:", error);
return null;
}
}
// src/utils/license.ts
var PRO_COMPONENTS = [
{
id: "data-table",
name: "Data Table",
category: "data",
tier: "pro",
description: "Advanced data table with sorting, filtering, pagination, and export",
dependencies: ["@tanstack/react-table"],
tags: ["table", "data", "sorting", "filtering", "pagination"]
},
{
id: "advanced-chart",
name: "Advanced Chart",
category: "visualization",
tier: "pro",
description: "Interactive charts with multiple types and customization options",
dependencies: ["recharts"],
tags: ["chart", "visualization", "analytics", "graph"]
},
{
id: "dashboard",
name: "Dashboard",
category: "layout",
tier: "pro",
description: "Complete dashboard layout with widgets and responsive design",
dependencies: ["framer-motion"],
tags: ["dashboard", "layout", "widgets", "analytics"]
},
{
id: "file-upload",
name: "File Upload",
category: "input",
tier: "pro",
description: "Advanced file upload with drag-and-drop, preview, and progress",
dependencies: ["react-dropzone"],
tags: ["upload", "file", "drag-drop", "preview"]
},
{
id: "rich-text-editor",
name: "Rich Text Editor",
category: "input",
tier: "pro",
description: "WYSIWYG rich text editor with formatting and media support",
dependencies: ["@tiptap/react"],
tags: ["editor", "text", "wysiwyg", "formatting"]
},
{
id: "calendar",
name: "Calendar",
category: "input",
tier: "pro",
description: "Interactive calendar with event management and date selection",
dependencies: ["date-fns"],
tags: ["calendar", "date", "events", "scheduling"]
},
{
id: "kanban",
name: "Kanban Board",
category: "layout",
tier: "pro",
description: "Drag-and-drop kanban board for project management",
dependencies: ["@dnd-kit/core"],
tags: ["kanban", "board", "drag-drop", "project"]
},
{
id: "timeline",
name: "Timeline",
category: "visualization",
tier: "pro",
description: "Interactive timeline component for displaying chronological data",
dependencies: [],
tags: ["timeline", "chronological", "events", "history"]
}
];
function isProComponent(componentId) {
return PRO_COMPONENTS.some((comp) => comp.id === componentId);
}
// src/utils/registry-client.ts
import fs4 from "fs-extra";
import path4 from "path";
import chalk2 from "chalk";
// src/utils/component-source-reader.ts
import fs3 from "fs-extra";
import path3 from "path";
function transformImportPaths(content, componentName) {
let transformed = content.replace(/from ["']\.\.\/\.\.\/lib\/utils["']/g, 'from "@/lib/utils"').replace(/from ["']\.\.\/ui\/([^"']+)["']/g, 'from "@/components/ui/$1"').replace(/from ["']\.\/([^"']+)["']/g, (match, component) => {
if (component.endsWith(".css") || component.endsWith(".tsx") || component.endsWith(".ts")) {
return match;
}
return `from "@/components/ui/${component}"`;
});
transformed = transformed.replace(/from ["'](@\/[^"']+)\.tsx["']/g, 'from "$1"');
return transformed;
}
async function readComponentFromPackage(componentName, isProComponent2 = false) {
try {
const packageName = isProComponent2 ? "@moontra/moonui-pro" : "@moontra/moonui";
let componentPath;
let content = null;
try {
const cwd = process.cwd();
const localPackagePath = isProComponent2 ? path3.join(cwd, "packages", "moonui-pro") : path3.join(cwd, "packages", "moonui");
let packageRoot;
if (fs3.existsSync(path3.join(localPackagePath, "package.json"))) {
packageRoot = localPackagePath;
console.log(`\u2713 Using local development package: ${packageRoot}`);
} else {
const packagePath = __require.resolve(packageName);
const packageDir = path3.dirname(packagePath);
packageRoot = packageDir;
while (!fs3.existsSync(path3.join(packageRoot, "package.json")) && packageRoot !== "/") {
packageRoot = path3.dirname(packageRoot);
}
console.log(`\u2713 Using NPM package: ${packageRoot}`);
}
const srcPath = path3.join(packageRoot, "src", "components", "ui", `${componentName}.tsx`);
const srcProPath = path3.join(packageRoot, "src", "components", componentName, "index.tsx");
if (!isProComponent2 && fs3.existsSync(srcPath)) {
componentPath = srcPath;
content = await fs3.readFile(srcPath, "utf8");
console.log(`\u2713 Read component from source: ${srcPath}`);
} else if (isProComponent2 && fs3.existsSync(srcProPath)) {
componentPath = srcProPath;
content = await fs3.readFile(srcProPath, "utf8");
console.log(`\u2713 Read pro component from source: ${srcProPath}`);
} else {
const distPath = path3.join(packageRoot, "dist", "components", "ui", `${componentName}.mjs`);
const distProPath = path3.join(packageRoot, "dist", "components", componentName, "index.mjs");
if (!isProComponent2 && fs3.existsSync(distPath)) {
console.log(`Warning: Reading from built file, source may be minified: ${distPath}`);
return null;
}
}
} catch (error) {
console.log(`Failed to resolve package: ${packageName}`, error);
return null;
}
if (!content) {
console.log(`Component source not found in package: ${componentName}`);
return null;
}
const transformedContent = transformImportPaths(content, componentName);
const dependencies = [];
const importRegex = /from ["']([^"']+)["']/g;
let match;
while ((match = importRegex.exec(transformedContent)) !== null) {
const importPath = match[1];
if (importPath.startsWith("@/components/ui/")) {
const componentName2 = importPath.replace("@/components/ui/", "");
dependencies.push(componentName2);
}
}
const componentData = {
id: componentName,
name: componentName.charAt(0).toUpperCase() + componentName.slice(1),
description: `${componentName} component from ${packageName}`,
dependencies: [...new Set(dependencies)],
// Remove duplicates
files: [{
name: `${componentName}.tsx`,
content: transformedContent
}],
category: isProComponent2 ? "pro" : "free"
};
return componentData;
} catch (error) {
console.log(`Failed to read component from package: ${componentName}`, error);
return null;
}
}
// src/utils/registry-client.ts
var fetch = (url, options) => import("node-fetch").then(({ default: fetch3 }) => fetch3(url, options));
var RegistryClient = class {
constructor() {
this.cache = /* @__PURE__ */ new Map();
this.baseUrl = "https://moonui.dev/api/registry";
}
/**
* Fetch component from registry
*/
async fetchComponent(componentId, license) {
const cacheKey = `${componentId}-${license?.user?.plan || "free"}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
try {
const isProComponent2 = this.isProComponent(componentId);
const isPattern = componentId.includes("/");
let endpoint;
let url;
if (isProComponent2 || isPattern) {
endpoint = `${this.baseUrl}/pro`;
const componentPath = isPattern ? componentId : `components/${componentId}`;
url = `${endpoint}?component=${encodeURIComponent(componentPath)}`;
} else {
endpoint = `${this.baseUrl}/free`;
url = `${endpoint}?component=${componentId}`;
}
const deviceId = DeviceService.getDeviceFingerprint();
const requestOptions = {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Device-ID": deviceId
}
};
if ((isProComponent2 || isPattern) && license) {
requestOptions.headers["Authorization"] = `Bearer ${license.accessToken}`;
}
const response = await fetch(url, requestOptions);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (response.status === 401 && errorData.error?.includes("revoked")) {
console.error(chalk2.red("\n\u274C Your device has been revoked"));
console.log(chalk2.yellow("Please login again to continue:"));
console.log(chalk2.cyan(" moonui login\n"));
const { AuthService: AuthService2 } = await import("./auth-service-26PGUZJL.mjs");
const authService = new AuthService2();
await authService.clearAuth();
process.exit(1);
} else if (response.status === 403) {
console.error(
chalk2.red(
errorData.error || "Pro subscription required"
)
);
console.log(
chalk2.yellow(
"This component requires a MoonUI Pro license."
)
);
console.log(
chalk2.cyan("Get Pro at: https://moonui.dev/pricing")
);
} else {
console.error(
chalk2.red(
`Failed to fetch component: ${errorData.error || response.statusText}`
)
);
}
return null;
}
const data = await response.json();
const componentData = {
id: componentId,
name: data.component.name,
description: data.component.description,
dependencies: data.dependencies || data.component.dependencies,
files: data.files,
npmInstall: data.npmInstall,
category: data.component.category
};
this.cache.set(cacheKey, componentData);
return componentData;
} catch (error) {
console.warn(
chalk2.yellow("Registry unavailable, trying NPM package")
);
const isProComponent2 = this.isProComponent(componentId);
const componentFromPackage = await readComponentFromPackage(componentId, isProComponent2);
if (componentFromPackage) {
console.log(chalk2.green(`\u2713 Found component in NPM package: ${componentId}`));
return componentFromPackage;
}
return this.getLocalTemplate(componentId);
}
}
/**
* Get local template as fallback
*/
async getLocalTemplate(componentId) {
try {
const templatePath = path4.join(
__dirname,
"..",
"templates",
`${componentId}.json`
);
if (await fs4.pathExists(templatePath)) {
const templateData = await fs4.readJson(templatePath);
return templateData;
}
return this.getHardcodedTemplate(componentId);
} catch (error) {
return null;
}
}
/**
* Hardcoded templates for development/fallback
*/
getHardcodedTemplate(componentId) {
const templates = {
button: {
id: "button",
name: "Button",
description: "A versatile button component",
dependencies: [],
files: [
{
name: "button.tsx",
content: `"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }`
}
]
},
card: {
id: "card",
name: "Card",
description: "A versatile card component",
dependencies: [],
files: [
{
name: "card.tsx",
content: `import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }`
}
]
}
};
return templates[componentId] || null;
}
/**
* Check if component is a pro component
*/
isProComponent(componentId) {
const proComponents = [
"data-table",
"advanced-chart",
"dashboard",
"file-upload",
"rich-text-editor",
"calendar",
"kanban",
"timeline",
"login-form",
"pricing-cards",
"checkout-form",
"user-profile",
"feature-sections",
"hero-sections",
"dashboard-layout",
"landing-page",
// Performance & Utility (Pro)
"virtual-list",
"memory-efficient-data",
"lazy-component",
"optimized-image",
"performance-monitor",
"performance-debugger",
"error-boundary",
"health-check",
// Interactive & Animation (Pro)
"github-stars",
"animated-button",
"magnetic-button",
"hover-card-3d",
"spotlight-card",
"floating-action-button",
// Gesture & Touch (Pro)
"swipeable-card",
"draggable-list",
"pinch-zoom",
"gesture-drawer",
// Enhanced Foundation (Pro)
"color-picker",
"advanced-forms"
];
return proComponents.includes(componentId) || componentId.startsWith("patterns/") || componentId.startsWith("blocks/") || componentId.startsWith("templates/");
}
/**
* List all available components
*/
async listComponents() {
try {
const response = await fetch(`${this.baseUrl}/list`);
if (!response.ok) {
throw new Error("Failed to fetch component list");
}
const data = await response.json();
return data.components;
} catch (error) {
return [
{
id: "button",
name: "Button",
description: "A versatile button component",
dependencies: [],
files: []
},
{
id: "card",
name: "Card",
description: "A versatile card component",
dependencies: [],
files: []
}
// ... more components
];
}
}
/**
* Clear cache
*/
clearCache() {
this.cache.clear();
}
};
var registryClient = new RegistryClient();
// src/utils/component-installer.ts
import fs5 from "fs-extra";
import path5 from "path";
import chalk3 from "chalk";
import prompts from "prompts";
// src/config/overlapping-components.ts
var OVERLAPPING_COMPONENTS = {
button: {
name: "button",
free: {
package: "@moontra/moonui",
exportName: "Button",
fileName: "button.tsx",
description: "Basic button with variants and sizes",
features: ["Variants", "Sizes", "Loading state", "Icons"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUIButtonPro",
fileName: "button-pro.tsx",
description: "Advanced button with animations and effects",
features: ["All free features", "Ripple effect", "Magnetic hover", "Particle effects", "3D transforms"]
}
},
card: {
name: "card",
free: {
package: "@moontra/moonui",
exportName: "Card",
fileName: "card.tsx",
description: "Basic card container",
features: ["Header", "Content", "Footer", "Basic styling"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUICardPro",
fileName: "card-pro.tsx",
description: "Interactive card with effects",
features: ["All free features", "3D tilt", "Glassmorphism", "Parallax", "Reveal animations"]
}
},
dialog: {
name: "dialog",
free: {
package: "@moontra/moonui",
exportName: "Dialog",
fileName: "dialog.tsx",
description: "Basic modal dialog",
features: ["Accessible", "Keyboard navigation", "Focus trap"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUIDialogPro",
fileName: "dialog-pro.tsx",
description: "Animated modal with effects",
features: ["All free features", "Spring animations", "Backdrop blur", "Custom transitions"]
}
},
badge: {
name: "badge",
free: {
package: "@moontra/moonui",
exportName: "Badge",
fileName: "badge.tsx",
description: "Static badge component",
features: ["Variants", "Sizes", "Colors"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUIBadgePro",
fileName: "badge-pro.tsx",
description: "Animated badge",
features: ["All free features", "Pulse", "Glow", "Shimmer effects"]
}
},
input: {
name: "input",
free: {
package: "@moontra/moonui",
exportName: "Input",
fileName: "input.tsx",
description: "Standard input field",
features: ["Validation", "Placeholder", "Disabled state"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUIInputPro",
fileName: "input-pro.tsx",
description: "Enhanced input with animations",
features: ["All free features", "Floating labels", "Focus glow", "Smooth borders"]
}
},
select: {
name: "select",
free: {
package: "@moontra/moonui",
exportName: "Select",
fileName: "select.tsx",
description: "Basic dropdown select",
features: ["Options", "Groups", "Keyboard navigation"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUISelectPro",
fileName: "select-pro.tsx",
description: "Advanced select with search",
features: ["All free features", "Search", "Virtualization", "Multi-select", "Async loading"]
}
},
tabs: {
name: "tabs",
free: {
package: "@moontra/moonui",
exportName: "Tabs",
fileName: "tabs.tsx",
description: "Basic tab navigation",
features: ["Tab panels", "Keyboard navigation", "ARIA"]
},
pro: {
package: "@moontra/moonui-pro",
exportName: "MoonUITabsPro",
fileName: "tabs-pro.tsx",
description: "Animated tabs",
features: ["All free features", "Animated indicators", "Gestures", "Smooth transitions"]
}
}
};
function hasOverlappingVersions(componentName) {
return componentName in OVERLAPPING_COMPONENTS;
}
var PRO_ONLY_COMPONENTS = [
"data-table-pro",
"calendar-pro",
"rich-text-editor",
"kanban",
"form-wizard",
"moonui-form-wizard",
"file-upload-pro",
"charts-pro",
"timeline-pro",
"moonui-credit-card-input",
"moonui-phone-number-input",
"moonui-otp-input",
"moonui-quiz-form"
];
var FREE_ONLY_COMPONENTS = [
"accordion",
"alert",
"aspect-ratio",
"avatar",
"breadcrumb",
"calendar",
// Basic calendar (not calendar-pro)
"checkbox",
"collapsible",
"command",
"context-menu",
"dropdown-menu",
"form",
"hover-card",
"label",
"menubar",
"navigation-menu",
"pagination",
"popover",
"progress",
"radio-group",
"scroll-area",
"separator",
"sheet",
"skeleton",
"slider",
"switch",
"table",
// Basic table (not data-table-pro)
"textarea",
"toast",
"toggle",
"toggle-group",
"tooltip"
];
// src/utils/component-installer.ts
var hasOverlappingVersions2 = hasOverlappingVersions;
var getOverlapping = getOverlapping;
async function determineComponentVariant(componentName, options, authService) {
if (PRO_ONLY_COMPONENTS.includes(componentName)) {
return "pro-only";
}
if (FREE_ONLY_COMPONENTS.includes(componentName)) {
return "free-only";
}
if (!hasOverlappingVersions(componentName)) {
if (componentName.includes("-pro") || componentName.startsWith("moonui-")) {
return "pro-only";
}
return "free-only";
}
const hasProLicense = await authService.checkProAccess();
if (options.variant) {
if (options.variant === "pro" && !hasProLicense) {
throw new Error(`Pro license required for ${componentName} pro variant`);
}
return options.variant;
}
if (!hasProLicense) {
console.log(chalk3.blue(`\u2139 Installing free version of ${componentName}`));
return "free";
}
const { variant } = await prompts({
type: "select",
name: "variant",
message: `Which version of ${chalk3.cyan(componentName)} would you like to install?`,
choices: [
{
title: "\u{1F193} Free Version",
description: "Basic features, standard functionality",
value: "free"
},
{
title: "\u{1F48E} Pro Version",
description: "Advanced features, animations, and effects",
value: "pro"
}
],
initial: 1
// Default to Pro since they have a license
});
return variant || "free";
}
function generateComponentFile(componentName, variant) {
const overlapping = getOverlapping(componentName);
if (variant === "free") {
return null;
}
if (variant === "pro" && overlapping) {
return {
fileName: overlapping.pro.fileName,
content: generateProComponentContent(overlapping.pro)
};
}
if (variant === "pro-only") {
return {
fileName: `${componentName}.tsx`,
content: generateProOnlyComponentContent(componentName)
};
}
return null;
}
function generateProComponentContent(variant) {
const componentName = variant.exportName.replace("MoonUI", "").replace("Pro", "");
return `"use client"
import { ${variant.exportName} } from "${variant.package}"
import type { ${variant.exportName}Props } from "${variant.package}"
/**
* ${componentName} - Pro Version
*
* This is a wrapper component that uses the Pro version from @moontra/moonui-pro
* Pro features: ${variant.features.join(", ")}
*
* Note: This component requires a valid MoonUI Pro license.
* The actual implementation is provided by the @moontra/moonui-pro package.
*/
export function ${componentName}(props: ${variant.exportName}Props) {
return <${variant.exportName} {...props} />
}
export type { ${variant.exportName}Props as ${componentName}Props }
`;
}
function generateProOnlyComponentContent(componentName) {
const componentClassName = componentName.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
return `"use client"
import { ${componentClassName} as ${componentClassName}Core } from "@moontra/moonui-pro"
import type { ${componentClassName}Props } from "@moontra/moonui-pro"
// MoonUI Pro ${componentClassName} wrapper
export function ${componentClassName}(props: ${componentClassName}Props) {
return <${componentClassName}Core {...props} />
}
export type { ${componentClassName}Props }
`;
}
async function installComponentWithVariant(componentName, options, authService) {
try {
const variant = await determineComponentVariant(componentName, options, authService);
if ((variant === "pro" || variant === "pro-only") && !await authService.checkProAccess()) {
return {
success: false,
message: `Pro license required for ${componentName}`
};
}
if (variant === "free" || variant === "free-only") {
return {
success: true,
message: `Use registry for ${componentName} (free version)`,
needsRegistry: true
};
}
const componentFile = generateComponentFile(componentName, variant);
if (!componentFile) {
return {
success: false,
message: `Could not generate pro wrapper for ${componentName}`
};
}
const outputDir = path5.join(options.outputPath, "..", "pro");
await fs5.ensureDir(outputDir);
const filePath = path5.join(outputDir, componentFile.fileName);
if (await fs5.pathExists(filePath) && !options.force) {
return {
success: false,
message: `Component already exists: ${componentFile.fileName}. Use --force to overwrite.`
};
}
await fs5.writeFile(filePath, componentFile.content);
const packageJsonPath = path5.join(process.cwd(), "package.json");
const packageJson = await fs5.readJson(packageJsonPath);
const hasProPackage = packageJson.dependencies?.["@moontra/moonui-pro"] || packageJson.devDependencies?.["@moontra/moonui-pro"];
if (!hasProPackage) {
console.log(chalk3.yellow("\n\u26A0\uFE0F @moontra/moonui-pro package not installed"));
console.log(chalk3.cyan("Run: npm install @moontra/moonui-pro"));
}
return {
success: true,
message: `Successfully installed ${componentName} (${variant} version)`,
fileName: componentFile.fileName
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : "Unknown error"
};
}
}
function showComponentComparison(componentName) {
const overlapping = getOverlapping(componentName);
if (!overlapping) {
return;
}
console.log(chalk3.blue("\n\u{1F4CA} Component Comparison:\n"));
console.log(chalk3.yellow("Free Version:"));
console.log(` ${overlapping.free.description}`);
console.log(chalk3.gray(" Features:"));
overlapping.free.features.forEach((f) => {
console.log(chalk3.gray(` \u2022 ${f}`));
});
console.log("");
console.log(chalk3.magenta("Pro Version:"));
console.log(` ${overlapping.pro.description}`);
console.log(chalk3.gray(" Features:"));
overlapping.pro.features.forEach((f) => {
console.log(chalk3.gray(` \u2022 ${f}`));
});
console.log("");
}
// src/commands/add.ts
async function addCommand(componentNames, options = {}) {
const spinner = ora2("Preparing to add components...").start();
try {
const authService = new AuthService();
const auth = await authService.requireAuth();
const cwd = process.cwd();
const config = await getConfig(cwd);
if (!config) {
spinner.fail(chalk4.red("MoonUI configuration not found."));
console.log(chalk4.yellow("Please run `moonui init` to initialize MoonUI in your project."));
return;
}
const outputPath = options.path || config.paths?.components || path6.join(cwd, "src", "components", "ui");
fs6.ensureDirSync(outputPath);
const categories = await getComponentCategories();
const allComponents = [...categories.flatMap((category) => category.components), ...PRO_COMPONENTS];
const proPatterns = ["patterns/login-form", "patterns/checkout-form", "patterns/user-profile"];
const proBlocks = ["blocks/pricing-cards", "blocks/feature-sections", "blocks/hero-sections"];
const proTemplates = ["templates/dashboard-layout", "templates/landing-page"];
const allProItems = [...proPatterns, ...proBlocks, ...proTemplates].map((id) => ({
id,
name: id.split("/")[1].split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
description: `Pro ${id.split("/")[0].slice(0, -1)}`,
tier: "pro"
}));
allComponents.push(...allProItems);
if (options.all) {
spinner.text = "Adding all components...";
componentNames = allComponents.map((component) => component.id);
}
if (componentNames.length === 0) {
spinner.stop();
const { selectedComponents } = await prompts2({
type: "multiselect",
name: "selectedComponents",
message: "Select components to add:",
choices: allComponents.map((component) => ({
title: component.name,
value: component.id,
description: component.description
})),
min: 1
});
if (!selectedComponents || selectedComponents.length === 0) {
console.log(chalk4.yellow("No components selected. Operation cancelled."));
return;
}
componentNames = selectedComponents;
spinner.start("Adding selected components...");
}
const validComponentNames = componentNames.filter(
(name) => allComponents.some((component) => component.id === name)
);
const invalidComponentNames = componentNames.filter(
(name) => !allComponents.some((component) => component.id === name)
);
if (invalidComponentNames.length > 0) {
spinner.warn(chalk4.yellow(`The following components are not recognized: ${invalidComponentNames.join(", ")}`));
console.log(chalk4.yellow("Use `moonui list` to see available components."));
}
if (validComponentNames.length === 0) {
spinner.fail(chalk4.red("No valid components to add."));
return;
}
const addedComponents = [];
const skippedComponents = [];
const deniedComponents = [];
const hasProLicense = await authService.checkProAccess();
if (hasProLicense) {
console.log(chalk4.magenta(`
\u{1F3AF} Pro Lisans Tespit Edildi!`));
console.log(chalk4.yellow(` Device: ${os.hostname()}`));
console.log(chalk4.gray(` \u26A0\uFE0F Single-Device License
`));
}
for (const componentName of validComponentNames) {
const component = allComponents.find((c) => c.id === componentName);
if (!component)
continue;
if (hasOverlappingVersions2(componentName)) {
spinner.stop();
if (validComponentNames.length === 1) {
showComponentComparison(componentName);
}
const result = await installComponentWithVariant(
componentName,
{
variant: options.variant,
force: options.force,
outputPath
},
authService
);
if (result.needsRegistry) {
spinner.start(`Fetching ${componentName} from registry...`);
} else if (result.success) {
addedComponents.push(componentName);
console.log(chalk4.green(`\u2705 ${result.message}`));
console.log(chalk4.cyan(`\u{1F4C1} Created: components/pro/${result.fileName}`));
spinner.start("Adding components...");
continue;
} else {
if (result.message.includes("already exists")) {
skippedComponents.push(componentName);
} else {
deniedComponents.push(componentName);
}
console.log(chalk4.yellow(`\u26A0\uFE0F ${result.message}`));
spinner.start("Adding components...");
continue;
}
}
if (isProComponent(componentName)) {
spinner.text = `Checking license for ${componentName}...`;
const hasAccess = await authService.checkProAccess(componentName);
if (!hasAccess) {
deniedComponents.push(componentName);
spinner.warn(chalk4.yellow(`Access denied for Pro component: ${componentName}`));
continue;
}
}
const componentFileName = `${componentName}.tsx`;
const componentOutputPath = path6.join(outputPath, componentFileName);
if (fs6.existsSync(componentOutputPath) && !options.force) {
skippedComponents.push(componentName);
continue;
}
let componentData;
if (!options.noRegistry) {
spinner.text = `Fetching ${componentName} from registry...`;
componentData = await registryClient.fetchComponent(componentName, auth);
if (!componentData) {
spinner.warn(chalk4.yellow(`Could not fetch component from registry: ${componentName}`));
if (!isProComponent(componentName)) {
spinner.text = `Using local template for ${componentName}...`;
const templateContent = await getComponentTemplate(componentName);
if (templateContent) {
componentData = {
id: componentName,
name: componentName,
description: "",
files: [{
name: `${componentName}.tsx`,
content: templateContent
}],
dependencies: []
};
}
}
}
} else {
if (isProComponent(componentName)) {
spinner.fail(chalk4.red(`Pro components require registry access. Remove --no-registry flag.`));
deniedComponents.push(componentName);
continue;
}
spinner.text = `Using local template for ${componentName}...`;
const templateContent = await getComponentTemplate(componentName);
if (templateContent) {
componentData = {
id: componentName,
name: componentName,
description: "",
files: [{
name: `${componentName}.tsx`,
content: templateContent
}],
dependencies: []
};
}
}
if (!componentData) {
spinner.warn(chalk4.yellow(`Could not find component: ${componentName}`));
continue;
}
for (const file of componentData.files) {
const filePath = path6.join(outputPath, file.name);
fs6.writeFileSync(filePath, file.content);
}
addedComponents.push(componentName);
if (isProComponent(componentName)) {
console.log(chalk4.magenta(`
\u{1F3AF} Pro Component Kurulumu:`));