pejay-ui 1.3.4 → 1.4.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/README.md CHANGED
@@ -11,14 +11,55 @@ npx pejay-ui init
11
11
 
12
12
  ### 2. Add Component
13
13
  ```bash
14
- npx pejay-ui add <component-name>
14
+ npx pejay-ui add <component-name-or-category> [options]
15
15
  ```
16
16
 
17
+ **Options:**
18
+ - `--all`: Install all components in the specified category.
19
+ - `--select`: Interactively select which components to install from the specified category.
20
+
21
+ **Examples:**
22
+ - Install a single component directly:
23
+ ```bash
24
+ npx pejay-ui add form/input
25
+ ```
26
+ - Install all components in a category (e.g., `form`):
27
+ ```bash
28
+ npx pejay-ui add form --all
29
+ ```
30
+ - Select specific components to install from a category (e.g., `form`):
31
+ ```bash
32
+ npx pejay-ui add form --select
33
+ ```
34
+ *(Note: Running `npx pejay-ui add <category>` without options will default to the interactive selection prompt).*
35
+
36
+ **Automatic Exports (Auto-Indexing):**
37
+ Installing components automatically generates or updates `index.ts` (or `index.js`) files at:
38
+ 1. The category level (e.g., `src/pejay-ui/components/form/index.ts`)
39
+ 2. The global components level (`src/pejay-ui/components/index.ts`)
40
+
41
+ This allows you to easily import multiple components:
42
+ ```typescript
43
+ import { Input, Checkbox, AmountInput } from "@/pejay-ui/components";
44
+ ```
45
+
46
+ **Component-Specific Documentation (READMEs):**
47
+ Certain components (like `toast`) ship with localized `README.md` guides. When you install them, the CLI automatically copies their detailed usage documentation directly into the component's folder (e.g., `src/pejay-ui/components/toast/README.md`) so you have helper docs right next to the code.
48
+
17
49
  ### 3. Remove Component
18
50
  ```bash
19
51
  npx pejay-ui remove <component-name>
20
52
  ```
21
53
 
54
+ ### 4. Check Components Status
55
+ ```bash
56
+ npx pejay-ui status
57
+ ```
58
+
59
+ Lists all available components categorized, showing their local installation status:
60
+ - Installed components are marked with a green `[āœ”]`.
61
+ - Uninstalled components are marked with `[ ]`.
62
+
22
63
  ---
23
64
 
24
65
  ## Available Components & Scaffolds
@@ -46,6 +87,7 @@ npx pejay-ui add form/switch
46
87
  npx pejay-ui add form/textarea
47
88
  npx pejay-ui add form/url-input
48
89
  ```
90
+ *(Supports category-wide commands: `npx pejay-ui add form --all` or `npx pejay-ui add form --select`)*
49
91
 
50
92
  ### Date & Time Pickers
51
93
  ```bash
@@ -54,17 +96,32 @@ npx pejay-ui add form/date-range-picker
54
96
  npx pejay-ui add form/time-picker
55
97
  npx pejay-ui add form/time-range-picker
56
98
  ```
99
+ *(Supports category-wide commands: `npx pejay-ui add form --all` or `npx pejay-ui add form --select`)*
57
100
 
58
101
  ### Dropdowns & Selects
59
102
  ```bash
60
103
  npx pejay-ui add dropdown/select-input
61
104
  npx pejay-ui add dropdown/multiselect-input
62
105
  ```
106
+ *(Supports category-wide commands: `npx pejay-ui add dropdown --all` or `npx pejay-ui add dropdown --select`)*
63
107
 
64
108
  ### Layouts
65
109
  ```bash
66
110
  npx pejay-ui add layouts/lv1
