UNPKG

@moontra/moonui-cli

Version:

CLI tool for MoonUI component library

1,579 lines (1,539 loc) 118 kB
#!/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:`));