lerpa-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,736 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const commander_1 = require("commander");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const child_process = __importStar(require("child_process"));
43
+ const prompts_1 = __importDefault(require("prompts"));
44
+ const picocolors_1 = __importDefault(require("picocolors"));
45
+ const ora_1 = __importDefault(require("ora"));
46
+ const program = new commander_1.Command();
47
+ const CONFIG_FILE = "lerpa.json";
48
+ // Default Registry resolution
49
+ function getLocalRegistryPath() {
50
+ // 1. Bundled with the published CLI (preferred for `pnpm dlx lerpa-cli`).
51
+ // The publish step copies `packages/registry/generated/registry.json`
52
+ // into `packages/cli/registry/registry.json` so the tarball is
53
+ // self-contained.
54
+ const bundled = path.join(__dirname, "../registry/registry.json");
55
+ if (fs.existsSync(bundled)) {
56
+ return bundled;
57
+ }
58
+ // 2. Local registry inside monorepo context (developer convenience).
59
+ const monorepoPath = path.join(process.cwd(), "packages/registry/generated/registry.json");
60
+ if (fs.existsSync(monorepoPath)) {
61
+ return monorepoPath;
62
+ }
63
+ // 3. Peer registry path if running inside mono packages.
64
+ const peerPath = path.join(__dirname, "../../../registry/generated/registry.json");
65
+ if (fs.existsSync(peerPath)) {
66
+ return peerPath;
67
+ }
68
+ // 4. Locally installed inside node_modules relative path.
69
+ const localNodePath = path.join(__dirname, "../registry/generated/registry.json");
70
+ if (fs.existsSync(localNodePath)) {
71
+ return localNodePath;
72
+ }
73
+ return null;
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- registry items are dynamic shadcn JSON; downstream uses heterogeneous fields
76
+ function loadRegistry() {
77
+ const localPath = getLocalRegistryPath();
78
+ if (localPath) {
79
+ try {
80
+ const content = fs.readFileSync(localPath, "utf-8");
81
+ return JSON.parse(content);
82
+ }
83
+ catch (e) {
84
+ console.warn(picocolors_1.default.yellow(`⚠️ Warning: Failed to read local registry from ${localPath}. Using fallback.`));
85
+ }
86
+ }
87
+ // Fallback embedded subset registry if registry.json is not generated yet
88
+ return [
89
+ {
90
+ name: "button",
91
+ type: "registry:ui",
92
+ dependencies: ["class-variance-authority"],
93
+ files: [
94
+ {
95
+ path: "components/ui/button.tsx",
96
+ type: "registry:ui",
97
+ content: "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive: \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline: \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary: \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n }\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
98
+ }
99
+ ]
100
+ }
101
+ ];
102
+ }
103
+ function getConfig() {
104
+ const configPath = path.join(process.cwd(), CONFIG_FILE);
105
+ if (!fs.existsSync(configPath)) {
106
+ return null;
107
+ }
108
+ try {
109
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
110
+ }
111
+ catch (e) {
112
+ return null;
113
+ }
114
+ }
115
+ function detectPackageManager() {
116
+ const cwd = process.cwd();
117
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml")))
118
+ return "pnpm";
119
+ if (fs.existsSync(path.join(cwd, "yarn.lock")))
120
+ return "yarn";
121
+ if (fs.existsSync(path.join(cwd, "bun.lockb")))
122
+ return "bun";
123
+ if (fs.existsSync(path.join(cwd, "package-lock.json")))
124
+ return "npm";
125
+ return "npm";
126
+ }
127
+ function getInstallCommand(pm, deps) {
128
+ const depStr = deps.join(" ");
129
+ switch (pm) {
130
+ case "pnpm":
131
+ return `pnpm add ${depStr}`;
132
+ case "yarn":
133
+ return `yarn add ${depStr}`;
134
+ case "bun":
135
+ return `bun add ${depStr}`;
136
+ default:
137
+ return `npm install ${depStr} --save`;
138
+ }
139
+ }
140
+ function ensureDirectoryExists(filePath) {
141
+ const dirname = path.dirname(filePath);
142
+ if (!fs.existsSync(dirname)) {
143
+ fs.mkdirSync(dirname, { recursive: true });
144
+ }
145
+ }
146
+ function backupFile(targetPath) {
147
+ if (fs.existsSync(targetPath)) {
148
+ const backupPath = `${targetPath}.bak`;
149
+ fs.copyFileSync(targetPath, backupPath);
150
+ console.log(picocolors_1.default.gray(`📁 Created backup file at: ${backupPath}`));
151
+ }
152
+ }
153
+ // -----------------------------------------
154
+ // CLI commands implementation
155
+ // -----------------------------------------
156
+ program
157
+ .name("lerpa")
158
+ .description("Interactive Registry CLI utility for Lerpa UI animated blocks & components")
159
+ .version("0.1.0");
160
+ // INIT COMMAND
161
+ program
162
+ .command("init")
163
+ .description("Initialize configuration and styling aliases in your project")
164
+ .action(async () => {
165
+ console.log(picocolors_1.default.cyan("\n🚀 Welcome to the Lerpa UI Registry System!\n"));
166
+ const configPath = path.join(process.cwd(), CONFIG_FILE);
167
+ if (fs.existsSync(configPath)) {
168
+ const overwrite = await (0, prompts_1.default)({
169
+ type: "confirm",
170
+ name: "value",
171
+ message: `${CONFIG_FILE} already exists. Overwrite configuration?`,
172
+ initial: false,
173
+ });
174
+ if (!overwrite.value) {
175
+ console.log(picocolors_1.default.yellow("Initialization aborted."));
176
+ return;
177
+ }
178
+ }
179
+ const detectedPm = detectPackageManager();
180
+ const questions = [
181
+ {
182
+ type: "select",
183
+ name: "packageManager",
184
+ message: "Which package manager do you use?",
185
+ choices: [
186
+ { title: "pnpm", value: "pnpm" },
187
+ { title: "npm", value: "npm" },
188
+ { title: "yarn", value: "yarn" },
189
+ { title: "bun", value: "bun" },
190
+ ],
191
+ initial: ["pnpm", "npm", "yarn", "bun"].indexOf(detectedPm),
192
+ },
193
+ {
194
+ type: "text",
195
+ name: "cssPath",
196
+ message: "Where is your main globals CSS stylesheet located?",
197
+ initial: fs.existsSync("src/app/globals.css")
198
+ ? "src/app/globals.css"
199
+ : fs.existsSync("app/globals.css")
200
+ ? "app/globals.css"
201
+ : "src/index.css",
202
+ },
203
+ {
204
+ type: "text",
205
+ name: "tailwindConfigPath",
206
+ message: "Where is your Tailwind config file located?",
207
+ initial: fs.existsSync("tailwind.config.ts")
208
+ ? "tailwind.config.ts"
209
+ : "tailwind.config.js",
210
+ },
211
+ {
212
+ type: "text",
213
+ name: "componentsAlias",
214
+ message: "What import alias / target folder do you want to use for components?",
215
+ initial: "@/components",
216
+ },
217
+ {
218
+ type: "text",
219
+ name: "utilsAlias",
220
+ message: "What import alias do you use for helper utility functions (e.g. cn)?",
221
+ initial: "@/lib/utils",
222
+ },
223
+ ];
224
+ const answers = await (0, prompts_1.default)(questions);
225
+ if (!answers.packageManager ||
226
+ !answers.cssPath ||
227
+ !answers.tailwindConfigPath ||
228
+ !answers.componentsAlias ||
229
+ !answers.utilsAlias) {
230
+ console.log(picocolors_1.default.red("❌ Initialization cancelled. Missing inputs."));
231
+ return;
232
+ }
233
+ const config = {
234
+ style: "default",
235
+ tailwind: {
236
+ config: answers.tailwindConfigPath,
237
+ css: answers.cssPath,
238
+ },
239
+ aliases: {
240
+ components: answers.componentsAlias,
241
+ utils: answers.utilsAlias,
242
+ },
243
+ packageManager: answers.packageManager,
244
+ };
245
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
246
+ console.log(picocolors_1.default.green(`\n✔ Config successfully saved to ${picocolors_1.default.bold(CONFIG_FILE)}`));
247
+ // Ensure utils path and cn helper exists
248
+ const utilsRoot = answers.utilsAlias.startsWith("@/")
249
+ ? answers.utilsAlias.replace("@/", "")
250
+ : answers.utilsAlias;
251
+ const targetUtilsPath = path.join(process.cwd(), utilsRoot + ".ts");
252
+ if (!fs.existsSync(targetUtilsPath)) {
253
+ ensureDirectoryExists(targetUtilsPath);
254
+ const cnCode = `import { clsx, type ClassValue } from "clsx";\nimport { twMerge } from "tailwind-merge";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n`;
255
+ fs.writeFileSync(targetUtilsPath, cnCode, "utf-8");
256
+ console.log(picocolors_1.default.green(`✔ Created helper utility function 'cn' at: ${picocolors_1.default.bold(targetUtilsPath)}`));
257
+ }
258
+ console.log(picocolors_1.default.cyan("\n🎉 Lerpa UI initialized successfully! Run 'lerpa add <name>' to fetch items.\n"));
259
+ });
260
+ // ADD COMMAND
261
+ program
262
+ .command("add <name>")
263
+ .description("Fetch and install a specific component/block from the registry")
264
+ .option("-y, --yes", "Skip verification prompt", false)
265
+ .action(async (name, options) => {
266
+ const config = getConfig();
267
+ if (!config) {
268
+ console.error(picocolors_1.default.red(`❌ Error: Please initialize Lerpa UI first by running 'lerpa init'`));
269
+ process.exit(1);
270
+ }
271
+ const registry = loadRegistry();
272
+ const item = registry.find((x) => x.name === name);
273
+ if (!item) {
274
+ console.error(picocolors_1.default.red(`❌ Error: Component "${name}" not found in registry.`));
275
+ process.exit(1);
276
+ }
277
+ console.log(picocolors_1.default.cyan(`\n📦 Found item "${picocolors_1.default.bold(item.name)}" [type: ${item.type}]`));
278
+ // Ask to confirm if not --yes
279
+ if (!options.yes) {
280
+ const confirm = await (0, prompts_1.default)({
281
+ type: "confirm",
282
+ name: "value",
283
+ message: `Install "${item.name}" and all its dependencies into your project?`,
284
+ initial: true,
285
+ });
286
+ if (!confirm.value) {
287
+ console.log(picocolors_1.default.yellow("Installation cancelled."));
288
+ return;
289
+ }
290
+ }
291
+ // Resolve dependencies and install them
292
+ const deps = item.dependencies || [];
293
+ if (deps.length > 0) {
294
+ const spinner = (0, ora_1.default)(`Installing npm dependencies: ${deps.join(", ")}...`).start();
295
+ try {
296
+ const cmd = getInstallCommand(config.packageManager, deps);
297
+ child_process.execSync(cmd, { stdio: "ignore" });
298
+ spinner.succeed(picocolors_1.default.green(`Dependencies installed successfully via ${config.packageManager}`));
299
+ }
300
+ catch (e) {
301
+ spinner.fail(picocolors_1.default.red("Failed to install npm dependencies."));
302
+ console.error(e);
303
+ process.exit(1);
304
+ }
305
+ }
306
+ // Handle registry dependencies recursively
307
+ const regDeps = item.registryDependencies || [];
308
+ for (const regDep of regDeps) {
309
+ console.log(picocolors_1.default.blue(`🔗 Component "${item.name}" depends on "${regDep}". Processing automatically...`));
310
+ // Call add function recursively/synchronously
311
+ child_process.execSync(`node ${process.argv[1]} add ${regDep} --yes`, { stdio: "inherit" });
312
+ }
313
+ // Write file components
314
+ for (const file of item.files) {
315
+ // Map file path based on component path alias
316
+ const fileBase = path.basename(file.path);
317
+ const isUi = file.path.includes("components/ui/");
318
+ const isBlock = file.path.includes("components/blocks/");
319
+ const compRoot = config.aliases.components.startsWith("@/")
320
+ ? config.aliases.components.replace("@/", "")
321
+ : config.aliases.components;
322
+ let relativeTarget = "";
323
+ if (isUi) {
324
+ relativeTarget = path.join(compRoot, "ui", fileBase);
325
+ }
326
+ else if (isBlock) {
327
+ relativeTarget = path.join(compRoot, "blocks", fileBase);
328
+ }
329
+ else {
330
+ relativeTarget = path.join(compRoot, fileBase);
331
+ }
332
+ const targetPath = path.join(process.cwd(), relativeTarget);
333
+ // Process content to replace alias imports if needed (e.g. "@/lib/utils" replaced by actual alias)
334
+ let finalContent = file.content;
335
+ if (config.aliases.utils !== "@/lib/utils") {
336
+ finalContent = finalContent.replaceAll("@/lib/utils", config.aliases.utils);
337
+ }
338
+ if (config.aliases.components !== "@/components") {
339
+ finalContent = finalContent.replaceAll("@/components", config.aliases.components);
340
+ }
341
+ if (fs.existsSync(targetPath)) {
342
+ // Identical (e.g. a shared bundled file like use-animation-hooks re-added by a
343
+ // dependency) — skip silently: no prompt, no .bak clutter.
344
+ if (fs.readFileSync(targetPath, "utf-8") === finalContent) {
345
+ continue;
346
+ }
347
+ // --yes auto-overwrites; otherwise confirm.
348
+ if (!options.yes) {
349
+ const overwrite = await (0, prompts_1.default)({
350
+ type: "confirm",
351
+ name: "value",
352
+ message: `File already exists: ${picocolors_1.default.bold(relativeTarget)}. Overwrite?`,
353
+ initial: true,
354
+ });
355
+ if (!overwrite.value) {
356
+ console.log(picocolors_1.default.yellow(`Skipped ${relativeTarget}`));
357
+ continue;
358
+ }
359
+ }
360
+ backupFile(targetPath);
361
+ }
362
+ ensureDirectoryExists(targetPath);
363
+ fs.writeFileSync(targetPath, finalContent, "utf-8");
364
+ console.log(picocolors_1.default.green(`✔ Wrote file to: ${picocolors_1.default.bold(relativeTarget)}`));
365
+ }
366
+ console.log(picocolors_1.default.green(`\n🎉 Component "${item.name}" successfully added to your project!\n`));
367
+ });
368
+ // LIST COMMAND
369
+ program
370
+ .command("list")
371
+ .description("List all available components and blocks in the registry")
372
+ .action(() => {
373
+ const registry = loadRegistry();
374
+ const uis = registry.filter((x) => x.type === "registry:ui");
375
+ const blocks = registry.filter((x) => x.type === "registry:block");
376
+ console.log(picocolors_1.default.cyan("\n✨ Available Atomic UI Components (registry:ui):"));
377
+ if (uis.length === 0) {
378
+ console.log(picocolors_1.default.gray(" (No UI components found. Run build script first)"));
379
+ }
380
+ else {
381
+ uis.forEach((x) => {
382
+ console.log(` - ${picocolors_1.default.bold(picocolors_1.default.green(x.name))} ${picocolors_1.default.gray(`(Dependencies: ${x.dependencies?.join(", ") || "none"})`)}`);
383
+ });
384
+ }
385
+ console.log(picocolors_1.default.cyan("\n✨ Available High-Fidelity Interactive Blocks (registry:block):"));
386
+ if (blocks.length === 0) {
387
+ console.log(picocolors_1.default.gray(" (No interactive blocks found. Run build script first)"));
388
+ }
389
+ else {
390
+ blocks.forEach((x) => {
391
+ const regDepsStr = x.registryDependencies ? ` [requires: ${x.registryDependencies.join(", ")}]` : "";
392
+ console.log(` - ${picocolors_1.default.bold(picocolors_1.default.blue(x.name))}${picocolors_1.default.gray(regDepsStr)}`);
393
+ });
394
+ }
395
+ console.log("");
396
+ });
397
+ // SEARCH COMMAND
398
+ program
399
+ .command("search [query]")
400
+ .description("Search registry elements for matches")
401
+ .action((query) => {
402
+ const registry = loadRegistry();
403
+ const matches = registry.filter((x) => {
404
+ if (!query)
405
+ return true;
406
+ return x.name.toLowerCase().includes(query.toLowerCase());
407
+ });
408
+ console.log(picocolors_1.default.cyan(`\n🔍 Found ${matches.length} matching items in registry:`));
409
+ matches.forEach((x) => {
410
+ console.log(` [${x.type === "registry:ui" ? picocolors_1.default.green("UI") : picocolors_1.default.blue("Block")}] ${picocolors_1.default.bold(x.name)}`);
411
+ });
412
+ console.log("");
413
+ });
414
+ // THEME COMMAND
415
+ program
416
+ .command("theme")
417
+ .description("Apply a specific color theme values to tailwind CSS file")
418
+ .action(async () => {
419
+ const config = getConfig();
420
+ if (!config) {
421
+ console.error(picocolors_1.default.red(`❌ Error: Lerpa UI is not initialized yet. Run 'lerpa init'`));
422
+ process.exit(1);
423
+ }
424
+ const cssPath = path.join(process.cwd(), config.tailwind.css);
425
+ if (!fs.existsSync(cssPath)) {
426
+ console.error(picocolors_1.default.red(`❌ Error: Global CSS file not found at: ${cssPath}`));
427
+ process.exit(1);
428
+ }
429
+ const themeResponse = await (0, prompts_1.default)({
430
+ type: "select",
431
+ name: "theme",
432
+ message: "Select a visual color theme to apply:",
433
+ choices: [
434
+ { title: "Zinc (Modern Slate Minimalist)", value: "zinc" },
435
+ { title: "Slate (Corporate Developer Grey)", value: "slate" },
436
+ { title: "Rose (Immersive Warm Gradient)", value: "rose" },
437
+ { title: "Violet (AI Chat Futuristic Purple)", value: "violet" },
438
+ { title: "Orange (Energetic High-Contrast Glow)", value: "orange" },
439
+ ],
440
+ });
441
+ if (!themeResponse.theme) {
442
+ return;
443
+ }
444
+ const themeVariables = {
445
+ zinc: `
446
+ :root {
447
+ --background: 0 0% 100%;
448
+ --foreground: 240 10% 3.9%;
449
+ --primary: 240 5.9% 10%;
450
+ --primary-foreground: 0 0% 98%;
451
+ --secondary: 240 4.8% 95.9%;
452
+ --secondary-foreground: 240 5.9% 10%;
453
+ --muted: 240 4.8% 95.9%;
454
+ --muted-foreground: 240 3.8% 46.1%;
455
+ --accent: 240 4.8% 95.9%;
456
+ --accent-foreground: 240 5.9% 10%;
457
+ --border: 240 5.9% 90%;
458
+ --input: 240 5.9% 90%;
459
+ --ring: 240 5.9% 10%;
460
+ }
461
+ .dark {
462
+ --background: 240 10% 3.9%;
463
+ --foreground: 0 0% 98%;
464
+ --primary: 0 0% 98%;
465
+ --primary-foreground: 240 5.9% 10%;
466
+ --secondary: 240 3.7% 15.9%;
467
+ --secondary-foreground: 0 0% 98%;
468
+ --muted: 240 3.7% 15.9%;
469
+ --muted-foreground: 240 5% 64.9%;
470
+ --accent: 240 3.7% 15.9%;
471
+ --accent-foreground: 0 0% 98%;
472
+ --border: 240 3.7% 15.9%;
473
+ --input: 240 3.7% 15.9%;
474
+ --ring: 240 4.9% 83.9%;
475
+ }`,
476
+ slate: `
477
+ :root {
478
+ --background: 0 0% 100%;
479
+ --foreground: 222.2 84% 4.9%;
480
+ --primary: 222.2 47.4% 11.2%;
481
+ --primary-foreground: 210 40% 98%;
482
+ --secondary: 210 40% 96.1%;
483
+ --secondary-foreground: 222.2 47.4% 11.2%;
484
+ --muted: 210 40% 96.1%;
485
+ --muted-foreground: 215.4 16.3% 46.9%;
486
+ --accent: 210 40% 96.1%;
487
+ --accent-foreground: 222.2 47.4% 11.2%;
488
+ --border: 214.3 31.8% 91.4%;
489
+ --input: 214.3 31.8% 91.4%;
490
+ --ring: 222.2 84% 4.9%;
491
+ }
492
+ .dark {
493
+ --background: 222.2 84% 4.9%;
494
+ --foreground: 210 40% 98%;
495
+ --primary: 210 40% 98%;
496
+ --primary-foreground: 222.2 47.4% 11.2%;
497
+ --secondary: 217.2 32.6% 17.5%;
498
+ --secondary-foreground: 210 40% 98%;
499
+ --muted: 217.2 32.6% 17.5%;
500
+ --muted-foreground: 215 20.2% 65.1%;
501
+ --accent: 217.2 32.6% 17.5%;
502
+ --accent-foreground: 210 40% 98%;
503
+ --border: 217.2 32.6% 17.5%;
504
+ --input: 217.2 32.6% 17.5%;
505
+ --ring: 212.7 26.8% 83.9%;
506
+ }`,
507
+ rose: `
508
+ :root {
509
+ --background: 0 0% 100%;
510
+ --foreground: 343 35% 3.9%;
511
+ --primary: 343 90% 46%;
512
+ --primary-foreground: 0 0% 98%;
513
+ --secondary: 343 20% 96%;
514
+ --secondary-foreground: 343 90% 46%;
515
+ --muted: 343 20% 96%;
516
+ --muted-foreground: 343 10% 46%;
517
+ --border: 343 20% 90%;
518
+ --input: 343 20% 90%;
519
+ --ring: 343 90% 46%;
520
+ }
521
+ .dark {
522
+ --background: 343 35% 3.9%;
523
+ --foreground: 0 0% 98%;
524
+ --primary: 343 90% 60%;
525
+ --primary-foreground: 343 35% 3.9%;
526
+ --secondary: 343 20% 15%;
527
+ --secondary-foreground: 0 0% 98%;
528
+ --muted: 343 20% 15%;
529
+ --muted-foreground: 343 10% 65%;
530
+ --border: 343 20% 15%;
531
+ --input: 343 20% 15%;
532
+ --ring: 343 90% 60%;
533
+ }`,
534
+ violet: `
535
+ :root {
536
+ --background: 0 0% 100%;
537
+ --foreground: 262.1 83.3% 2%;
538
+ --primary: 262.1 83.3% 58%;
539
+ --primary-foreground: 210 40% 98%;
540
+ --secondary: 210 40% 96.1%;
541
+ --secondary-foreground: 222.2 47.4% 11.2%;
542
+ --muted: 210 40% 96.1%;
543
+ --muted-foreground: 215.4 16.3% 46.9%;
544
+ --border: 214.3 31.8% 91.4%;
545
+ --input: 214.3 31.8% 91.4%;
546
+ --ring: 262.1 83.3% 58%;
547
+ }
548
+ .dark {
549
+ --background: 224 71.4% 4.1%;
550
+ --foreground: 210 20% 98%;
551
+ --primary: 263.4 70% 50.4%;
552
+ --primary-foreground: 210 20% 98%;
553
+ --secondary: 215 27.9% 16.9%;
554
+ --secondary-foreground: 210 20% 98%;
555
+ --muted: 215 27.9% 16.9%;
556
+ --muted-foreground: 217.9 10.6% 64.9%;
557
+ --border: 215 27.9% 16.9%;
558
+ --input: 215 27.9% 16.9%;
559
+ --ring: 263.4 70% 50.4%;
560
+ }`,
561
+ orange: `
562
+ :root {
563
+ --background: 0 0% 100%;
564
+ --foreground: 20 14.3% 4.1%;
565
+ --primary: 24.6 95% 53.1%;
566
+ --primary-foreground: 60 9.1% 97.8%;
567
+ --secondary: 60 4.8% 95.9%;
568
+ --secondary-foreground: 24.6 95% 53.1%;
569
+ --muted: 60 4.8% 95.9%;
570
+ --muted-foreground: 25 5.3% 44.7%;
571
+ --border: 20 5.9% 90%;
572
+ --input: 20 5.9% 90%;
573
+ --ring: 24.6 95% 53.1%;
574
+ }
575
+ .dark {
576
+ --background: 20 14.3% 4.1%;
577
+ --foreground: 60 9.1% 97.8%;
578
+ --primary: 20.5 90.2% 48.2%;
579
+ --primary-foreground: 60 9.1% 97.8%;
580
+ --secondary: 12 6.5% 15.1%;
581
+ --secondary-foreground: 60 9.1% 97.8%;
582
+ --muted: 12 6.5% 15.1%;
583
+ --muted-foreground: 24 5.4% 63.9%;
584
+ --border: 12 6.5% 15.1%;
585
+ --input: 12 6.5% 15.1%;
586
+ --ring: 20.5 90.2% 48.2%;
587
+ }`,
588
+ };
589
+ backupFile(cssPath);
590
+ const originalCSS = fs.readFileSync(cssPath, "utf-8");
591
+ // Replace existing :root block or append it
592
+ const chosenThemeVars = themeVariables[themeResponse.theme];
593
+ if (originalCSS.includes(":root")) {
594
+ console.log(picocolors_1.default.yellow("⚠️ A :root section was detected in your CSS file. Appending select Lerpa UI variables."));
595
+ }
596
+ fs.writeFileSync(cssPath, originalCSS + "\n" + chosenThemeVars, "utf-8");
597
+ console.log(picocolors_1.default.green(`\n✔ Theme "${picocolors_1.default.bold(themeResponse.theme)}" successfully applied to ${picocolors_1.default.bold(config.tailwind.css)}!\n`));
598
+ });
599
+ // DOCTOR COMMAND
600
+ program
601
+ .command("doctor")
602
+ .description("Validate current workspace configurations and check dependency health status")
603
+ .action(() => {
604
+ console.log(picocolors_1.default.cyan("\n👨‍⚕️ Running Lerpa UI Doctor diagnostic health check...\n"));
605
+ const config = getConfig();
606
+ if (!config) {
607
+ console.error(picocolors_1.default.red(`❌ Diagnostic Fail: No lerpa.json detected. Run 'lerpa init' to configure.`));
608
+ process.exit(1);
609
+ }
610
+ console.log(`[PASS] Config file lerpa.json detected.`);
611
+ console.log(` Target Package Manager: ${config.packageManager}`);
612
+ console.log(` Globals Stylesheet: ${config.tailwind.css}`);
613
+ console.log(` Tailwind configuration: ${config.tailwind.config}`);
614
+ // Check CSS existence
615
+ if (fs.existsSync(path.join(process.cwd(), config.tailwind.css))) {
616
+ console.log(`[PASS] Globals CSS file exists.`);
617
+ }
618
+ else {
619
+ console.warn(`[WARN] Globals CSS file not found at matching path: ${config.tailwind.css}`);
620
+ }
621
+ // Check packages
622
+ const packageJsonPath = path.join(process.cwd(), "package.json");
623
+ if (fs.existsSync(packageJsonPath)) {
624
+ try {
625
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
626
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
627
+ const standardChecks = ["lucide-react", "motion", "clsx", "tailwind-merge"];
628
+ standardChecks.forEach((dep) => {
629
+ if (allDeps[dep]) {
630
+ console.log(`[PASS] Found standard dependency: ${dep} (${allDeps[dep]})`);
631
+ }
632
+ else {
633
+ console.log(`[INFO] Suggestion: ${dep} is not explicitly specified in package.json root. (CLI automatically installs it when components require it)`);
634
+ }
635
+ });
636
+ }
637
+ catch (e) {
638
+ console.error(`[FAIL] Could not parse package.json. Check syntax integrity.`);
639
+ }
640
+ }
641
+ else {
642
+ console.warn(`[WARN] No package.json found at project root directory.`);
643
+ }
644
+ console.log(picocolors_1.default.green(`\n✔ Diagnostics complete! System configurations look healthy and ready to go.\n`));
645
+ });
646
+ // UPDATE COMMAND
647
+ program
648
+ .command("update [name]")
649
+ .description("Update a specific component or all installed components to latest versions")
650
+ .action(async (name) => {
651
+ const config = getConfig();
652
+ if (!config) {
653
+ console.error(picocolors_1.default.red(`❌ Error: Lerpa UI is not initialized yet. Run 'lerpa init'`));
654
+ process.exit(1);
655
+ }
656
+ const registry = loadRegistry();
657
+ if (name) {
658
+ // Update specific component
659
+ const item = registry.find((x) => x.name === name);
660
+ if (!item) {
661
+ console.error(picocolors_1.default.red(`❌ Error: Component "${name}" not found in registry.`));
662
+ process.exit(1);
663
+ }
664
+ console.log(picocolors_1.default.cyan(`Updating component: ${name}...`));
665
+ child_process.execSync(`node ${process.argv[1]} add ${name} --yes`, { stdio: "inherit" });
666
+ }
667
+ else {
668
+ // Update all components currently installed in components/ui or components/blocks directories
669
+ console.log(picocolors_1.default.cyan("Updating all installed Lerpa UI components and blocks..."));
670
+ const compRoot = config.aliases.components.startsWith("@/")
671
+ ? config.aliases.components.replace("@/", "")
672
+ : config.aliases.components;
673
+ const uiDir = path.join(process.cwd(), compRoot, "ui");
674
+ const blockDir = path.join(process.cwd(), compRoot, "blocks");
675
+ const installedNames = [];
676
+ if (fs.existsSync(uiDir)) {
677
+ fs.readdirSync(uiDir).forEach((file) => {
678
+ if (file.endsWith(".tsx")) {
679
+ const componentName = path.basename(file, ".tsx");
680
+ if (registry.some((x) => x.name === componentName)) {
681
+ installedNames.push(componentName);
682
+ }
683
+ }
684
+ });
685
+ }
686
+ if (fs.existsSync(blockDir)) {
687
+ fs.readdirSync(blockDir).forEach((file) => {
688
+ if (file.endsWith(".tsx")) {
689
+ const componentName = path.basename(file, ".tsx");
690
+ if (registry.some((x) => x.name === componentName)) {
691
+ installedNames.push(componentName);
692
+ }
693
+ }
694
+ });
695
+ }
696
+ if (installedNames.length === 0) {
697
+ console.log(picocolors_1.default.yellow("No standard registry components detected in your components folders."));
698
+ return;
699
+ }
700
+ console.log(picocolors_1.default.blue(`Detected ${installedNames.length} components to update: ${installedNames.join(", ")}`));
701
+ for (const componentName of installedNames) {
702
+ console.log(picocolors_1.default.cyan(`\nUpdating ${componentName}...`));
703
+ try {
704
+ child_process.execSync(`node ${process.argv[1]} add ${componentName} --yes`, { stdio: "inherit" });
705
+ }
706
+ catch (e) {
707
+ console.error(picocolors_1.default.red(`❌ Failed to update component ${componentName}`));
708
+ }
709
+ }
710
+ console.log(picocolors_1.default.green("\n🎉 All detected components successfully updated to latest versions!\n"));
711
+ }
712
+ });
713
+ // INFO COMMAND
714
+ program
715
+ .command("info")
716
+ .description("Display Lerpa UI monorepo CLI metadata info")
717
+ .action(() => {
718
+ console.log(picocolors_1.default.cyan("\n🔥 Lerpa UI Monorepo developer environment metadata:"));
719
+ console.log(` CLI tool version: ${picocolors_1.default.bold("0.1.0-alpha")}`);
720
+ console.log(` License terms : MIT License`);
721
+ console.log(` Repository URL : https://github.com/cuibit-labs/lerpaui`);
722
+ console.log(` Official docs : https://lerpaui.com`);
723
+ const config = getConfig();
724
+ if (config) {
725
+ console.log(picocolors_1.default.green("\n✔ Local Project configuration details:"));
726
+ console.log(` Import components alias : ${config.aliases.components}`);
727
+ console.log(` Import utility alias : ${config.aliases.utils}`);
728
+ console.log(` Global CSS file path : ${config.tailwind.css}`);
729
+ console.log(` Package Manager Lock : ${config.packageManager}`);
730
+ }
731
+ else {
732
+ console.log(picocolors_1.default.yellow("\n⚠️ Lerpa UI is not initialized yet in this directory. Run 'lerpa init'"));
733
+ }
734
+ console.log("");
735
+ });
736
+ program.parse(process.argv);