67
111
  ```
112
+ *(Supports category-wide commands: `npx pejay-ui add layouts --all` or `npx pejay-ui add layouts --select`)*
113
+
114
+ ### Toast
115
+ ```bash
116
+ npx pejay-ui add toast
117
+ ```
118
+ *(Includes a localized `README.md` guide copied directly into your components folder).*
119
+
120
+ ### Overlays
121
+ ```bash
122
+ npx pejay-ui add overlays/portal
123
+ ```
124
+ *(Supports category-wide commands: `npx pejay-ui add overlays --all` or `npx pejay-ui add overlays --select`)*
68
125
 
69
126
  ### Scaffolds & Templates
70
127
  ```bash
@@ -75,3 +132,4 @@ npx pejay-ui add axios-client
75
132
  npx pejay-ui add redux-store-client
76
133
  npx pejay-ui add rtk-query-client
77
134
  ```
135
+ *(Supports category-wide commands: `npx pejay-ui add scaffold --all` or `npx pejay-ui add scaffold --select`)*
package/bin/cli.js CHANGED
@@ -38,14 +38,37 @@ const getFilesRecursively = async (dir) => {
38
38
  return results;
39
39
  };
40
40
 
41
+ const loadRegistry = async () => {
42
+ const registryDir = path.join(packageRoot, "registry");
43
+ if (!await fs.pathExists(registryDir)) {
44
+ console.error(`Error: Registry directory not found at ${registryDir}`);
45
+ process.exit(1);
46
+ }
47
+ const files = await fs.readdir(registryDir);
48
+ const registry = {};
49
+ for (const file of files) {
50
+ if (file.endsWith(".json")) {
51
+ const filePath = path.join(registryDir, file);
52
+ try {
53
+ const data = await fs.readJSON(filePath);
54
+ Object.assign(registry, data);
55
+ } catch (e) {
56
+ console.error(`Error reading registry file: ${file}`, e);
57
+ }
58
+ }
59
+ }
60
+ return registry;
61
+ };
62
+ const pkg = await fs.readJSON(path.join(packageRoot, "package.json"));
63
+
41
64
  program
42
65
  .name("pejay-ui")
43
66
  .description("CLI to initialize, add, and remove React UI components")
44
- .version("1.0.0");
67
+ .version(pkg.version);
45
68
 
46
69
  /* =============================
47
70
  INIT COMMAND
48
- ============================= */
71
+ ============================= */
49
72
  program
50
73
  .command("init")
51
74
  .description("Initialize pejay-ui configuration in your project")
@@ -70,11 +93,13 @@ program
70
93
 
71
94
  /* =============================
72
95
  ADD COMMAND
73
- ============================= */
96
+ ============================= */
74
97
  program
75
98
  .command("add <component>")
76
99
  .description("Add a component to your project")
77
- .action(async (component) => {
100
+ .option("--all", "Add all components in the category")
101
+ .option("--select", "Select specific components from the category to add")
102
+ .action(async (component, options) => {
78
103
  try {
79
104
  const cwd = process.cwd();
80
105
  const configPath = path.join(cwd, "pejay-ui.json");
@@ -85,15 +110,75 @@ program
85
110
  }
86
111
 
87
112
  const config = await fs.readJSON(configPath);
88
- const registryPath = path.join(packageRoot, "registry.json");
113
+ const registry = await loadRegistry();
114
+ const isTsProject = await fs.pathExists(path.join(cwd, "tsconfig.json"));
89
115
 
90
- if (!await fs.pathExists(registryPath)) {
91
- console.error("Error: Registry configuration not found in the package.");
92
- process.exit(1);
116
+ // Determine which components to install
117
+ let selectedComponents = [];
118
+
119
+ // Determine if the input refers to a category that supports category-wide operations
120
+ const categorySupportMap = {};
121
+ for (const [key, compData] of Object.entries(registry)) {
122
+ if (compData.category && compData.supportsCategory) {
123
+ categorySupportMap[compData.category.toLowerCase()] = compData.category;
124
+ }
93
125
  }
94
126
 
95
- const registry = await fs.readJSON(registryPath);
96
- const isTsProject = await fs.pathExists(path.join(cwd, "tsconfig.json"));
127
+ const getCategoryFromInput = (input, supportMap) => {
128
+ const normInput = input.toLowerCase().trim().replace(/s$/, "");
129
+ for (const [normCat, originalCat] of Object.entries(supportMap)) {
130
+ const normCatSingular = normCat.replace(/s$/, "");
131
+ if (normInput === normCatSingular || (normInput === "dropdown" && normCatSingular === "select-dropdown")) {
132
+ return originalCat;
133
+ }
134
+ }
135
+ return null;
136
+ };
137
+
138
+ const targetCategory = getCategoryFromInput(component, categorySupportMap);
139
+ const isExactComponent = !!registry[component];
140
+
141
+ if (targetCategory && (!isExactComponent || options.all || options.select)) {
142
+ // Treat as category installation
143
+ const categoryComponents = Object.keys(registry).filter(
144
+ (key) => registry[key].category === targetCategory
145
+ );
146
+
147
+ if (categoryComponents.length === 0) {
148
+ console.error(`Error: Category '${component}' has no components in the registry.`);
149
+ process.exit(1);
150
+ }
151
+
152
+ if (options.all) {
153
+ selectedComponents = categoryComponents;
154
+ } else {
155
+ // Dynamic prompt using inquirer checkbox for --select or when no flag is specified for category
156
+ const answers = await prompt([
157
+ {
158
+ type: "checkbox",
159
+ name: "components",
160
+ message: `Select components from category "${targetCategory}" to add:`,
161
+ choices: categoryComponents.map((key) => ({
162
+ name: `${registry[key].name} (${key})`,
163
+ value: key,
164
+ })),
165
+ },
166
+ ]);
167
+ selectedComponents = answers.components;
168
+ if (selectedComponents.length === 0) {
169
+ console.log("No components selected. Exiting.");
170
+ process.exit(0);
171
+ }
172
+ }
173
+ } else {
174
+ // Not a category (or exact component targeted without category flags), treat as a single component key
175
+ if (!isExactComponent) {
176
+ console.error(`Error: Component or Category '${component}' not found in registry.`);
177
+ console.log(`Available categories/components: ${Array.from(new Set(Object.values(registry).map(c => c.category).filter(Boolean))).join(", ")} or ${Object.keys(registry).join(", ")}`);
178
+ process.exit(1);
179
+ }
180
+ selectedComponents = [component];
181
+ }
97
182
 
98
183
  // Track all components to install (including dependencies) in topological/order of dependencies
99
184
  const installQueue = [];
@@ -106,7 +191,6 @@ program
106
191
  const compData = registry[compName];
107
192
  if (!compData) {
108
193
  console.error(`Error: Component '${compName}' not found in registry.`);
109
- console.log(`Available components: ${Object.keys(registry).join(", ")}`);
110
194
  process.exit(1);
111
195
  }
112
196
 
@@ -120,13 +204,15 @@ program
120
204
  installQueue.push(compName);
121
205
  };
122
206
 
123
- resolveDependencies(component);
207
+ for (const comp of selectedComponents) {
208
+ resolveDependencies(comp);
209
+ }
124
210
 
125
211
  console.log("\nšŸš€ Starting installation...\n");
126
212
 
127
213
  for (const compToInstall of installQueue) {
128
- // Skip if already marked as installed in config (unless it is the main component requested)
129
- if (config.installed?.[compToInstall] && compToInstall !== component) {
214
+ // Skip if already marked as installed in config (unless it is one of the explicitly requested components)
215
+ if (config.installed?.[compToInstall] && !selectedComponents.includes(compToInstall)) {
130
216
  console.log(`Component '${compToInstall}' is already installed. Skipping dependency installation.`);
131
217
  continue;
132
218
  }
@@ -293,6 +379,56 @@ program
293
379
  }
294
380
  }
295
381
 
382
+ // 3.5 Automatically generate/update category-level and global index.ts/index.js files
383
+ if (componentData.category && !["scaffold", "scaffolds", "script", "scripts"].includes(componentData.category.toLowerCase())) {
384
+ const indexExt = isTsProject ? "ts" : "js";
385
+ const filesInDir = await fs.readdir(targetDir);
386
+ const exportableFiles = [];
387
+
388
+ for (const file of filesInDir) {
389
+ const filePath = path.join(targetDir, file);
390
+ const stat = await fs.stat(filePath);
391
+ if (stat.isFile()) {
392
+ const ext = path.extname(file);
393
+ const name = path.basename(file, ext);
394
+ if ((ext === ".tsx" || ext === ".ts" || ext === ".jsx" || ext === ".js") && name !== "index") {
395
+ exportableFiles.push(name);
396
+ }
397
+ }
398
+ }
399
+
400
+ if (exportableFiles.length > 0) {
401
+ exportableFiles.sort();
402
+ const indexFilePath = path.join(targetDir, `index.${indexExt}`);
403
+ const exportLines = exportableFiles.map(name => `export * from "./${name}";`);
404
+ await fs.writeFile(indexFilePath, exportLines.join("\n") + "\n", "utf-8");
405
+ console.log(`āœ… Updated index.${indexExt} in ${path.relative(cwd, targetDir)}`);
406
+
407
+ // Also update the global components index file
408
+ const componentsDir = path.dirname(targetDir); // src/pejay-ui/components
409
+ if (await fs.pathExists(componentsDir)) {
410
+ const categories = await fs.readdir(componentsDir);
411
+ const validCategories = [];
412
+ for (const cat of categories) {
413
+ const catDir = path.join(componentsDir, cat);
414
+ const catStat = await fs.stat(catDir);
415
+ if (catStat.isDirectory()) {
416
+ if (await fs.pathExists(path.join(catDir, `index.ts`)) || await fs.pathExists(path.join(catDir, `index.js`))) {
417
+ validCategories.push(cat);
418
+ }
419
+ }
420
+ }
421
+ if (validCategories.length > 0) {
422
+ validCategories.sort();
423
+ const globalIndexFile = path.join(componentsDir, `index.${indexExt}`);
424
+ const globalExportLines = validCategories.map(cat => `export * from "./${cat}";`);
425
+ await fs.writeFile(globalIndexFile, globalExportLines.join("\n") + "\n", "utf-8");
426
+ console.log(`āœ… Updated global index.${indexExt} in ${path.relative(cwd, componentsDir)}`);
427
+ }
428
+ }
429
+ }
430
+ }
431
+
296
432
  // 4. Update State tracking in config
297
433
  config.installed = config.installed || {};
298
434
  config.installed[compToInstall] = {
@@ -326,14 +462,7 @@ program
326
462
  }
327
463
 
328
464
  const config = await fs.readJSON(configPath);
329
- const registryPath = path.join(packageRoot, "registry.json");
330
-
331
- if (!await fs.pathExists(registryPath)) {
332
- console.error("Error: Registry configuration not found.");
333
- process.exit(1);
334
- }
335
-
336
- const registry = await fs.readJSON(registryPath);
465
+ const registry = await loadRegistry();
337
466
  const componentData = registry[component];
338
467
 
339
468
  if (!componentData) {
@@ -358,6 +487,73 @@ program
358
487
  }
359
488
  }
360
489
 
490
+ // 1.5 Update index files
491
+ if (componentData.category && !["scaffold", "scaffolds", "script", "scripts"].includes(componentData.category.toLowerCase())) {
492
+ const isTsProject = await fs.pathExists(path.join(cwd, "tsconfig.json"));
493
+ const targetDir = path.join(cwd, config.baseDir, "components", componentData.category);
494
+ const indexExt = isTsProject ? "ts" : "js";
495
+
496
+ if (await fs.pathExists(targetDir)) {
497
+ const filesInDir = await fs.readdir(targetDir);
498
+ const exportableFiles = [];
499
+
500
+ for (const file of filesInDir) {
501
+ const filePath = path.join(targetDir, file);
502
+ const stat = await fs.stat(filePath);
503
+ if (stat.isFile()) {
504
+ const ext = path.extname(file);
505
+ const name = path.basename(file, ext);
506
+ if ((ext === ".tsx" || ext === ".ts" || ext === ".jsx" || ext === ".js") && name !== "index") {
507
+ exportableFiles.push(name);
508
+ }
509
+ }
510
+ }
511
+
512
+ const indexFilePath = path.join(targetDir, `index.${indexExt}`);
513
+ if (exportableFiles.length > 0) {
514
+ exportableFiles.sort();
515
+ const exportLines = exportableFiles.map(name => `export * from "./${name}";`);
516
+ await fs.writeFile(indexFilePath, exportLines.join("\n") + "\n", "utf-8");
517
+ console.log(`āœ… Updated index.${indexExt} in ${path.relative(cwd, targetDir)}`);
518
+ } else {
519
+ // No files left, delete the category index file
520
+ if (await fs.pathExists(indexFilePath)) {
521
+ await fs.remove(indexFilePath);
522
+ console.log(`šŸ—‘ļø Removed empty index.${indexExt} in ${path.relative(cwd, targetDir)}`);
523
+ }
524
+ }
525
+
526
+ // Also update the global components index file
527
+ const componentsDir = path.dirname(targetDir);
528
+ if (await fs.pathExists(componentsDir)) {
529
+ const categories = await fs.readdir(componentsDir);
530
+ const validCategories = [];
531
+ for (const cat of categories) {
532
+ const catDir = path.join(componentsDir, cat);
533
+ const catStat = await fs.stat(catDir);
534
+ if (catStat.isDirectory()) {
535
+ if (await fs.pathExists(path.join(catDir, `index.ts`)) || await fs.pathExists(path.join(catDir, `index.js`))) {
536
+ validCategories.push(cat);
537
+ }
538
+ }
539
+ }
540
+ const globalIndexFile = path.join(componentsDir, `index.${indexExt}`);
541
+ if (validCategories.length > 0) {
542
+ validCategories.sort();
543
+ const globalExportLines = validCategories.map(cat => `export * from "./${cat}";`);
544
+ await fs.writeFile(globalIndexFile, globalExportLines.join("\n") + "\n", "utf-8");
545
+ console.log(`āœ… Updated global index.${indexExt} in ${path.relative(cwd, componentsDir)}`);
546
+ } else {
547
+ // No categories left with index files, remove the global index
548
+ if (await fs.pathExists(globalIndexFile)) {
549
+ await fs.remove(globalIndexFile);
550
+ console.log(`šŸ—‘ļø Removed empty global index.${indexExt} in ${path.relative(cwd, componentsDir)}`);
551
+ }
552
+ }
553
+ }
554
+ }
555
+ }
556
+
361
557
  // 2. Build Utility Usage Map
362
558
  const utilityUsage = {};
363
559
  for (const [compName, compInfo] of Object.entries(config.installed)) {
@@ -452,4 +648,80 @@ program
452
648
  }
453
649
  });
454
650
 
651
+ /* =============================
652
+ STATUS COMMAND
653
+ ============================= */
654
+ program
655
+ .command("status")
656
+ .description("List all available components and check their installation status")
657
+ .action(async () => {
658
+ try {
659
+ const cwd = process.cwd();
660
+ const configPath = path.join(cwd, "pejay-ui.json");
661
+
662
+ let config = { installed: {} };
663
+ let hasConfig = true;
664
+
665
+ if (!await fs.pathExists(configPath)) {
666
+ hasConfig = false;
667
+ } else {
668
+ try {
669
+ config = await fs.readJSON(configPath);
670
+ } catch (e) {
671
+ hasConfig = false;
672
+ }
673
+ }
674
+
675
+ const registry = await loadRegistry();
676
+
677
+ // Terminal styling colors
678
+ const GREEN = "\x1b[32m";
679
+ const CYAN = "\x1b[36m";
680
+ const DIM = "\x1b[2m";
681
+ const RESET = "\x1b[0m";
682
+ const YELLOW = "\x1b[33m";
683
+
684
+ console.log(`\nšŸ” ${CYAN}pejay-ui Components Status:${RESET}\n`);
685
+
686
+ if (!hasConfig) {
687
+ console.log(`${YELLOW}Note: pejay-ui.json not found. Initialize first via 'npx pejay-ui init'.${RESET}`);
688
+ console.log(`${YELLOW}Showing all components as uninstalled.${RESET}\n`);
689
+ }
690
+
691
+ // Group registry items by category
692
+ const categories = {};
693
+ for (const [key, compData] of Object.entries(registry)) {
694
+ const category = compData.category || "other";
695
+ if (!categories[category]) {
696
+ categories[category] = [];
697
+ }
698
+ categories[category].push({
699
+ key,
700
+ name: compData.name,
701
+ installed: !!config.installed?.[key]
702
+ });
703
+ }
704
+
705
+ // Print categories and components sorted alphabetically
706
+ const sortedCategoryNames = Object.keys(categories).sort();
707
+ for (const cat of sortedCategoryNames) {
708
+ console.log(`${CYAN}Category: ${cat}${RESET}`);
709
+ const comps = categories[cat];
710
+ comps.sort((a, b) => a.name.localeCompare(b.name));
711
+
712
+ for (const comp of comps) {
713
+ if (comp.installed) {
714
+ console.log(` ${GREEN}[āœ”] ${comp.name}${RESET} ${DIM}(${comp.key})${RESET}`);
715
+ } else {
716
+ console.log(` [ ] ${comp.name} ${DIM}(${comp.key})${RESET}`);
717
+ }
718
+ }
719
+ console.log(); // blank line between categories
720
+ }
721
+
722
+ } catch (err) {
723
+ console.error("\nāŒ Status display failed\n", err);
724
+ }
725
+ });
726
+
455
727
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pejay-ui",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "react ui components",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "bin/",
11
11
  "templates/",
12
12
  "utils/",
13
- "registry.json"
13
+ "registry/"
14
14
  ],
15
15
  "keywords": [
16
16
  "react",
@@ -0,0 +1,9 @@
1
+ {
2
+ "button": {
3
+ "name": "Button",
4
+ "category": "button",
5
+ "files": ["templates/button/Button.tsx", "templates/button/tooltip.tsx"],
6
+ "utils": ["cn.ts"],
7
+ "peerDependencies": ["clsx", "tailwind-merge"]
8
+ }
9
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "dropdown/select-input": {
3
+ "name": "SelectInput",
4
+ "category": "select-dropdown",
5
+ "files": ["templates/select-dropdown/select-input.tsx"],
6
+ "utils": ["cn.ts"],
7
+ "peerDependencies": [
8
+ "clsx",
9
+ "tailwind-merge",
10
+ "lucide-react",
11
+ "@floating-ui/react"
12
+ ],
13
+ "supportsCategory": true
14
+ },
15
+ "dropdown/multiselect-input": {
16
+ "name": "MultiselectInput",
17
+ "category": "select-dropdown",
18
+ "files": ["templates/select-dropdown/multiselect-input.tsx"],
19
+ "utils": ["cn.ts"],
20
+ "peerDependencies": [
21
+ "clsx",
22
+ "tailwind-merge",
23
+ "lucide-react",
24
+ "@floating-ui/react"
25
+ ],
26
+ "supportsCategory": true
27
+ }
28
+ }