@journal-ds/cli 1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +1108 -0
  5. package/package.json +61 -0
  6. package/registry/accordion.tsx +66 -0
  7. package/registry/alert-dialog.tsx +157 -0
  8. package/registry/alert.tsx +66 -0
  9. package/registry/aspect-ratio.tsx +11 -0
  10. package/registry/avatar.tsx +53 -0
  11. package/registry/badge.tsx +46 -0
  12. package/registry/breadcrumb.tsx +109 -0
  13. package/registry/button.tsx +59 -0
  14. package/registry/calendar.tsx +213 -0
  15. package/registry/card.tsx +92 -0
  16. package/registry/carousel.tsx +241 -0
  17. package/registry/chart.tsx +353 -0
  18. package/registry/checkbox.tsx +32 -0
  19. package/registry/collapsible.tsx +33 -0
  20. package/registry/command.tsx +186 -0
  21. package/registry/context-menu.tsx +252 -0
  22. package/registry/dialog.tsx +143 -0
  23. package/registry/drawer.tsx +135 -0
  24. package/registry/dropdown-menu.tsx +257 -0
  25. package/registry/form.tsx +167 -0
  26. package/registry/hover-card.tsx +44 -0
  27. package/registry/input-otp.tsx +77 -0
  28. package/registry/input.tsx +21 -0
  29. package/registry/label.tsx +24 -0
  30. package/registry/menubar.tsx +276 -0
  31. package/registry/navigation-menu.tsx +168 -0
  32. package/registry/pagination.tsx +127 -0
  33. package/registry/popover.tsx +48 -0
  34. package/registry/progress.tsx +31 -0
  35. package/registry/radio-group.tsx +45 -0
  36. package/registry/resizable.tsx +56 -0
  37. package/registry/scroll-area.tsx +58 -0
  38. package/registry/select.tsx +185 -0
  39. package/registry/separator.tsx +28 -0
  40. package/registry/sheet.tsx +139 -0
  41. package/registry/sidebar.tsx +726 -0
  42. package/registry/skeleton.tsx +13 -0
  43. package/registry/slider.tsx +63 -0
  44. package/registry/sonner.tsx +25 -0
  45. package/registry/switch.tsx +31 -0
  46. package/registry/table.tsx +116 -0
  47. package/registry/tabs.tsx +66 -0
  48. package/registry/textarea.tsx +18 -0
  49. package/registry/toast.tsx +129 -0
  50. package/registry/toaster.tsx +35 -0
  51. package/registry/toggle-group.tsx +73 -0
  52. package/registry/toggle.tsx +47 -0
  53. package/registry/tooltip.tsx +61 -0
  54. package/registry/use-mobile.ts +19 -0
  55. package/registry/use-toast.ts +194 -0
  56. package/registry/utils.ts +6 -0
  57. package/templates/globals.css +322 -0
  58. package/templates/utils.ts +6 -0
