cooterlabs 1.0.1 → 2.0.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/bin/index.js CHANGED
@@ -14,6 +14,116 @@ const __dirname = path.dirname(__filename);
14
14
  const cwd = process.cwd();
15
15
 
16
16
  const REGISTRY = {
17
+ alert: {
18
+ name: "Alert",
19
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
20
+ dev_dependencies: [],
21
+ },
22
+ "action-sheet": {
23
+ name: "ActionSheet",
24
+ dependencies: ["@radix-ui/react-dialog", "clsx", "tailwind-merge"],
25
+ dev_dependencies: [],
26
+ },
27
+ breadcrumb: {
28
+ name: "Breadcrumb",
29
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
30
+ dev_dependencies: [],
31
+ },
32
+ "button-group": {
33
+ name: "ButtonGroup",
34
+ dependencies: ["clsx", "tailwind-merge"],
35
+ dev_dependencies: [],
36
+ },
37
+ "context-menu": {
38
+ name: "ContextMenu",
39
+ dependencies: [
40
+ "@radix-ui/react-context-menu",
41
+ "lucide-react",
42
+ "clsx",
43
+ "tailwind-merge",
44
+ ],
45
+ dev_dependencies: [],
46
+ },
47
+ dialog: {
48
+ name: "Dialog",
49
+ dependencies: [
50
+ "@radix-ui/react-dialog",
51
+ "lucide-react",
52
+ "clsx",
53
+ "tailwind-merge",
54
+ ],
55
+ dev_dependencies: [],
56
+ },
57
+ dropdown: {
58
+ name: "Dropdown",
59
+ dependencies: [
60
+ "@radix-ui/react-dropdown-menu",
61
+ "lucide-react",
62
+ "clsx",
63
+ "tailwind-merge",
64
+ ],
65
+ dev_dependencies: [],
66
+ },
67
+ list: {
68
+ name: "List",
69
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
70
+ dev_dependencies: [],
71
+ },
72
+ loader: {
73
+ name: "Loader",
74
+ dependencies: ["clsx", "tailwind-merge"],
75
+ dev_dependencies: [],
76
+ },
77
+ "navbar-bottom": {
78
+ name: "NavbarBottom",
79
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
80
+ dev_dependencies: [],
81
+ },
82
+ "navbar-top": {
83
+ name: "NavbarTop",
84
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
85
+ dev_dependencies: [],
86
+ },
87
+ "page-control": {
88
+ name: "PageControl",
89
+ dependencies: ["clsx", "tailwind-merge"],
90
+ dev_dependencies: [],
91
+ },
92
+ pagination: {
93
+ name: "Pagination",
94
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
95
+ dev_dependencies: [],
96
+ },
97
+ progress: {
98
+ name: "Progress",
99
+ dependencies: ["@radix-ui/react-progress", "clsx", "tailwind-merge"],
100
+ dev_dependencies: [],
101
+ },
102
+ radio: {
103
+ name: "Radio",
104
+ dependencies: ["@radix-ui/react-radio-group", "clsx", "tailwind-merge"],
105
+ dev_dependencies: [],
106
+ },
107
+ stepper: {
108
+ name: "Stepper",
109
+ dependencies: ["lucide-react", "clsx", "tailwind-merge"],
110
+ dev_dependencies: [],
111
+ },
112
+ tabs: {
113
+ name: "Tabs",
114
+ dependencies: ["@radix-ui/react-tabs", "clsx", "tailwind-merge"],
115
+ dev_dependencies: [],
116
+ },
117
+ toggle: {
118
+ name: "Toggle",
119
+ dependencies: ["@radix-ui/react-switch", "clsx", "tailwind-merge"],
120
+ dev_dependencies: [],
121
+ },
122
+ tooltip: {
123
+ name: "Tooltip",
124
+ dependencies: ["@radix-ui/react-tooltip", "clsx", "tailwind-merge"],
125
+ dev_dependencies: [],
126
+ },
17
127
  button: {
18
128
  name: "Button",
19
129
  dependencies: [
@@ -57,15 +167,237 @@ const REGISTRY = {
57
167
  checkbox: {
58
168
  name: "Checkbox",
59
169
  dependencies: [
170
+ "@radix-ui/react-checkbox",
60
171
  "lucide-react",
61
172
  "clsx",
62
173
  "tailwind-merge",
63
- "@radix-ui/react-checkbox",
64
174
  ],
65
175
  dev_dependencies: [],
66
176
  },
67
177
  };
68
178
 
179
+ const THEMES = {
180
+ aeon: `@theme static {
181
+ /* ——— Aeon Product Design System ——— */
182
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
183
+
184
+ /* Semantic colors */
185
+ --color-background: #ffffff;
186
+ --color-foreground: #1c222a;
187
+ --color-card: #ffffff;
188
+ --color-card-foreground: #1c222a;
189
+ --color-popover: #ffffff;
190
+ --color-popover-foreground: #1c222a;
191
+ --color-primary: #0d3435;
192
+ --color-primary-foreground: #ffffff;
193
+ --color-secondary: #f4f5f6;
194
+ --color-secondary-foreground: #1c222a;
195
+ --color-muted: #e7e9ec;
196
+ --color-muted-foreground: #475569;
197
+ --color-accent: #f1f3f3;
198
+ --color-accent-foreground: #0a2b2b;
199
+ --color-destructive: #821717;
200
+ --color-destructive-foreground: #ffffff;
201
+ --color-border: #cbcfd5;
202
+ --color-input: #cbcfd5;
203
+ --color-ring: #0d3435;
204
+ --color-success: #14532d;
205
+ --color-success-foreground: #ffffff;
206
+ --color-info: #075985;
207
+ --color-info-foreground: #ffffff;
208
+ --color-warning: #713f12;
209
+ --color-warning-foreground: #ffffff;
210
+ --color-error: #991b1b;
211
+ --color-error-foreground: #ffffff;
212
+
213
+ /* Primitive scales */
214
+ --color-primary-50: #f1f3f3;
215
+ --color-primary-100: #e0e6e6;
216
+ --color-primary-200: #bcc9c9;
217
+ --color-primary-300: #93a8a8;
218
+ --color-primary-400: #577778;
219
+ --color-primary-500: #0f3d3e;
220
+ --color-primary-600: #0d3435;
221
+ --color-primary-700: #0a2b2b;
222
+ --color-primary-800: #082222;
223
+ --color-primary-900: #061819;
224
+ --color-grey-50: #f4f5f6;
225
+ --color-grey-100: #e7e9ec;
226
+ --color-grey-200: #cbcfd5;
227
+ --color-grey-300: #acb2bc;
228
+ --color-grey-400: #7e8896;
229
+ --color-grey-500: #475569;
230
+ --color-grey-600: #3c4859;
231
+ --color-grey-700: #323c4a;
232
+ --color-grey-800: #272f3a;
233
+ --color-grey-900: #1c222a;
234
+ --color-success-50: #f1f5f2;
235
+ --color-success-100: #e0e9e4;
236
+ --color-success-200: #bdcfc4;
237
+ --color-success-300: #95b2a0;
238
+ --color-success-400: #5a876c;
239
+ --color-success-500: #14532d;
240
+ --color-success-600: #114726;
241
+ --color-success-700: #0e3a20;
242
+ --color-success-800: #0b2e19;
243
+ --color-success-900: #082112;
244
+ --color-error-50: #f9f1f1;
245
+ --color-error-100: #f2e1e1;
246
+ --color-error-200: #e2bfbf;
247
+ --color-error-300: #d19898;
248
+ --color-error-400: #b85f5f;
249
+ --color-error-500: #991b1b;
250
+ --color-error-600: #821717;
251
+ --color-error-700: #6b1313;
252
+ --color-error-800: #540f0f;
253
+ --color-error-900: #3d0b0b;
254
+ --color-warning-50: #f6f3f1;
255
+ --color-warning-100: #ede6e0;
256
+ --color-warning-200: #d7c9bd;
257
+ --color-warning-300: #bfa994;
258
+ --color-warning-400: #9c7959;
259
+ --color-warning-500: #713f12;
260
+ --color-warning-600: #60360f;
261
+ --color-warning-700: #4f2c0d;
262
+ --color-warning-800: #3e230a;
263
+ --color-warning-900: #2d1907;
264
+ --color-info-50: #f0f5f8;
265
+ --color-info-100: #dfe9ef;
266
+ --color-info-200: #bad1dd;
267
+ --color-info-300: #8fb4c8;
268
+ --color-info-400: #518baa;
269
+ --color-info-500: #075985;
270
+ --color-info-600: #064c71;
271
+ --color-info-700: #053e5d;
272
+ --color-info-800: #04314a;
273
+ --color-info-900: #032435;
274
+
275
+ /* Typography — Aeon text ramp (text-h1 … text-c3, text-label, text-btn-*) */
276
+ --text-h1: 3rem;
277
+ --text-h1--line-height: 3.625rem;
278
+ --text-h1--font-weight: 600;
279
+ --text-h2: 2.5rem;
280
+ --text-h2--line-height: 3rem;
281
+ --text-h2--font-weight: 600;
282
+ --text-h3: 2rem;
283
+ --text-h3--line-height: 2.375rem;
284
+ --text-h3--font-weight: 600;
285
+ --text-h4: 1.75rem;
286
+ --text-h4--line-height: 2.125rem;
287
+ --text-h4--font-weight: 600;
288
+ --text-h5: 1.5rem;
289
+ --text-h5--line-height: 1.75rem;
290
+ --text-h5--font-weight: 600;
291
+ --text-s1: 1.125rem;
292
+ --text-s1--line-height: 1.75rem;
293
+ --text-s1--font-weight: 600;
294
+ --text-s2: 1rem;
295
+ --text-s2--line-height: 1.5rem;
296
+ --text-s2--font-weight: 600;
297
+ --text-b1: 1rem;
298
+ --text-b1--line-height: 1.5rem;
299
+ --text-b1--font-weight: 400;
300
+ --text-b2: 1rem;
301
+ --text-b2--line-height: 1.5rem;
302
+ --text-b2--font-weight: 500;
303
+ --text-b3: 0.875rem;
304
+ --text-b3--line-height: 1.25rem;
305
+ --text-b3--font-weight: 400;
306
+ --text-b4: 0.875rem;
307
+ --text-b4--line-height: 1.25rem;
308
+ --text-b4--font-weight: 500;
309
+ --text-c1: 0.75rem;
310
+ --text-c1--line-height: 1rem;
311
+ --text-c1--font-weight: 400;
312
+ --text-c2: 0.75rem;
313
+ --text-c2--line-height: 1rem;
314
+ --text-c2--font-weight: 500;
315
+ --text-c3: 0.625rem;
316
+ --text-c3--line-height: 0.875rem;
317
+ --text-c3--font-weight: 500;
318
+ --text-label: 0.75rem;
319
+ --text-label--line-height: 1rem;
320
+ --text-label--font-weight: 500;
321
+ --text-btn-giant: 1.125rem;
322
+ --text-btn-giant--line-height: 1.5rem;
323
+ --text-btn-giant--font-weight: 600;
324
+ --text-btn-large: 1rem;
325
+ --text-btn-large--line-height: 1.25rem;
326
+ --text-btn-large--font-weight: 600;
327
+ --text-btn-medium: 0.875rem;
328
+ --text-btn-medium--line-height: 1rem;
329
+ --text-btn-medium--font-weight: 600;
330
+ --text-btn-small: 0.75rem;
331
+ --text-btn-small--line-height: 1rem;
332
+ --text-btn-small--font-weight: 600;
333
+ --text-btn-tiny: 0.625rem;
334
+ --text-btn-tiny--line-height: 0.75rem;
335
+ --text-btn-tiny--font-weight: 600;
336
+
337
+ /* Radius scale — rounded-xxs … rounded-xl */
338
+ --radius-xxs: 0.25rem;
339
+ --radius-xs: 0.5rem;
340
+ --radius-sm: 0.75rem;
341
+ --radius-md: 1rem;
342
+ --radius-lg: 1.25rem;
343
+ --radius-xl: 1.5rem;
344
+
345
+ /* Elevation — shadow-100 … shadow-800 */
346
+ --shadow-100: 0 4px 4px -2px #13192714, 0 2px 4px -2px #1319271f;
347
+ --shadow-200: 0 8px 8px -4px #13192714, 0 4px 6px -4px #1319271f;
348
+ --shadow-300: 0 8px 16px -6px #13192714, 0 6px 8px -6px #1319271f;
349
+ --shadow-400: 0 8px 24px -4px #13192714, 0 6px 12px -6px #1319271f;
350
+ --shadow-500: 0 10px 32px -4px #1319271a, 0 6px 14px -6px #1319271f;
351
+ --shadow-600: 0 12px 42px -4px #1319271f, 0 8px 18px -6px #1319271f;
352
+ --shadow-700: 0 14px 64px -4px #1319271f, 0 8px 22px -6px #1319271f;
353
+ --shadow-800: 0 18px 88px -4px #13192724, 0 8px 28px -6px #1319271f;
354
+ }`,
355
+ zinc: `:root {
356
+ --background: 0 0% 100%;
357
+ --foreground: 240 10% 3.9%;
358
+ --card: 0 0% 100%;
359
+ --card-foreground: 240 10% 3.9%;
360
+ --popover: 0 0% 100%;
361
+ --popover-foreground: 240 10% 3.9%;
362
+ --primary: 240 5.9% 10%;
363
+ --primary-foreground: 0 0% 98%;
364
+ --secondary: 240 4.8% 95.9%;
365
+ --secondary-foreground: 240 5.9% 10%;
366
+ --muted: 240 4.8% 95.9%;
367
+ --muted-foreground: 240 3.8% 46.1%;
368
+ --accent: 240 4.8% 95.9%;
369
+ --accent-foreground: 240 5.9% 10%;
370
+ --destructive: 0 84.2% 60.2%;
371
+ --destructive-foreground: 0 0% 98%;
372
+ --border: 240 5.9% 90%;
373
+ --input: 240 5.9% 90%;
374
+ --ring: 240 10% 3.9%;
375
+ --radius: 0.5rem;
376
+ }`,
377
+ slate: `:root {
378
+ --background: 0 0% 100%;
379
+ --foreground: 222.2 84% 4.9%;
380
+ --card: 0 0% 100%;
381
+ --card-foreground: 222.2 84% 4.9%;
382
+ --popover: 0 0% 100%;
383
+ --popover-foreground: 222.2 84% 4.9%;
384
+ --primary: 222.2 47.4% 11.2%;
385
+ --primary-foreground: 210 40% 98%;
386
+ --secondary: 210 40% 96.1%;
387
+ --secondary-foreground: 222.2 47.4% 11.2%;
388
+ --muted: 210 40% 96.1%;
389
+ --muted-foreground: 215.4 16.3% 46.9%;
390
+ --accent: 210 40% 96.1%;
391
+ --accent-foreground: 222.2 47.4% 11.2%;
392
+ --destructive: 0 84.2% 60.2%;
393
+ --destructive-foreground: 210 40% 98%;
394
+ --border: 214.3 31.8% 91.4%;
395
+ --input: 214.3 31.8% 91.4%;
396
+ --ring: 222.2 84% 4.9%;
397
+ --radius: 0.5rem;
398
+ }`,
399
+ };
400
+
69
401
  const templatesDir = path.join(__dirname, "../templates");
70
402
 
71
403
  const detectEnvironment = () => {
@@ -164,13 +496,20 @@ const main = async () => {
164
496
  }
165
497
 
166
498
  const args = process.argv.slice(2);
167
- let command = args[0];
168
- let componentName = args[1];
169
-
170
- if (!command || command !== "add") {
171
- // If command isn't provided or is entirely missing, treat the first arg as the component name
172
- command = "add";
173
- componentName = args[0] || "";
499
+ const isAll = args.includes("--all");
500
+ let command = "add";
501
+ let componentName = "";
502
+
503
+ // Parse command & componentName from args (ignoring flags)
504
+ const positionalArgs = args.filter((a) => !a.startsWith("--"));
505
+ if (positionalArgs.length > 0) {
506
+ if (positionalArgs[0] === "init" || positionalArgs[0] === "add") {
507
+ command = positionalArgs[0];
508
+ componentName = positionalArgs[1] || "";
509
+ } else {
510
+ command = "add";
511
+ componentName = positionalArgs[0];
512
+ }
174
513
  }
175
514
 
176
515
  let availableComponents = Object.keys(REGISTRY);
@@ -181,102 +520,198 @@ const main = async () => {
181
520
  if (dirs.length > 0) availableComponents = dirs;
182
521
  }
183
522
 
184
- if (command === "add") {
185
- if (!componentName) {
523
+ if (command === "init") {
524
+ let theme = componentName; // support `cooterlabs init aeon`
525
+ if (!THEMES[theme]) {
186
526
  const response = await prompts({
187
527
  type: "select",
188
- name: "component",
189
- message: "Which component would you like to add?",
528
+ name: "theme",
529
+ message: "Which theme would you like to use?",
530
+ choices: Object.keys(THEMES).map((t) => ({
531
+ title: t === "aeon" ? "aeon (default)" : t,
532
+ value: t,
533
+ })),
534
+ });
535
+
536
+ if (!response.theme) {
537
+ console.log(chalk.red("❌ Theme selection cancelled."));
538
+ process.exit(1);
539
+ }
540
+ theme = response.theme;
541
+ }
542
+
543
+ // aeon is a Tailwind v4 @theme block appended at top level;
544
+ // legacy themes are :root vars wrapped in @layer base.
545
+ const isV4Theme = theme === "aeon";
546
+ const themeCSS = THEMES[theme];
547
+ let cssPath = path.join(cwd, "src", "index.css");
548
+
549
+ if (env.framework === "next") {
550
+ if (env.isAppDir) {
551
+ cssPath = fs.existsSync(path.join(cwd, "src", "app"))
552
+ ? path.join(cwd, "src", "app", "globals.css")
553
+ : path.join(cwd, "app", "globals.css");
554
+ } else {
555
+ cssPath = fs.existsSync(path.join(cwd, "src", "styles", "globals.css"))
556
+ ? path.join(cwd, "src", "styles", "globals.css")
557
+ : path.join(cwd, "styles", "globals.css");
558
+ }
559
+ } else {
560
+ cssPath = fs.existsSync(path.join(cwd, "src", "index.css"))
561
+ ? path.join(cwd, "src", "index.css")
562
+ : path.join(cwd, "src", "globals.css");
563
+ }
564
+
565
+ // Ensure dir exists
566
+ const cssDir = path.dirname(cssPath);
567
+ if (!fs.existsSync(cssDir)) {
568
+ fs.mkdirSync(cssDir, { recursive: true });
569
+ }
570
+
571
+ let cssContent = "";
572
+ if (fs.existsSync(cssPath)) {
573
+ cssContent = fs.readFileSync(cssPath, "utf-8");
574
+ }
575
+
576
+ const hasTailwindDirectives =
577
+ cssContent.includes("@tailwind base") ||
578
+ cssContent.includes('@import "tailwindcss');
579
+ if (!hasTailwindDirectives) {
580
+ cssContent = isV4Theme
581
+ ? `@import "tailwindcss";\n\n${cssContent}`
582
+ : `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n${cssContent}`;
583
+ }
584
+
585
+ const themeBlock = isV4Theme
586
+ ? `\n${themeCSS}\n`
587
+ : `\n@layer base {\n${themeCSS}\n}\n`;
588
+
589
+ const hasExistingVars = isV4Theme
590
+ ? cssContent.includes("@theme")
591
+ : cssContent.includes(":root {") || cssContent.includes("--background:");
592
+
593
+ if (hasExistingVars) {
594
+ console.log(
595
+ chalk.yellow(
596
+ `⚠️ Theme variables may already exist in ${cssPath}. Please append them manually if needed:\n${themeBlock}`,
597
+ ),
598
+ );
599
+ } else {
600
+ cssContent = `${cssContent}${themeBlock}`;
601
+ fs.writeFileSync(cssPath, cssContent);
602
+ console.log(chalk.green(`✅ Theme '${theme}' initialized in ${cssPath}`));
603
+ }
604
+ } else if (command === "add") {
605
+ let componentsToAdd = [];
606
+
607
+ if (isAll) {
608
+ componentsToAdd = availableComponents;
609
+ } else if (componentName) {
610
+ componentsToAdd = [componentName];
611
+ } else {
612
+ const response = await prompts({
613
+ type: "autocompleteMultiselect",
614
+ name: "components",
615
+ message:
616
+ "Which components would you like to add? (Space to select, Enter to submit, or use --all flag)",
190
617
  choices: availableComponents.map((c) => ({ title: c, value: c })),
191
618
  });
192
- componentName = response.component;
619
+ componentsToAdd = response.components || [];
193
620
  }
194
621
 
195
- if (!componentName) {
196
- console.log(chalk.red("❌ Operation cancelled."));
622
+ if (componentsToAdd.length === 0) {
623
+ console.log(chalk.red("❌ No components selected. Operation cancelled."));
197
624
  process.exit(1);
198
625
  }
199
626
 
200
- if (!availableComponents.includes(componentName)) {
201
- console.log(chalk.red(`❌ Component '${componentName}' not found.`));
627
+ // Check availability
628
+ const notFound = componentsToAdd.filter(
629
+ (c) => !availableComponents.includes(c),
630
+ );
631
+ if (notFound.length > 0) {
632
+ console.log(chalk.red(`❌ Components not found: ${notFound.join(", ")}`));
202
633
  process.exit(1);
203
634
  }
204
635
 
205
- const templatePath = path.join(templatesDir, componentName);
206
-
207
636
  // Create target dir
208
637
  const targetDir = path.join(cwd, env.componentsDir);
209
638
  if (!fs.existsSync(targetDir)) {
210
639
  fs.mkdirSync(targetDir, { recursive: true });
211
640
  }
212
641
 
213
- // Determine target path
214
- const extension = env.typescript ? ".tsx" : ".jsx";
215
- const componentFile = `${componentName}${extension}`;
216
- const targetPath = path.join(targetDir, componentFile);
217
-
218
- const spinner = ora(`Installing ${componentName}...`).start();
219
-
220
- try {
221
- let sourceContent = "";
222
- if (fs.existsSync(templatePath)) {
223
- const files = fs.readdirSync(templatePath);
224
- const srcFile = files.find(
225
- (f) => f.endsWith(".tsx") || f.endsWith(".jsx"),
226
- );
227
- if (srcFile) {
228
- sourceContent = fs.readFileSync(
229
- path.join(templatePath, srcFile),
230
- "utf-8",
642
+ let allDependencies = new Set();
643
+
644
+ for (const comp of componentsToAdd) {
645
+ const templatePath = path.join(templatesDir, comp);
646
+ const extension = env.typescript ? ".tsx" : ".jsx";
647
+ const componentFile = `${comp}${extension}`;
648
+ const targetPath = path.join(targetDir, componentFile);
649
+
650
+ const spinner = ora(`Installing ${comp}...`).start();
651
+
652
+ try {
653
+ let sourceContent = "";
654
+ if (fs.existsSync(templatePath)) {
655
+ const files = fs.readdirSync(templatePath);
656
+ const srcFile = files.find(
657
+ (f) => f.endsWith(".tsx") || f.endsWith(".jsx"),
231
658
  );
659
+ if (srcFile) {
660
+ sourceContent = fs.readFileSync(
661
+ path.join(templatePath, srcFile),
662
+ "utf-8",
663
+ );
664
+ } else if (files.length > 0) {
665
+ sourceContent = fs.readFileSync(
666
+ path.join(templatePath, files[0]),
667
+ "utf-8",
668
+ );
669
+ }
232
670
  } else {
233
- sourceContent = fs.readFileSync(
234
- path.join(templatePath, files[0]),
235
- "utf-8",
671
+ sourceContent = `import React from 'react';\n\nexport const ${REGISTRY[comp]?.name || "Component"} = () => { return <div>${comp}</div>; };\n`;
672
+ }
673
+
674
+ // Format code
675
+ let formattedContent = sourceContent;
676
+ try {
677
+ formattedContent = await formatFile(sourceContent, targetPath);
678
+ } catch (err) {
679
+ console.log(
680
+ chalk.yellow(
681
+ `\n⚠️ Formatting failed for ${comp}. Using original formatting.`,
682
+ ),
236
683
  );
237
684
  }
238
- } else {
239
- // Fallback or generic content simply to show it works
240
- sourceContent = `import React from 'react';\n\nexport const ${REGISTRY[componentName]?.name || "Component"} = () => { return <div>${componentName}</div>; };\n`;
241
- }
242
685
 
243
- // Hacky TS -> JS stripping if they don't use TypeScript
244
- if (!env.typescript) {
245
- // Simple type stripping if needed, but omitted for stability.
246
- // It's recommended to publish compiled JS if targeting non-TS,
247
- // or let the user fix up the .jsx file.
248
- }
686
+ fs.writeFileSync(targetPath, formattedContent, "utf-8");
687
+ spinner.succeed(`Created ${componentFile} in ${env.componentsDir}`);
249
688
 
250
- // Format code
251
- let formattedContent = sourceContent;
252
- try {
253
- formattedContent = await formatFile(sourceContent, targetPath);
689
+ // Track dependencies
690
+ const registryEntry = REGISTRY[comp];
691
+ if (registryEntry && registryEntry.dependencies) {
692
+ registryEntry.dependencies.forEach((d) => allDependencies.add(d));
693
+ }
254
694
  } catch (err) {
255
- console.log(
256
- chalk.yellow("⚠️ Formatting failed. Using original formatting."),
257
- );
695
+ spinner.fail(`Failed to add ${comp}`);
696
+ console.error(chalk.red(err.message));
258
697
  }
698
+ }
259
699
 
260
- // Write file
261
- fs.writeFileSync(targetPath, formattedContent, "utf-8");
262
-
263
- spinner.succeed(`Created ${componentFile} in ${env.componentsDir}`);
264
-
265
- // Install dependencies
266
- const registryEntry = REGISTRY[componentName];
267
- if (registryEntry && registryEntry.dependencies?.length > 0) {
268
- const depSpinner = ora(
269
- `Installing dependencies: ${registryEntry.dependencies.join(", ")}...`,
270
- ).start();
271
- installDependencies(registryEntry.dependencies, env.pkgCommand);
272
- depSpinner.succeed(`Installed dependencies.`);
700
+ if (allDependencies.size > 0) {
701
+ const depsArray = Array.from(allDependencies);
702
+ const depSpinner = ora(
703
+ `Installing collective dependencies: ${depsArray.join(", ")}...`,
704
+ ).start();
705
+ try {
706
+ installDependencies(depsArray, env.pkgCommand);
707
+ depSpinner.succeed(`Dependencies installed successfully.`);
708
+ } catch (err) {
709
+ depSpinner.fail(`Failed to install dependencies.`);
710
+ console.error(chalk.red(err.message));
273
711
  }
274
-
275
- console.log(chalk.green(`\n✅ ${componentName} added successfully!`));
276
- } catch (err) {
277
- spinner.fail(`Failed to add ${componentName}`);
278
- console.error(chalk.red(err.message));
279
712
  }
713
+
714
+ console.log(chalk.green(`\n✅ Finished adding components!`));
280
715
  } else {
281
716
  console.log(chalk.red(`❌ Unknown command: ${command}`));
282
717
  }
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "cooterlabs",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Component library CLI for CooterLabs apps",
5
5
  "files": [
6
6
  "bin",
7
7
  "templates"
8
8
  ],
9
- "main": "index.js",
10
9
  "scripts": {
11
10
  "test": "echo \"Error: no test specified\" && exit 1"
12
11
  },
13
12
  "bin": {
14
- "cooterlabs": "./bin/index.js"
13
+ "cooterlabs": "bin/index.js"
15
14
  },
16
- "keywords": [],
15
+ "keywords": [
16
+ "aeon",
17
+ "react",
18
+ "typescript",
19
+ "cli",
20
+ "component-library"
21
+ ],
17
22
  "author": "",
18
23
  "license": "ISC",
19
24
  "type": "module",
@@ -25,5 +30,8 @@
25
30
  "prettier": "^3.8.1",
26
31
  "prompts": "^2.4.2",
27
32
  "tailwind-merge": "^3.5.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^19.2.14"
28
36
  }
29
37
  }