@brxndxndiaz/ui 0.1.1 → 0.1.3
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/README.md +2 -0
- package/bin/brxndxndiaz-ui.js +371 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,9 @@ CLI for the brxndxndiaz UI registry.
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
npx @brxndxndiaz/ui init
|
|
8
9
|
npx @brxndxndiaz/ui add animated-timeline
|
|
10
|
+
npx @brxndxndiaz/ui add
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
This pulls registry items from the public registry and writes the component files into your project.
|
package/bin/brxndxndiaz-ui.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
|
+
const { spawnSync } = require("child_process");
|
|
7
|
+
const readline = require("readline");
|
|
6
8
|
|
|
7
9
|
const REGISTRY_BASE = "https://ui.brndndiaz.dev/r";
|
|
8
10
|
const SOURCE_BASE = process.env.BRXN_SOURCE_BASE;
|
|
@@ -11,10 +13,13 @@ function printHelp() {
|
|
|
11
13
|
console.log(`brxndxndiaz-ui
|
|
12
14
|
|
|
13
15
|
Usage:
|
|
14
|
-
brxndxndiaz-ui
|
|
16
|
+
brxndxndiaz-ui init
|
|
17
|
+
brxndxndiaz-ui add [component]
|
|
15
18
|
|
|
16
19
|
Examples:
|
|
20
|
+
brxndxndiaz-ui init
|
|
17
21
|
brxndxndiaz-ui add animated-timeline
|
|
22
|
+
brxndxndiaz-ui add
|
|
18
23
|
`);
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -40,15 +45,257 @@ async function writeFile(targetPath, content) {
|
|
|
40
45
|
await fs.promises.writeFile(fullPath, content, "utf8");
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
function fileExists(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
fs.accessSync(filePath, fs.constants.F_OK);
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readJson(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function prompt(question) {
|
|
66
|
+
const rl = readline.createInterface({
|
|
67
|
+
input: process.stdin,
|
|
68
|
+
output: process.stdout,
|
|
69
|
+
});
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
rl.question(question, (answer) => {
|
|
72
|
+
rl.close();
|
|
73
|
+
resolve(answer.trim());
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseSelection(input, max) {
|
|
79
|
+
const normalized = input.replace(/\s+/g, "").toLowerCase();
|
|
80
|
+
if (!normalized) return [];
|
|
81
|
+
if (normalized === "all" || normalized === "*") {
|
|
82
|
+
return Array.from({ length: max }, (_, i) => i + 1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parts = normalized.split(",").filter(Boolean);
|
|
86
|
+
const result = new Set();
|
|
87
|
+
|
|
88
|
+
for (const part of parts) {
|
|
89
|
+
if (part.includes("-")) {
|
|
90
|
+
const [startRaw, endRaw] = part.split("-");
|
|
91
|
+
const start = Number(startRaw);
|
|
92
|
+
const end = Number(endRaw);
|
|
93
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) continue;
|
|
94
|
+
const from = Math.min(start, end);
|
|
95
|
+
const to = Math.max(start, end);
|
|
96
|
+
for (let i = from; i <= to; i += 1) {
|
|
97
|
+
if (i >= 1 && i <= max) result.add(i);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
const index = Number(part);
|
|
101
|
+
if (Number.isInteger(index) && index >= 1 && index <= max) {
|
|
102
|
+
result.add(index);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Array.from(result.values());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function detectPackageManager(projectRoot) {
|
|
111
|
+
if (fileExists(path.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
112
|
+
if (fileExists(path.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
113
|
+
if (fileExists(path.join(projectRoot, "bun.lock")) || fileExists(path.join(projectRoot, "bun.lockb"))) {
|
|
114
|
+
return "bun";
|
|
115
|
+
}
|
|
116
|
+
return "npm";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function installDependencies(dependencies, devDependencies) {
|
|
120
|
+
const deps = dependencies || [];
|
|
121
|
+
const devDeps = devDependencies || [];
|
|
122
|
+
if (deps.length === 0 && devDeps.length === 0) return;
|
|
123
|
+
const projectRoot = process.cwd();
|
|
124
|
+
if (!fileExists(path.join(projectRoot, "package.json"))) {
|
|
125
|
+
console.warn("Skipping dependency install: package.json not found.");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const pkgManager = detectPackageManager(projectRoot);
|
|
129
|
+
const installArgs = (items, isDev) => {
|
|
130
|
+
if (items.length === 0) return null;
|
|
131
|
+
if (pkgManager === "npm") {
|
|
132
|
+
return isDev ? ["install", "--save-dev", ...items] : ["install", ...items];
|
|
133
|
+
}
|
|
134
|
+
if (pkgManager === "yarn") {
|
|
135
|
+
return isDev ? ["add", "-D", ...items] : ["add", ...items];
|
|
136
|
+
}
|
|
137
|
+
if (pkgManager === "pnpm") {
|
|
138
|
+
return isDev ? ["add", "-D", ...items] : ["add", ...items];
|
|
139
|
+
}
|
|
140
|
+
if (pkgManager === "bun") {
|
|
141
|
+
return isDev ? ["add", "-d", ...items] : ["add", ...items];
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (deps.length > 0) {
|
|
147
|
+
console.log(`Installing dependencies with ${pkgManager}: ${deps.join(", ")}`);
|
|
148
|
+
const args = installArgs(deps, false);
|
|
149
|
+
const result = spawnSync(pkgManager, args, { stdio: "inherit" });
|
|
150
|
+
if (result.status !== 0) {
|
|
151
|
+
throw new Error(`Failed to install dependencies with ${pkgManager}.`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (devDeps.length > 0) {
|
|
156
|
+
console.log(`Installing dev dependencies with ${pkgManager}: ${devDeps.join(", ")}`);
|
|
157
|
+
const args = installArgs(devDeps, true);
|
|
158
|
+
const result = spawnSync(pkgManager, args, { stdio: "inherit" });
|
|
159
|
+
if (result.status !== 0) {
|
|
160
|
+
throw new Error(`Failed to install dev dependencies with ${pkgManager}.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveTargetPath(targetPath) {
|
|
166
|
+
return targetPath.replace(/\\/g, "/");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveAliasPath(alias, projectRoot) {
|
|
170
|
+
if (!alias || typeof alias !== "string") return null;
|
|
171
|
+
if (!alias.startsWith("@/")) return null;
|
|
172
|
+
const relPath = alias.slice(2);
|
|
173
|
+
const directPath = path.join(projectRoot, relPath);
|
|
174
|
+
if (fileExists(directPath)) return relPath.replace(/\\/g, "/");
|
|
175
|
+
const srcPath = path.join(projectRoot, "src", relPath);
|
|
176
|
+
if (fileExists(srcPath)) return path.join("src", relPath).replace(/\\/g, "/");
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function detectComponentsRoot(projectRoot) {
|
|
181
|
+
const configPath = path.join(projectRoot, "components.json");
|
|
182
|
+
if (fileExists(configPath)) {
|
|
183
|
+
const config = readJson(configPath);
|
|
184
|
+
const aliasRoot = resolveAliasPath(config?.aliases?.components, projectRoot);
|
|
185
|
+
if (aliasRoot) return aliasRoot;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const candidates = [
|
|
189
|
+
"src/components",
|
|
190
|
+
"components",
|
|
191
|
+
"src/app/components",
|
|
192
|
+
"app/components",
|
|
193
|
+
];
|
|
194
|
+
const matches = candidates.filter((candidate) =>
|
|
195
|
+
fileExists(path.join(projectRoot, candidate))
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (matches.length === 1) return matches[0];
|
|
199
|
+
|
|
200
|
+
return matches.length ? matches : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function promptForComponentsRoot(projectRoot) {
|
|
204
|
+
const detection = detectComponentsRoot(projectRoot);
|
|
205
|
+
if (typeof detection === "string") return detection;
|
|
206
|
+
|
|
207
|
+
const defaults = ["components", "src/components", "app/components", "src/app/components"];
|
|
208
|
+
const options = detection && Array.isArray(detection) ? detection : defaults;
|
|
209
|
+
|
|
210
|
+
console.log("\nSelect a components directory:");
|
|
211
|
+
options.forEach((option, index) => {
|
|
212
|
+
console.log(` ${index + 1}) ${option}`);
|
|
213
|
+
});
|
|
214
|
+
const answer = await prompt("Enter a number (default 1): ");
|
|
215
|
+
const selection = parseSelection(answer || "1", options.length)[0] || 1;
|
|
216
|
+
const chosen = options[selection - 1] || options[0];
|
|
217
|
+
return chosen;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolveComponentTarget(targetPath, componentsRoot) {
|
|
221
|
+
if (!targetPath.startsWith("components/")) {
|
|
222
|
+
return resolveTargetPath(targetPath);
|
|
223
|
+
}
|
|
224
|
+
const suffix = targetPath.slice("components/".length);
|
|
225
|
+
const resolved = path.join(componentsRoot, suffix).replace(/\\/g, "/");
|
|
226
|
+
return resolved;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function fetchRegistryIndex() {
|
|
230
|
+
const registryUrl = `${REGISTRY_BASE}/registry.json`;
|
|
231
|
+
const data = await fetchJson(registryUrl);
|
|
232
|
+
return data?.items || [];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isComponentInstalled(item, componentsRoot) {
|
|
236
|
+
if (!item?.files?.length) return false;
|
|
237
|
+
return item.files.every((file) => {
|
|
238
|
+
const target = file.target || file.path;
|
|
239
|
+
const resolvedTarget = resolveComponentTarget(target, componentsRoot);
|
|
240
|
+
return fileExists(path.join(process.cwd(), resolvedTarget));
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function addComponent(
|
|
245
|
+
name,
|
|
246
|
+
componentsRoot,
|
|
247
|
+
seen = new Set(),
|
|
248
|
+
dependencies = new Set(),
|
|
249
|
+
devDependencies = new Set()
|
|
250
|
+
) {
|
|
251
|
+
if (seen.has(name)) return;
|
|
252
|
+
seen.add(name);
|
|
253
|
+
|
|
44
254
|
const registryUrl = `${REGISTRY_BASE}/${name}.json`;
|
|
45
|
-
|
|
255
|
+
let item;
|
|
256
|
+
try {
|
|
257
|
+
item = await fetchJson(registryUrl);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (String(error.message).includes("404")) {
|
|
260
|
+
console.error(`Error: Component "${name}" not found in the registry.`);
|
|
261
|
+
console.error("Run `npx @brxndxndiaz/ui add` to select from available components.");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
46
266
|
if (!item?.files?.length) {
|
|
47
267
|
throw new Error(`Registry item has no files: ${registryUrl}`);
|
|
48
268
|
}
|
|
49
269
|
|
|
270
|
+
if (isComponentInstalled(item, componentsRoot)) {
|
|
271
|
+
console.log(`Skipped ${name}: already installed.`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (item.registryDependencies && item.registryDependencies.length > 0) {
|
|
276
|
+
for (const dependency of item.registryDependencies) {
|
|
277
|
+
await addComponent(
|
|
278
|
+
dependency,
|
|
279
|
+
componentsRoot,
|
|
280
|
+
seen,
|
|
281
|
+
dependencies,
|
|
282
|
+
devDependencies
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(item.dependencies)) {
|
|
287
|
+
item.dependencies.forEach((dep) => dependencies.add(dep));
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(item.devDependencies)) {
|
|
290
|
+
item.devDependencies.forEach((dep) => devDependencies.add(dep));
|
|
291
|
+
}
|
|
292
|
+
if (Array.isArray(item.peerDependencies)) {
|
|
293
|
+
item.peerDependencies.forEach((dep) => dependencies.add(dep));
|
|
294
|
+
}
|
|
295
|
+
|
|
50
296
|
for (const file of item.files) {
|
|
51
297
|
const targetPath = file.target || file.path;
|
|
298
|
+
const resolvedTarget = resolveComponentTarget(targetPath, componentsRoot);
|
|
52
299
|
let content = file.content;
|
|
53
300
|
if (!content) {
|
|
54
301
|
if (!SOURCE_BASE) {
|
|
@@ -60,11 +307,107 @@ async function addComponent(name) {
|
|
|
60
307
|
const sourceUrl = `${SOURCE_BASE}/${sourcePath}`;
|
|
61
308
|
content = await fetchText(sourceUrl);
|
|
62
309
|
}
|
|
63
|
-
await writeFile(
|
|
64
|
-
console.log(`Added ${
|
|
310
|
+
await writeFile(resolvedTarget, content);
|
|
311
|
+
console.log(`Added ${resolvedTarget}`);
|
|
65
312
|
}
|
|
66
313
|
}
|
|
67
314
|
|
|
315
|
+
async function promptForRegistryComponents(componentsRoot) {
|
|
316
|
+
const registryIndex = await fetchRegistryIndex();
|
|
317
|
+
if (!registryIndex.length) {
|
|
318
|
+
throw new Error("Failed to fetch registry index.");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const available = registryIndex.filter((item) => !isComponentInstalled(item, componentsRoot));
|
|
322
|
+
if (!available.length) {
|
|
323
|
+
console.log("All available components are already installed.");
|
|
324
|
+
process.exit(0);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log("\nWhich components would you like to add?");
|
|
328
|
+
available.forEach((item, index) => {
|
|
329
|
+
console.log(` ${index + 1}) ${item.name}`);
|
|
330
|
+
});
|
|
331
|
+
console.log("Enter numbers separated by commas (or 'all'):");
|
|
332
|
+
const answer = await prompt("> ");
|
|
333
|
+
const selections = parseSelection(answer, available.length);
|
|
334
|
+
if (!selections.length) {
|
|
335
|
+
console.log("No components selected. Exiting.");
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
return selections.map((index) => available[index - 1].name);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function resolveTailwindCssPath(projectRoot) {
|
|
342
|
+
const candidates = [
|
|
343
|
+
"src/app/globals.css",
|
|
344
|
+
"app/globals.css",
|
|
345
|
+
"src/styles/globals.css",
|
|
346
|
+
"styles/globals.css",
|
|
347
|
+
];
|
|
348
|
+
const match = candidates.find((candidate) =>
|
|
349
|
+
fileExists(path.join(projectRoot, candidate))
|
|
350
|
+
);
|
|
351
|
+
return match || candidates[0];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function resolveUtilsAlias(projectRoot) {
|
|
355
|
+
const candidates = ["src/lib/utils", "lib/utils"];
|
|
356
|
+
const match = candidates.find((candidate) =>
|
|
357
|
+
fileExists(path.join(projectRoot, `${candidate}.ts`)) ||
|
|
358
|
+
fileExists(path.join(projectRoot, `${candidate}.tsx`))
|
|
359
|
+
);
|
|
360
|
+
if (match?.startsWith("src/")) return `@/${match.slice(4)}`;
|
|
361
|
+
return match ? `@/${match}` : "@/lib/utils";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function initProject() {
|
|
365
|
+
const projectRoot = process.cwd();
|
|
366
|
+
const configPath = path.join(projectRoot, "components.json");
|
|
367
|
+
if (fileExists(configPath)) {
|
|
368
|
+
console.log("components.json already exists. Skipping init.");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const componentsRoot = await promptForComponentsRoot(projectRoot);
|
|
373
|
+
const tailwindCss = resolveTailwindCssPath(projectRoot);
|
|
374
|
+
const utilsAlias = resolveUtilsAlias(projectRoot);
|
|
375
|
+
const componentsAlias = componentsRoot.startsWith("src/")
|
|
376
|
+
? `@/${componentsRoot.slice(4)}`
|
|
377
|
+
: `@/${componentsRoot}`;
|
|
378
|
+
|
|
379
|
+
const config = {
|
|
380
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
381
|
+
style: "base-nova",
|
|
382
|
+
rsc: true,
|
|
383
|
+
tsx: true,
|
|
384
|
+
tailwind: {
|
|
385
|
+
config: "",
|
|
386
|
+
css: tailwindCss,
|
|
387
|
+
baseColor: "neutral",
|
|
388
|
+
cssVariables: true,
|
|
389
|
+
prefix: "",
|
|
390
|
+
},
|
|
391
|
+
iconLibrary: "lucide",
|
|
392
|
+
rtl: false,
|
|
393
|
+
aliases: {
|
|
394
|
+
components: componentsAlias,
|
|
395
|
+
utils: utilsAlias,
|
|
396
|
+
ui: componentsAlias,
|
|
397
|
+
lib: "@/lib",
|
|
398
|
+
hooks: "@/hooks",
|
|
399
|
+
},
|
|
400
|
+
menuColor: "default",
|
|
401
|
+
menuAccent: "subtle",
|
|
402
|
+
registries: {
|
|
403
|
+
"@brxndxndiaz/ui": "https://ui.brndndiaz.dev/r/{name}.json",
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
await writeFile("components.json", `${JSON.stringify(config, null, 2)}\n`);
|
|
408
|
+
console.log("Created components.json");
|
|
409
|
+
}
|
|
410
|
+
|
|
68
411
|
async function main() {
|
|
69
412
|
const [, , command, arg] = process.argv;
|
|
70
413
|
if (!command || command === "-h" || command === "--help") {
|
|
@@ -72,12 +415,33 @@ async function main() {
|
|
|
72
415
|
return;
|
|
73
416
|
}
|
|
74
417
|
|
|
75
|
-
if (command
|
|
418
|
+
if (command === "init") {
|
|
419
|
+
await initProject();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (command !== "add") {
|
|
76
424
|
printHelp();
|
|
77
425
|
process.exit(1);
|
|
78
426
|
}
|
|
79
427
|
|
|
80
|
-
|
|
428
|
+
const projectRoot = process.cwd();
|
|
429
|
+
const componentsRoot = await promptForComponentsRoot(projectRoot);
|
|
430
|
+
|
|
431
|
+
let components = [];
|
|
432
|
+
if (arg) {
|
|
433
|
+
components = [arg];
|
|
434
|
+
} else {
|
|
435
|
+
components = await promptForRegistryComponents(componentsRoot);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const seen = new Set();
|
|
439
|
+
const dependencies = new Set();
|
|
440
|
+
const devDependencies = new Set();
|
|
441
|
+
for (const component of components) {
|
|
442
|
+
await addComponent(component, componentsRoot, seen, dependencies, devDependencies);
|
|
443
|
+
}
|
|
444
|
+
installDependencies(Array.from(dependencies), Array.from(devDependencies));
|
|
81
445
|
}
|
|
82
446
|
|
|
83
447
|
main().catch((error) => {
|