@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.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1108 -0
- package/package.json +61 -0
- package/registry/accordion.tsx +66 -0
- package/registry/alert-dialog.tsx +157 -0
- package/registry/alert.tsx +66 -0
- package/registry/aspect-ratio.tsx +11 -0
- package/registry/avatar.tsx +53 -0
- package/registry/badge.tsx +46 -0
- package/registry/breadcrumb.tsx +109 -0
- package/registry/button.tsx +59 -0
- package/registry/calendar.tsx +213 -0
- package/registry/card.tsx +92 -0
- package/registry/carousel.tsx +241 -0
- package/registry/chart.tsx +353 -0
- package/registry/checkbox.tsx +32 -0
- package/registry/collapsible.tsx +33 -0
- package/registry/command.tsx +186 -0
- package/registry/context-menu.tsx +252 -0
- package/registry/dialog.tsx +143 -0
- package/registry/drawer.tsx +135 -0
- package/registry/dropdown-menu.tsx +257 -0
- package/registry/form.tsx +167 -0
- package/registry/hover-card.tsx +44 -0
- package/registry/input-otp.tsx +77 -0
- package/registry/input.tsx +21 -0
- package/registry/label.tsx +24 -0
- package/registry/menubar.tsx +276 -0
- package/registry/navigation-menu.tsx +168 -0
- package/registry/pagination.tsx +127 -0
- package/registry/popover.tsx +48 -0
- package/registry/progress.tsx +31 -0
- package/registry/radio-group.tsx +45 -0
- package/registry/resizable.tsx +56 -0
- package/registry/scroll-area.tsx +58 -0
- package/registry/select.tsx +185 -0
- package/registry/separator.tsx +28 -0
- package/registry/sheet.tsx +139 -0
- package/registry/sidebar.tsx +726 -0
- package/registry/skeleton.tsx +13 -0
- package/registry/slider.tsx +63 -0
- package/registry/sonner.tsx +25 -0
- package/registry/switch.tsx +31 -0
- package/registry/table.tsx +116 -0
- package/registry/tabs.tsx +66 -0
- package/registry/textarea.tsx +18 -0
- package/registry/toast.tsx +129 -0
- package/registry/toaster.tsx +35 -0
- package/registry/toggle-group.tsx +73 -0
- package/registry/toggle.tsx +47 -0
- package/registry/tooltip.tsx +61 -0
- package/registry/use-mobile.ts +19 -0
- package/registry/use-toast.ts +194 -0
- package/registry/utils.ts +6 -0
- package/templates/globals.css +322 -0
- 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();
|