package/dist/index.js ADDED
@@ -0,0 +1,1108 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
3
+ import { dirname, resolve } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import * as readline from 'readline/promises';
6
+ import { stdout, stdin } from 'process';
7
+
8
+ var DEFAULT_CONFIG = {
9
+ $schema: "https://journal-ds.dev/schema.json",
10
+ style: "default",
11
+ rsc: true,
12
+ tsx: true,
13
+ tailwind: {
14
+ config: "tailwind.config.ts",
15
+ css: "src/app/globals.css",
16
+ baseColor: "neutral",
17
+ cssVariables: true
18
+ },
19
+ aliases: {
20
+ components: "@/components",
21
+ utils: "@/lib/utils",
22
+ ui: "@/components/ui",
23
+ lib: "@/lib",
24
+ hooks: "@/hooks"
25
+ },
26
+ iconLibrary: "lucide"
27
+ };
28
+ function configPath(cwd = process.cwd()) {
29
+ return resolve(cwd, "journal.json");
30
+ }
31
+ function readConfig(cwd = process.cwd()) {
32
+ const path = configPath(cwd);
33
+ if (!existsSync(path)) {
34
+ throw new ConfigError(
35
+ `journal.json not found at ${path}.
36
+ Run \`npx @journal-ds/cli init\` first to create one.`
37
+ );
38
+ }
39
+ const raw = readFileSync(path, "utf8");
40
+ try {
41
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
42
+ } catch {
43
+ throw new ConfigError(`Failed to parse journal.json at ${path}.`);
44
+ }
45
+ }
46
+ function writeConfig(config, cwd = process.cwd()) {
47
+ const path = configPath(cwd);
48
+ const json = JSON.stringify(config, null, 2) + "\n";
49
+ writeFileSync(path, json, "utf8");
50
+ }
51
+ var ConfigError = class extends Error {
52
+ constructor(message) {
53
+ super(message);
54
+ this.name = "ConfigError";
55
+ }
56
+ };
57
+ var rl = null;
58
+ function getRL() {
59
+ if (!rl) {
60
+ rl = readline.createInterface({
61
+ input: stdin,
62
+ output: stdout,
63
+ terminal: true
64
+ });
65
+ }
66
+ return rl;
67
+ }
68
+ async function ask(question, defaultValue) {
69
+ const rl2 = getRL();
70
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
71
+ const answer = await rl2.question(`${question}${suffix}: `);
72
+ const trimmed = answer.trim();
73
+ return trimmed || defaultValue || "";
74
+ }
75
+ async function askSelect(question, options, defaultValue) {
76
+ const rl2 = getRL();
77
+ console.log(question);
78
+ options.forEach((opt, i) => {
79
+ const marker = opt === defaultValue ? " \u2190 default" : "";
80
+ console.log(` ${i + 1}) ${opt}${marker}`);
81
+ });
82
+ const defaultIndex = defaultValue ? options.indexOf(defaultValue) + 1 : void 0;
83
+ const suffix = defaultIndex ? ` (${defaultIndex})` : "";
84
+ const answer = await rl2.question(`Choice${suffix}: `);
85
+ const trimmed = answer.trim();
86
+ if (!trimmed && defaultIndex) return defaultValue;
87
+ const idx = parseInt(trimmed, 10);
88
+ if (isNaN(idx) || idx < 1 || idx > options.length) {
89
+ console.log(" Invalid choice, using default.");
90
+ return defaultValue || options[0];
91
+ }
92
+ return options[idx - 1];
93
+ }
94
+ async function askConfirm(question, defaultValue = false) {
95
+ const rl2 = getRL();
96
+ const hint = defaultValue ? "Y/n" : "y/N";
97
+ const answer = await rl2.question(`${question} [${hint}]: `);
98
+ const trimmed = answer.trim().toLowerCase();
99
+ if (!trimmed) return defaultValue;
100
+ return trimmed === "y" || trimmed === "yes";
101
+ }
102
+ function closePrompts() {
103
+ if (rl) {
104
+ rl.close();
105
+ rl = null;
106
+ }
107
+ }
108
+
109
+ // src/lib/log.ts
110
+ var COLORS = {
111
+ reset: "\x1B[0m",
112
+ bold: "\x1B[1m",
113
+ dim: "\x1B[2m",
114
+ red: "\x1B[31m",
115
+ green: "\x1B[32m",
116
+ yellow: "\x1B[33m",
117
+ blue: "\x1B[34m",
118
+ magenta: "\x1B[35m",
119
+ cyan: "\x1B[36m",
120
+ gray: "\x1B[90m",
121
+ // Journal-inspired
122
+ burgundy: "\x1B[38;5;88m",
123
+ forest: "\x1B[38;5;22m",
124
+ gold: "\x1B[38;5;136m",
125
+ sepia: "\x1B[38;5;95m"
126
+ };
127
+ function paint(color, text) {
128
+ return `${COLORS[color]}${text}${COLORS.reset}`;
129
+ }
130
+ var log = {
131
+ info(msg) {
132
+ console.log(msg);
133
+ },
134
+ step(msg) {
135
+ console.log(paint("cyan", "\u25C6"), msg);
136
+ },
137
+ success(msg) {
138
+ console.log(paint("forest", "\u2713"), paint("forest", msg));
139
+ },
140
+ warn(msg) {
141
+ console.log(paint("gold", "\u26A0"), paint("gold", msg));
142
+ },
143
+ error(msg) {
144
+ console.error(paint("red", "\u2717"), paint("red", msg));
145
+ },
146
+ created(path) {
147
+ console.log(paint("forest", " \u2713 Created"), paint("gray", path));
148
+ },
149
+ updated(path) {
150
+ console.log(paint("gold", " \u21BB Updated"), paint("gray", path));
151
+ },
152
+ skipped(path, reason) {
153
+ console.log(paint("sepia", " \u2192 Skipped"), paint("gray", `${path} (${reason})`));
154
+ },
155
+ dryRun(path) {
156
+ console.log(paint("sepia", " \u2192 Would create"), paint("gray", path));
157
+ },
158
+ banner() {
159
+ const line = "\u2500".repeat(52);
160
+ console.log();
161
+ console.log(paint("burgundy", line));
162
+ console.log(
163
+ paint("burgundy", " Journal Design System"),
164
+ paint("sepia", "\xB7 CLI v1.0.0")
165
+ );
166
+ console.log(paint("burgundy", line));
167
+ console.log();
168
+ },
169
+ group(title) {
170
+ console.log();
171
+ console.log(paint("bold", title));
172
+ },
173
+ dim(msg) {
174
+ console.log(paint("gray", msg));
175
+ }
176
+ };
177
+
178
+ // src/commands/init.ts
179
+ var __dirname$1 = dirname(fileURLToPath(import.meta.url));
180
+ function templatesDir() {
181
+ if (__dirname$1.endsWith("dist")) {
182
+ return resolve(__dirname$1, "..", "templates");
183
+ }
184
+ return resolve(__dirname$1, "..", "..", "templates");
185
+ }
186
+ function readTemplate(name) {
187
+ return readFileSync(resolve(templatesDir(), name), "utf8");
188
+ }
189
+ async function initCommand(opts) {
190
+ const { cwd, yes } = opts;
191
+ log.banner();
192
+ const configPath2 = resolve(cwd, "journal.json");
193
+ if (existsSync(configPath2)) {
194
+ if (!yes) {
195
+ const overwrite = await askConfirm(
196
+ "journal.json already exists. Overwrite?",
197
+ false
198
+ );
199
+ if (!overwrite) {
200
+ log.warn("Aborted. Keeping existing journal.json.");
201
+ closePrompts();
202
+ return;
203
+ }
204
+ }
205
+ }
206
+ let config = { ...DEFAULT_CONFIG };
207
+ if (!opts.defaults && !yes) {
208
+ log.group("Let's configure your project.");
209
+ console.log();
210
+ config.style = await askSelect(
211
+ "Which style would you like to use?",
212
+ ["default", "new-york"],
213
+ "default"
214
+ );
215
+ config.tailwind.baseColor = await askSelect(
216
+ "Which color would you like to use as the base color?",
217
+ ["neutral", "gray", "slate", "stone", "zinc"],
218
+ "neutral"
219
+ );
220
+ const cssPath = await ask(
221
+ "Where is your global CSS file?",
222
+ config.tailwind.css
223
+ );
224
+ config.tailwind.css = cssPath;
225
+ const tailwindConfigPath = await ask(
226
+ "Where is your tailwind.config?",
227
+ config.tailwind.config
228
+ );
229
+ config.tailwind.config = tailwindConfigPath;
230
+ config.aliases.components = await ask(
231
+ "Configure the import alias for components:",
232
+ config.aliases.components
233
+ );
234
+ config.aliases.utils = await ask(
235
+ "Configure the import alias for utils:",
236
+ config.aliases.utils
237
+ );
238
+ config.aliases.ui = await ask(
239
+ "Configure the import alias for ui:",
240
+ config.aliases.ui
241
+ );
242
+ config.aliases.hooks = await ask(
243
+ "Configure the import alias for hooks:",
244
+ config.aliases.hooks
245
+ );
246
+ config.iconLibrary = await askSelect(
247
+ "Which icon library would you like to use?",
248
+ ["lucide", "radix"],
249
+ "lucide"
250
+ );
251
+ const isRSC = await askConfirm(
252
+ "Are you using React Server Components?",
253
+ true
254
+ );
255
+ config.rsc = isRSC;
256
+ closePrompts();
257
+ }
258
+ log.group("Writing configuration...");
259
+ writeConfig(config, cwd);
260
+ log.created(configPath2);
261
+ const cssAbs = resolve(cwd, config.tailwind.css);
262
+ if (existsSync(cssAbs)) {
263
+ const existing = readFileSync(cssAbs, "utf8");
264
+ const hasJournalVars = existing.includes("--journal-paper");
265
+ if (hasJournalVars) {
266
+ log.skipped(cssAbs, "already has Journal theme");
267
+ } else if (!opts.defaults && !yes) {
268
+ appendJournalTheme(cssAbs, existing);
269
+ log.updated(cssAbs);
270
+ } else {
271
+ appendJournalTheme(cssAbs, existing);
272
+ log.updated(cssAbs);
273
+ }
274
+ } else {
275
+ mkdirSync(dirname(cssAbs), { recursive: true });
276
+ writeFileSync(cssAbs, readTemplate("globals.css"), "utf8");
277
+ log.created(cssAbs);
278
+ }
279
+ const utilsAbs = resolve(cwd, "src/lib/utils.ts");
280
+ const utilsAliasBase = config.aliases.utils.replace(/\/utils$/, "");
281
+ const utilsAbsFromAlias = resolve(cwd, utilsAliasBase.replace(/^@\/?/, "src/"), "utils.ts");
282
+ const utilsTarget = existsSync(utilsAbsFromAlias) ? utilsAbsFromAlias : utilsAbs;
283
+ if (existsSync(utilsTarget)) {
284
+ log.skipped(utilsTarget, "already exists");
285
+ } else {
286
+ mkdirSync(dirname(utilsTarget), { recursive: true });
287
+ writeFileSync(utilsTarget, readTemplate("utils.ts"), "utf8");
288
+ log.created(utilsTarget);
289
+ }
290
+ log.group("Done!");
291
+ console.log();
292
+ log.success("Journal Design System is configured.");
293
+ console.log();
294
+ log.dim("Next steps:");
295
+ console.log(" Add a component: npx @journal-ds/cli add button");
296
+ console.log(" Add multiple: npx @journal-ds/cli add button card dialog");
297
+ console.log(" Add everything: npx @journal-ds/cli add --all");
298
+ console.log();
299
+ }
300
+ function appendJournalTheme(path, existing) {
301
+ const journalCss = readTemplate("globals.css");
302
+ const hasTailwindImport = existing.includes('@import "tailwindcss"') || existing.includes("@import 'tailwindcss'");
303
+ let merged;
304
+ if (hasTailwindImport) {
305
+ const journalStripped = journalCss.replace(/@import\s+["']tailwindcss["'];?\n?/g, "").replace(/@import\s+["']tw-animate-css["'];?\n?/g, "");
306
+ merged = existing.trimEnd() + "\n\n" + journalStripped;
307
+ } else {
308
+ merged = journalCss + "\n\n" + existing;
309
+ }
310
+ writeFileSync(path, merged, "utf8");
311
+ }
312
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
313
+ function registryDir() {
314
+ if (__dirname2.endsWith("dist")) {
315
+ return resolve(__dirname2, "..", "registry");
316
+ }
317
+ if (__dirname2.endsWith("src") || __dirname2.endsWith("src/lib") || __dirname2.endsWith("src/commands")) {
318
+ return resolve(__dirname2, "..", "..", "registry");
319
+ }
320
+ return resolve(__dirname2, "..", "registry");
321
+ }
322
+ var registry = {
323
+ // ── Forms ──
324
+ button: {
325
+ slug: "button",
326
+ name: "Button",
327
+ file: "button.tsx",
328
+ category: "forms",
329
+ npmDeps: ["@radix-ui/react-slot", "class-variance-authority"],
330
+ registryDeps: ["utils"]
331
+ },
332
+ input: {
333
+ slug: "input",
334
+ name: "Input",
335
+ file: "input.tsx",
336
+ category: "forms",
337
+ npmDeps: [],
338
+ registryDeps: ["utils"]
339
+ },
340
+ textarea: {
341
+ slug: "textarea",
342
+ name: "Textarea",
343
+ file: "textarea.tsx",
344
+ category: "forms",
345
+ npmDeps: [],
346
+ registryDeps: ["utils"]
347
+ },
348
+ label: {
349
+ slug: "label",
350
+ name: "Label",
351
+ file: "label.tsx",
352
+ category: "forms",
353
+ npmDeps: ["@radix-ui/react-label"],
354
+ registryDeps: ["utils"]
355
+ },
356
+ checkbox: {
357
+ slug: "checkbox",
358
+ name: "Checkbox",
359
+ file: "checkbox.tsx",
360
+ category: "forms",
361
+ npmDeps: ["@radix-ui/react-checkbox"],
362
+ registryDeps: ["utils"]
363
+ },
364
+ switch: {
365
+ slug: "switch",
366
+ name: "Switch",
367
+ file: "switch.tsx",
368
+ category: "forms",
369
+ npmDeps: ["@radix-ui/react-switch"],
370
+ registryDeps: ["utils"]
371
+ },
372
+ "radio-group": {
373
+ slug: "radio-group",
374
+ name: "RadioGroup",
375
+ file: "radio-group.tsx",
376
+ category: "forms",
377
+ npmDeps: ["@radix-ui/react-radio-group"],
378
+ registryDeps: ["utils"]
379
+ },
380
+ select: {
381
+ slug: "select",
382
+ name: "Select",
383
+ file: "select.tsx",
384
+ category: "forms",
385
+ npmDeps: ["@radix-ui/react-select"],
386
+ registryDeps: ["utils"]
387
+ },
388
+ slider: {
389
+ slug: "slider",
390
+ name: "Slider",
391
+ file: "slider.tsx",
392
+ category: "forms",
393
+ npmDeps: ["@radix-ui/react-slider"],
394
+ registryDeps: ["utils"]
395
+ },
396
+ toggle: {
397
+ slug: "toggle",
398
+ name: "Toggle",
399
+ file: "toggle.tsx",
400
+ category: "forms",
401
+ npmDeps: ["@radix-ui/react-toggle", "class-variance-authority"],
402
+ registryDeps: ["utils"]
403
+ },
404
+ "toggle-group": {
405
+ slug: "toggle-group",
406
+ name: "ToggleGroup",
407
+ file: "toggle-group.tsx",
408
+ category: "forms",
409
+ npmDeps: ["@radix-ui/react-toggle-group"],
410
+ registryDeps: ["toggle", "utils"]
411
+ },
412
+ "input-otp": {
413
+ slug: "input-otp",
414
+ name: "InputOTP",
415
+ file: "input-otp.tsx",
416
+ category: "forms",
417
+ npmDeps: ["input-otp"],
418
+ registryDeps: ["utils"]
419
+ },
420
+ form: {
421
+ slug: "form",
422
+ name: "Form",
423
+ file: "form.tsx",
424
+ category: "forms",
425
+ npmDeps: ["react-hook-form", "@radix-ui/react-label", "@radix-ui/react-slot"],
426
+ registryDeps: ["label", "utils"]
427
+ },
428
+ // ── Layout ──
429
+ card: {
430
+ slug: "card",
431
+ name: "Card",
432
+ file: "card.tsx",
433
+ category: "layout",
434
+ npmDeps: [],
435
+ registryDeps: ["utils"]
436
+ },
437
+ separator: {
438
+ slug: "separator",
439
+ name: "Separator",
440
+ file: "separator.tsx",
441
+ category: "layout",
442
+ npmDeps: ["@radix-ui/react-separator"],
443
+ registryDeps: ["utils"]
444
+ },
445
+ "aspect-ratio": {
446
+ slug: "aspect-ratio",
447
+ name: "AspectRatio",
448
+ file: "aspect-ratio.tsx",
449
+ category: "layout",
450
+ npmDeps: ["@radix-ui/react-aspect-ratio"],
451
+ registryDeps: ["utils"]
452
+ },
453
+ resizable: {
454
+ slug: "resizable",
455
+ name: "Resizable",
456
+ file: "resizable.tsx",
457
+ category: "layout",
458
+ npmDeps: ["react-resizable-panels"],
459
+ registryDeps: ["utils"]
460
+ },
461
+ "scroll-area": {
462
+ slug: "scroll-area",
463
+ name: "ScrollArea",
464
+ file: "scroll-area.tsx",
465
+ category: "layout",
466
+ npmDeps: ["@radix-ui/react-scroll-area"],
467
+ registryDeps: ["utils"]
468
+ },
469
+ // ── Display ──
470
+ badge: {
471
+ slug: "badge",
472
+ name: "Badge",
473
+ file: "badge.tsx",
474
+ category: "display",
475
+ npmDeps: ["class-variance-authority"],
476
+ registryDeps: ["utils"]
477
+ },
478
+ avatar: {
479
+ slug: "avatar",
480
+ name: "Avatar",
481
+ file: "avatar.tsx",
482
+ category: "display",
483
+ npmDeps: ["@radix-ui/react-avatar"],
484
+ registryDeps: ["utils"]
485
+ },
486
+ skeleton: {
487
+ slug: "skeleton",
488
+ name: "Skeleton",
489
+ file: "skeleton.tsx",
490
+ category: "display",
491
+ npmDeps: [],
492
+ registryDeps: ["utils"]
493
+ },
494
+ progress: {
495
+ slug: "progress",
496
+ name: "Progress",
497
+ file: "progress.tsx",
498
+ category: "display",
499
+ npmDeps: ["@radix-ui/react-progress"],
500
+ registryDeps: ["utils"]
501
+ },
502
+ table: {
503
+ slug: "table",
504
+ name: "Table",
505
+ file: "table.tsx",
506
+ category: "display",
507
+ npmDeps: [],
508
+ registryDeps: ["utils"]
509
+ },
510
+ alert: {
511
+ slug: "alert",
512
+ name: "Alert",
513
+ file: "alert.tsx",
514
+ category: "display",
515
+ npmDeps: ["class-variance-authority"],
516
+ registryDeps: ["utils"]
517
+ },
518
+ accordion: {
519
+ slug: "accordion",
520
+ name: "Accordion",
521
+ file: "accordion.tsx",
522
+ category: "display",
523
+ npmDeps: ["@radix-ui/react-accordion"],
524
+ registryDeps: ["utils"]
525
+ },
526
+ collapsible: {
527
+ slug: "collapsible",
528
+ name: "Collapsible",
529
+ file: "collapsible.tsx",
530
+ category: "display",
531
+ npmDeps: ["@radix-ui/react-collapsible"],
532
+ registryDeps: ["utils"]
533
+ },
534
+ tabs: {
535
+ slug: "tabs",
536
+ name: "Tabs",
537
+ file: "tabs.tsx",
538
+ category: "display",
539
+ npmDeps: ["@radix-ui/react-tabs"],
540
+ registryDeps: ["utils"]
541
+ },
542
+ // ── Overlays ──
543
+ dialog: {
544
+ slug: "dialog",
545
+ name: "Dialog",
546
+ file: "dialog.tsx",
547
+ category: "overlays",
548
+ npmDeps: ["@radix-ui/react-dialog"],
549
+ registryDeps: ["utils"]
550
+ },
551
+ sheet: {
552
+ slug: "sheet",
553
+ name: "Sheet",
554
+ file: "sheet.tsx",
555
+ category: "overlays",
556
+ npmDeps: ["@radix-ui/react-dialog"],
557
+ registryDeps: ["utils"]
558
+ },
559
+ drawer: {
560
+ slug: "drawer",
561
+ name: "Drawer",
562
+ file: "drawer.tsx",
563
+ category: "overlays",
564
+ npmDeps: ["vaul"],
565
+ registryDeps: ["utils"]
566
+ },
567
+ popover: {
568
+ slug: "popover",
569
+ name: "Popover",
570
+ file: "popover.tsx",
571
+ category: "overlays",
572
+ npmDeps: ["@radix-ui/react-popover"],
573
+ registryDeps: ["utils"]
574
+ },
575
+ tooltip: {
576
+ slug: "tooltip",
577
+ name: "Tooltip",
578
+ file: "tooltip.tsx",
579
+ category: "overlays",
580
+ npmDeps: ["@radix-ui/react-tooltip"],
581
+ registryDeps: ["utils"]
582
+ },
583
+ "hover-card": {
584
+ slug: "hover-card",
585
+ name: "HoverCard",
586
+ file: "hover-card.tsx",
587
+ category: "overlays",
588
+ npmDeps: ["@radix-ui/react-hover-card"],
589
+ registryDeps: ["utils"]
590
+ },
591
+ "alert-dialog": {
592
+ slug: "alert-dialog",
593
+ name: "AlertDialog",
594
+ file: "alert-dialog.tsx",
595
+ category: "overlays",
596
+ npmDeps: ["@radix-ui/react-alert-dialog"],
597
+ registryDeps: ["utils"]
598
+ },
599
+ // ── Navigation ──
600
+ "navigation-menu": {
601
+ slug: "navigation-menu",
602
+ name: "NavigationMenu",
603
+ file: "navigation-menu.tsx",
604
+ category: "navigation",
605
+ npmDeps: ["@radix-ui/react-navigation-menu"],
606
+ registryDeps: ["utils"]
607
+ },
608
+ breadcrumb: {
609
+ slug: "breadcrumb",
610
+ name: "Breadcrumb",
611
+ file: "breadcrumb.tsx",
612
+ category: "navigation",
613
+ npmDeps: ["@radix-ui/react-slot"],
614
+ registryDeps: ["utils"]
615
+ },
616
+ pagination: {
617
+ slug: "pagination",
618
+ name: "Pagination",
619
+ file: "pagination.tsx",
620
+ category: "navigation",
621
+ npmDeps: [],
622
+ registryDeps: ["utils"]
623
+ },
624
+ "dropdown-menu": {
625
+ slug: "dropdown-menu",
626
+ name: "DropdownMenu",
627
+ file: "dropdown-menu.tsx",
628
+ category: "navigation",
629
+ npmDeps: ["@radix-ui/react-dropdown-menu"],
630
+ registryDeps: ["utils"]
631
+ },
632
+ "context-menu": {
633
+ slug: "context-menu",
634
+ name: "ContextMenu",
635
+ file: "context-menu.tsx",
636
+ category: "navigation",
637
+ npmDeps: ["@radix-ui/react-context-menu"],
638
+ registryDeps: ["utils"]
639
+ },
640
+ menubar: {
641
+ slug: "menubar",
642
+ name: "Menubar",
643
+ file: "menubar.tsx",
644
+ category: "navigation",
645
+ npmDeps: ["@radix-ui/react-menubar"],
646
+ registryDeps: ["utils"]
647
+ },
648
+ command: {
649
+ slug: "command",
650
+ name: "Command",
651
+ file: "command.tsx",
652
+ category: "navigation",
653
+ npmDeps: ["cmdk"],
654
+ registryDeps: ["dialog", "utils"]
655
+ },
656
+ // ── Feedback ──
657
+ toast: {
658
+ slug: "toast",
659
+ name: "Toast",
660
+ file: "toast.tsx",
661
+ category: "feedback",
662
+ npmDeps: ["@radix-ui/react-toast"],
663
+ registryDeps: ["utils"]
664
+ },
665
+ toaster: {
666
+ slug: "toaster",
667
+ name: "Toaster",
668
+ file: "toaster.tsx",
669
+ category: "feedback",
670
+ npmDeps: ["@radix-ui/react-toast"],
671
+ registryDeps: ["toast", "utils", "use-toast"]
672
+ },
673
+ sonner: {
674
+ slug: "sonner",
675
+ name: "Sonner",
676
+ file: "sonner.tsx",
677
+ category: "feedback",
678
+ npmDeps: ["sonner", "next-themes"],
679
+ registryDeps: ["utils"]
680
+ },
681
+ calendar: {
682
+ slug: "calendar",
683
+ name: "Calendar",
684
+ file: "calendar.tsx",
685
+ category: "feedback",
686
+ npmDeps: ["react-day-picker"],
687
+ registryDeps: ["button", "utils"]
688
+ },
689
+ carousel: {
690
+ slug: "carousel",
691
+ name: "Carousel",
692
+ file: "carousel.tsx",
693
+ category: "feedback",
694
+ npmDeps: ["embla-carousel-react"],
695
+ registryDeps: ["button", "utils"]
696
+ },
697
+ chart: {
698
+ slug: "chart",
699
+ name: "Chart",
700
+ file: "chart.tsx",
701
+ category: "feedback",
702
+ npmDeps: ["recharts"],
703
+ registryDeps: ["utils"]
704
+ },
705
+ sidebar: {
706
+ slug: "sidebar",
707
+ name: "Sidebar",
708
+ file: "sidebar.tsx",
709
+ category: "feedback",
710
+ npmDeps: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
711
+ registryDeps: ["button", "separator", "sheet", "tooltip", "utils", "use-mobile"]
712
+ },
713
+ // ── Hooks ──
714
+ "use-toast": {
715
+ slug: "use-toast",
716
+ name: "use-toast",
717
+ file: "use-toast.ts",
718
+ category: "hooks",
719
+ npmDeps: ["@radix-ui/react-toast"],
720
+ registryDeps: []
721
+ },
722
+ "use-mobile": {
723
+ slug: "use-mobile",
724
+ name: "use-mobile",
725
+ file: "use-mobile.ts",
726
+ category: "hooks",
727
+ npmDeps: [],
728
+ registryDeps: []
729
+ },
730
+ // ── Utilities ──
731
+ utils: {
732
+ slug: "utils",
733
+ name: "cn",
734
+ file: "utils.ts",
735
+ category: "utilities",
736
+ npmDeps: ["clsx", "tailwind-merge"],
737
+ registryDeps: []
738
+ }
739
+ };
740
+ var UTILS_SLUG = "utils";
741
+ function targetDir(slug) {
742
+ if (slug === UTILS_SLUG) return "lib";
743
+ if (slug.startsWith("use-")) return "hooks";
744
+ return "ui";
745
+ }
746
+ function readRegistrySource(file) {
747
+ const path = resolve(registryDir(), file);
748
+ return readFileSync(path, "utf8");
749
+ }
750
+ function resolveTree(slugs) {
751
+ const visited = /* @__PURE__ */ new Set();
752
+ const ordered = [];
753
+ function visit(slug) {
754
+ if (visited.has(slug)) return;
755
+ visited.add(slug);
756
+ const entry = registry[slug];
757
+ if (!entry) return;
758
+ for (const dep of entry.registryDeps) {
759
+ visit(dep);
760
+ }
761
+ ordered.push(slug);
762
+ }
763
+ for (const slug of slugs) visit(slug);
764
+ return ordered;
765
+ }
766
+
767
+ // src/lib/transform.ts
768
+ function transformSource(source, config) {
769
+ const { aliases } = config;
770
+ let out = source;
771
+ out = out.replace(
772
+ /from\s+["']@\/lib\/utils["']/g,
773
+ `from "${aliases.utils}"`
774
+ );
775
+ out = out.replace(
776
+ /from\s+["']@\/components\/ui\/([^"']+)["']/g,
777
+ (_, name) => `from "${aliases.ui}/${name}"`
778
+ );
779
+ out = out.replace(
780
+ /from\s+["']@\/hooks\/([^"']+)["']/g,
781
+ (_, name) => `from "${aliases.hooks}/${name}"`
782
+ );
783
+ out = out.replace(
784
+ /from\s+["']@\/components\/([^"']+)["']/g,
785
+ (_, name) => `from "${aliases.components}/${name}"`
786
+ );
787
+ out = out.replace(
788
+ /from\s+["']@\/lib\/([^"']+)["']/g,
789
+ (_, name) => `from "${aliases.lib}/${name}"`
790
+ );
791
+ out = out.replace(
792
+ /from\s+["']\.\.\/lib\/utils["']/g,
793
+ `from "${aliases.utils}"`
794
+ );
795
+ out = out.replace(
796
+ /from\s+["']\.\/([^"']+)["']/g,
797
+ (_, name) => {
798
+ if (name.includes(".")) return `from "./${name}"`;
799
+ return `from "${aliases.ui}/${name}"`;
800
+ }
801
+ );
802
+ out = out.replace(
803
+ /from\s+["']\.\.\/hooks\/([^"']+)["']/g,
804
+ (_, name) => `from "${aliases.hooks}/${name}"`
805
+ );
806
+ out = out.replace(
807
+ /from\s+["']\.\.\/components\/ui\/([^"']+)["']/g,
808
+ (_, name) => `from "${aliases.ui}/${name}"`
809
+ );
810
+ out = out.replace(
811
+ /from\s+["']\.\.\/components\/([^"']+)["']/g,
812
+ (_, name) => `from "${aliases.components}/${name}"`
813
+ );
814
+ return out;
815
+ }
816
+
817
+ // src/commands/add.ts
818
+ async function addCommand(opts) {
819
+ const { cwd, all, overwrite, yes, dryRun } = opts;
820
+ let slugs = opts.slugs;
821
+ log.banner();
822
+ let config;
823
+ try {
824
+ config = readConfig(cwd);
825
+ } catch (err) {
826
+ log.error(err.message);
827
+ process.exit(1);
828
+ }
829
+ if (all) {
830
+ slugs = Object.keys(registry).filter(
831
+ (s) => s !== "utils" && !s.startsWith("use-")
832
+ );
833
+ log.info(`Installing all ${slugs.length} components.`);
834
+ } else if (slugs.length === 0) {
835
+ log.error("No components specified. Usage: journal add <slug> [<slug> ...]");
836
+ log.dim(" Or use --all to install everything.");
837
+ process.exit(1);
838
+ }
839
+ const invalid = slugs.filter((s) => !registry[s]);
840
+ if (invalid.length > 0) {
841
+ log.error(`Unknown component${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}`);
842
+ log.dim(" Run `journal list` to see available components.");
843
+ process.exit(1);
844
+ }
845
+ const tree = resolveTree(slugs);
846
+ log.group(`Resolving dependencies for ${slugs.length} component${slugs.length > 1 ? "s" : ""}...`);
847
+ console.log();
848
+ log.dim(` Will install ${tree.length} file${tree.length > 1 ? "s" : ""}: ${tree.join(", ")}`);
849
+ console.log();
850
+ const existingFiles = [];
851
+ for (const slug of tree) {
852
+ const entry = registry[slug];
853
+ const targetPath = resolveTargetPath(entry, config, cwd);
854
+ if (existsSync(targetPath)) {
855
+ existingFiles.push(targetPath);
856
+ }
857
+ }
858
+ if (existingFiles.length > 0 && !overwrite) {
859
+ if (!yes) {
860
+ const doOverwrite = await askConfirm(
861
+ `
862
+ ${existingFiles.length} file(s) already exist. Overwrite?`,
863
+ false
864
+ );
865
+ if (!doOverwrite) {
866
+ log.warn("Aborted. No files were changed.");
867
+ closePrompts();
868
+ return;
869
+ }
870
+ } else {
871
+ log.warn(`${existingFiles.length} file(s) already exist. Use --overwrite to replace.`);
872
+ log.warn("Skipping existing files.");
873
+ }
874
+ }
875
+ log.group(dryRun ? "Dry run \u2014 no files will be written." : "Installing...");
876
+ console.log();
877
+ const installed = [];
878
+ const skipped = [];
879
+ for (const slug of tree) {
880
+ const entry = registry[slug];
881
+ const targetPath = resolveTargetPath(entry, config, cwd);
882
+ const fileExists = existsSync(targetPath);
883
+ if (fileExists && !overwrite) {
884
+ log.skipped(targetPath, "already exists");
885
+ skipped.push(slug);
886
+ continue;
887
+ }
888
+ if (dryRun) {
889
+ log.dryRun(targetPath);
890
+ continue;
891
+ }
892
+ mkdirSync(dirname(targetPath), { recursive: true });
893
+ const raw = readRegistrySource(entry.file);
894
+ const transformed = transformSource(raw, config);
895
+ writeFileSync(targetPath, transformed, "utf8");
896
+ if (fileExists) {
897
+ log.updated(targetPath);
898
+ } else {
899
+ log.created(targetPath);
900
+ }
901
+ installed.push(slug);
902
+ }
903
+ const allNpmDeps = /* @__PURE__ */ new Set();
904
+ for (const slug of tree) {
905
+ for (const dep of registry[slug].npmDeps) {
906
+ allNpmDeps.add(dep);
907
+ }
908
+ }
909
+ console.log();
910
+ if (dryRun) {
911
+ log.info(`Dry run complete. ${tree.length} file(s) would be written.`);
912
+ } else {
913
+ log.success(
914
+ `Installed ${installed.length} file${installed.length !== 1 ? "s" : ""}.` + (skipped.length > 0 ? ` (${skipped.length} skipped)` : "")
915
+ );
916
+ }
917
+ if (allNpmDeps.size > 0) {
918
+ console.log();
919
+ log.group("npm dependencies you may need to install:");
920
+ console.log();
921
+ const depList = Array.from(allNpmDeps).sort().join(" ");
922
+ console.log(` npm install ${depList}`);
923
+ console.log(` pnpm add ${depList}`);
924
+ console.log(` yarn add ${depList}`);
925
+ console.log(` bun add ${depList}`);
926
+ console.log();
927
+ log.dim(" (The CLI doesn't auto-install these to avoid modifying your lockfile.)");
928
+ }
929
+ console.log();
930
+ }
931
+ function resolveTargetPath(entry, config, cwd) {
932
+ const dir = targetDir(entry.slug);
933
+ let alias;
934
+ let filename;
935
+ switch (dir) {
936
+ case "lib":
937
+ alias = config.aliases.lib;
938
+ filename = "utils.ts";
939
+ break;
940
+ case "hooks":
941
+ alias = config.aliases.hooks;
942
+ filename = entry.file;
943
+ break;
944
+ case "ui":
945
+ default:
946
+ alias = config.aliases.ui;
947
+ filename = entry.file;
948
+ break;
949
+ }
950
+ const fsBase = aliasToFs(alias, cwd);
951
+ return resolve(fsBase, filename);
952
+ }
953
+ function aliasToFs(alias, cwd) {
954
+ const stripped = alias.replace(/^@\//, "").replace(/^@\//, "");
955
+ return resolve(cwd, "src", stripped);
956
+ }
957
+
958
+ // src/commands/list.ts
959
+ function listCommand() {
960
+ log.banner();
961
+ const byCategory = {};
962
+ for (const [slug, entry] of Object.entries(registry)) {
963
+ if (!byCategory[entry.category]) byCategory[entry.category] = [];
964
+ byCategory[entry.category].push(slug);
965
+ }
966
+ const categoryOrder = [
967
+ "forms",
968
+ "layout",
969
+ "display",
970
+ "overlays",
971
+ "navigation",
972
+ "feedback",
973
+ "hooks",
974
+ "utilities"
975
+ ];
976
+ for (const cat of categoryOrder) {
977
+ const slugs = byCategory[cat];
978
+ if (!slugs || slugs.length === 0) continue;
979
+ log.group(`${cat} (${slugs.length})`);
980
+ console.log(` ${slugs.sort().join(", ")}`);
981
+ console.log();
982
+ }
983
+ log.dim(`Total: ${Object.keys(registry).length} components.`);
984
+ console.log();
985
+ log.dim("Install with: npx @journal-ds/cli add <slug>");
986
+ }
987
+
988
+ // src/index.ts
989
+ var VERSION = "1.0.0";
990
+ function parseArgs(argv) {
991
+ const args = argv.slice(2);
992
+ const positional = [];
993
+ let command = null;
994
+ const flags = {
995
+ overwrite: false,
996
+ yes: false,
997
+ dryRun: false,
998
+ all: false,
999
+ defaults: false,
1000
+ cwd: process.cwd()
1001
+ };
1002
+ let i = 0;
1003
+ while (i < args.length) {
1004
+ const arg = args[i];
1005
+ if (arg === "--help" || arg === "-h") {
1006
+ printHelp();
1007
+ process.exit(0);
1008
+ } else if (arg === "--version" || arg === "-v") {
1009
+ console.log(VERSION);
1010
+ process.exit(0);
1011
+ } else if (arg === "--overwrite" || arg === "-o") {
1012
+ flags.overwrite = true;
1013
+ } else if (arg === "--yes" || arg === "-y") {
1014
+ flags.yes = true;
1015
+ } else if (arg === "--dry-run" || arg === "-d") {
1016
+ flags.dryRun = true;
1017
+ } else if (arg === "--all" || arg === "-a") {
1018
+ flags.all = true;
1019
+ } else if (arg === "--defaults") {
1020
+ flags.defaults = true;
1021
+ } else if (arg === "--cwd") {
1022
+ i++;
1023
+ if (i < args.length) {
1024
+ flags.cwd = args[i];
1025
+ }
1026
+ } else if (arg.startsWith("--cwd=")) {
1027
+ flags.cwd = arg.slice("--cwd=".length);
1028
+ } else if (arg && !arg.startsWith("-") && command === null) {
1029
+ command = arg;
1030
+ } else if (arg && !arg.startsWith("-")) {
1031
+ positional.push(arg);
1032
+ } else {
1033
+ log.warn(`Unknown flag: ${arg}`);
1034
+ }
1035
+ i++;
1036
+ }
1037
+ return { command, positional, flags };
1038
+ }
1039
+ function printHelp() {
1040
+ log.banner();
1041
+ console.log(`
1042
+ Usage:
1043
+ journal init Create journal.json + theme
1044
+ journal add <slug> [<slug> ...] Add components to your project
1045
+ journal add --all Add every component
1046
+ journal list List available components
1047
+ journal --help Show this help
1048
+ journal --version Show CLI version
1049
+
1050
+ Flags:
1051
+ -o, --overwrite Overwrite existing files
1052
+ -y, --yes Skip confirmation prompts
1053
+ -d, --dry-run Don't write files, just print what would happen
1054
+ --cwd <path> Run in a different directory
1055
+ --defaults Use default config (skip init prompts)
1056
+
1057
+ Examples:
1058
+ npx @journal-ds/cli init
1059
+ npx @journal-ds/cli add button
1060
+ npx @journal-ds/cli add button card dialog input label
1061
+ npx @journal-ds/cli add --all --yes
1062
+ npx @journal-ds/cli list
1063
+ `);
1064
+ }
1065
+ async function main() {
1066
+ const { command, positional, flags } = parseArgs(process.argv);
1067
+ if (!command) {
1068
+ printHelp();
1069
+ process.exit(0);
1070
+ }
1071
+ try {
1072
+ switch (command) {
1073
+ case "init":
1074
+ await initCommand({
1075
+ cwd: flags.cwd,
1076
+ yes: flags.yes,
1077
+ defaults: flags.defaults
1078
+ });
1079
+ break;
1080
+ case "add":
1081
+ await addCommand({
1082
+ cwd: flags.cwd,
1083
+ slugs: positional,
1084
+ all: flags.all,
1085
+ overwrite: flags.overwrite,
1086
+ yes: flags.yes,
1087
+ dryRun: flags.dryRun
1088
+ });
1089
+ break;
1090
+ case "list":
1091
+ listCommand();
1092
+ break;
1093
+ case "help":
1094
+ printHelp();
1095
+ break;
1096
+ default:
1097
+ log.error(`Unknown command: ${command}`);
1098
+ console.log();
1099
+ printHelp();
1100
+ process.exit(1);
1101
+ }
1102
+ } catch (err) {
1103
+ log.error(err.message);
1104
+ console.log();
1105
+ process.exit(1);
1106
+ }
1107
+ }
1108
+ main();