juxscript 1.1.368 → 1.1.370

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -12,6 +12,135 @@ const PACKAGE_ROOT = path.resolve(__dirname, '..');
12
12
 
13
13
  const [, , command, ...args] = process.argv;
14
14
 
15
+ // ═══════════════════════════════════════════════════════════════
16
+ // UTILITIES (must be defined before commands that use them)
17
+ // ═══════════════════════════════════════════════════════════════
18
+ function copyDirRecursive(src, dest) {
19
+ if (!fs.existsSync(dest)) {
20
+ fs.mkdirSync(dest, { recursive: true });
21
+ }
22
+
23
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
24
+ if (entry.name.startsWith('.')) continue;
25
+
26
+ const srcPath = path.join(src, entry.name);
27
+ const destPath = path.join(dest, entry.name);
28
+
29
+ if (entry.isDirectory()) {
30
+ copyDirRecursive(srcPath, destPath);
31
+ } else {
32
+ fs.copyFileSync(srcPath, destPath);
33
+ }
34
+ }
35
+ }
36
+
37
+ function resolveProjectSrcDir() {
38
+ const projectRoot = process.cwd();
39
+ const configPath = path.join(projectRoot, 'juxconfig.js');
40
+
41
+ if (fs.existsSync(configPath)) {
42
+ try {
43
+ const configContent = fs.readFileSync(configPath, 'utf8');
44
+ const srcDirMatch = configContent.match(/srcDir\s*:\s*['"]([^'"]+)['"]/);
45
+ if (srcDirMatch) {
46
+ return path.resolve(projectRoot, srcDirMatch[1]);
47
+ }
48
+ } catch (_) { }
49
+ }
50
+
51
+ return path.resolve(projectRoot, 'jux');
52
+ }
53
+
54
+ function resolveDestFolderName(parentDir, baseName) {
55
+ const first = path.join(parentDir, baseName);
56
+ if (!fs.existsSync(first)) return baseName;
57
+
58
+ const juxName = baseName + '-jux';
59
+ const second = path.join(parentDir, juxName);
60
+ if (!fs.existsSync(second)) return juxName;
61
+
62
+ for (let i = 1; i <= 99; i++) {
63
+ const numbered = `${baseName}-${i}`;
64
+ if (!fs.existsSync(path.join(parentDir, numbered))) return numbered;
65
+ }
66
+
67
+ return `${baseName}-${Date.now()}`;
68
+ }
69
+
70
+ function listFilesRecursive(dir, base = '') {
71
+ const results = [];
72
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
73
+
74
+ for (const entry of entries) {
75
+ if (entry.name.startsWith('.')) continue;
76
+ const rel = base ? path.join(base, entry.name) : entry.name;
77
+
78
+ if (entry.isDirectory()) {
79
+ results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
80
+ } else {
81
+ results.push(rel);
82
+ }
83
+ }
84
+
85
+ return results;
86
+ }
87
+
88
+ function promptPresetSelection(presets) {
89
+ return new Promise((resolve) => {
90
+ console.log('\n📦 Available component presets:\n');
91
+ presets.forEach((p, i) => {
92
+ const presetDir = path.join(PACKAGE_ROOT, 'presets', p);
93
+ const files = fs.readdirSync(presetDir).filter(f => !f.startsWith('.'));
94
+ console.log(` ${i + 1}) ${p} (${files.join(', ')})`);
95
+ });
96
+ console.log(` 0) Cancel\n`);
97
+
98
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
99
+ rl.question('Select a preset (number or name): ', (answer) => {
100
+ rl.close();
101
+ const trimmed = answer.trim();
102
+
103
+ const num = parseInt(trimmed, 10);
104
+ if (num === 0) return resolve(null);
105
+ if (num >= 1 && num <= presets.length) return resolve(presets[num - 1]);
106
+
107
+ if (presets.includes(trimmed)) return resolve(trimmed);
108
+
109
+ console.error(`❌ Invalid selection: "${trimmed}"`);
110
+ resolve(null);
111
+ });
112
+ });
113
+ }
114
+
115
+ function promptYesNo(question) {
116
+ return new Promise((resolve) => {
117
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
118
+ rl.question(`${question} (y/N): `, (answer) => {
119
+ rl.close();
120
+ resolve(answer.trim().toLowerCase() === 'y');
121
+ });
122
+ });
123
+ }
124
+
125
+ function showHelp() {
126
+ console.log(`
127
+ JUX CLI
128
+
129
+ Usage:
130
+ jux create <name> Create a new JUX project
131
+ jux init Initialize JUX in current directory
132
+ jux build Build for production
133
+ jux serve Start production server
134
+ jux serve --hot Start dev server with hot reload
135
+ jux comp [name] Add a component preset to your project
136
+ jux comp [name] -f Force overwrite (backs up existing files)
137
+
138
+ Options:
139
+ --help, -h Show this help message
140
+ --force, -f Overwrite existing files (creates .bak backups)
141
+ `);
142
+ }
143
+
15
144
  // ═══════════════════════════════════════════════════════════════
16
145
  // COMMAND: create <project-name>
17
146
  // Creates a new JUX project from the template
@@ -256,51 +385,13 @@ function runServe() {
256
385
  child.on('close', (code) => process.exit(code || 0));
257
386
  }
258
387
 
259
- // ═══════════════════════════════════════════════════════════════
260
- // UTILITIES
261
- // ═══════════════════════════════════════════════════════════════
262
- function copyDirRecursive(src, dest) {
263
- if (!fs.existsSync(dest)) {
264
- fs.mkdirSync(dest, { recursive: true });
265
- }
266
-
267
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
268
- if (entry.name.startsWith('.')) continue; // Skip hidden files
269
-
270
- const srcPath = path.join(src, entry.name);
271
- const destPath = path.join(dest, entry.name);
272
-
273
- if (entry.isDirectory()) {
274
- copyDirRecursive(srcPath, destPath);
275
- } else {
276
- fs.copyFileSync(srcPath, destPath);
277
- }
278
- }
279
- }
280
-
281
- function showHelp() {
282
- console.log(`
283
- JUX CLI
284
-
285
- Usage:
286
- jux create <name> Create a new JUX project
287
- jux init Initialize JUX in current directory
288
- jux build Build for production
289
- jux serve Start production server
290
- jux serve --hot Start dev server with hot reload
291
- jux comp [name] Add a component preset to your project
292
-
293
- Options:
294
- --help, -h Show this help message
295
- `);
296
- }
297
-
298
388
  // ═══════════════════════════════════════════════════════════════
299
389
  // COMMAND: comp [name]
300
390
  // Copies a preset component into the project's jux directory
301
391
  // ═══════════════════════════════════════════════════════════════
302
392
  async function runComp(compName) {
303
393
  const presetsDir = path.join(PACKAGE_ROOT, 'presets');
394
+ const forceFlag = args.includes('--force') || args.includes('-f');
304
395
 
305
396
  if (!fs.existsSync(presetsDir)) {
306
397
  console.error('❌ No presets directory found in juxscript package.');
@@ -342,87 +433,109 @@ async function runComp(compName) {
342
433
  fs.mkdirSync(targetSrcDir, { recursive: true });
343
434
  }
344
435
 
345
- // Copy all files from preset to project src dir
346
- const presetFiles = fs.readdirSync(presetDir, { withFileTypes: true });
347
- let copied = 0;
436
+ // Resolve a safe destination folder name
437
+ const destFolderName = resolveDestFolderName(targetSrcDir, compName);
438
+ const destDir = path.join(targetSrcDir, destFolderName);
348
439
 
349
- for (const entry of presetFiles) {
350
- if (entry.isFile()) {
351
- const srcPath = path.join(presetDir, entry.name);
352
- const destPath = path.join(targetSrcDir, entry.name);
353
-
354
- if (fs.existsSync(destPath)) {
355
- const overwrite = await promptYesNo(` ⚠️ "${entry.name}" already exists. Overwrite?`);
356
- if (!overwrite) {
357
- console.log(` ⏭️ Skipped ${entry.name}`);
358
- continue;
359
- }
360
- }
440
+ // List what will be copied
441
+ const presetFiles = listFilesRecursive(presetDir);
361
442
 
362
- fs.copyFileSync(srcPath, destPath);
363
- console.log(` ${entry.name}`);
364
- copied++;
443
+ console.log(`\n📦 Preset: ${compName}`);
444
+ console.log(` Source: presets/${compName}/`);
445
+ console.log(` Target: ${path.relative(process.cwd(), destDir)}/\n`);
446
+
447
+ if (destFolderName !== compName) {
448
+ console.log(` ⚠️ "${compName}" already exists, using "${destFolderName}" instead.\n`);
449
+ }
450
+
451
+ console.log(` Files to copy:`);
452
+ presetFiles.forEach(f => console.log(` 📄 ${f}`));
453
+ console.log('');
454
+
455
+ // If dest exists (shouldn't with our naming, but just in case with --force)
456
+ if (fs.existsSync(destDir) && !forceFlag) {
457
+ const proceed = await promptYesNo(` Directory "${destFolderName}" exists. Overwrite contents?`);
458
+ if (!proceed) {
459
+ console.log(' Cancelled.\n');
460
+ process.exit(0);
365
461
  }
366
462
  }
367
463
 
368
- console.log(`\n✅ Copied ${copied} file(s) from preset "${compName}" to ${path.relative(process.cwd(), targetSrcDir)}/\n`);
369
- }
464
+ // Copy the entire preset folder
465
+ fs.mkdirSync(destDir, { recursive: true });
466
+ let copied = 0;
467
+ let backed = 0;
370
468
 
371
- function resolveProjectSrcDir() {
372
- const projectRoot = process.cwd();
373
- const configPath = path.join(projectRoot, 'juxconfig.js');
469
+ for (const relFile of presetFiles) {
470
+ const srcPath = path.join(presetDir, relFile);
471
+ const destPath = path.join(destDir, relFile);
374
472
 
375
- if (fs.existsSync(configPath)) {
376
- try {
377
- const configContent = fs.readFileSync(configPath, 'utf8');
378
- const srcDirMatch = configContent.match(/srcDir\s*:\s*['"]([^'"]+)['"]/);
379
- if (srcDirMatch) {
380
- return path.resolve(projectRoot, srcDirMatch[1]);
381
- }
382
- } catch (_) { }
473
+ // Ensure subdirectory exists
474
+ const destFileDir = path.dirname(destPath);
475
+ if (!fs.existsSync(destFileDir)) {
476
+ fs.mkdirSync(destFileDir, { recursive: true });
477
+ }
478
+
479
+ if (fs.existsSync(destPath)) {
480
+ // Backup existing
481
+ const backupPath = destPath + '.bak';
482
+ fs.copyFileSync(destPath, backupPath);
483
+ backed++;
484
+ console.log(` 🔄 ${relFile} (backed up to ${relFile}.bak)`);
485
+ } else {
486
+ console.log(` ✅ ${relFile}`);
487
+ }
488
+
489
+ fs.copyFileSync(srcPath, destPath);
490
+ copied++;
383
491
  }
384
492
 
385
- return path.resolve(projectRoot, 'jux');
493
+ console.log(`\n✅ Done: ${copied} file(s) copied to ${path.relative(process.cwd(), destDir)}/`);
494
+ if (backed > 0) {
495
+ console.log(` ${backed} existing file(s) backed up with .bak extension`);
496
+ }
497
+ console.log('');
386
498
  }
387
499
 
388
- function promptPresetSelection(presets) {
389
- return new Promise((resolve) => {
390
- console.log('\n📦 Available component presets:\n');
391
- presets.forEach((p, i) => {
392
- // Read files in preset to show what's included
393
- const presetDir = path.join(PACKAGE_ROOT, 'presets', p);
394
- const files = fs.readdirSync(presetDir).filter(f => !f.startsWith('.'));
395
- console.log(` ${i + 1}) ${p} (${files.join(', ')})`);
396
- });
397
- console.log(` 0) Cancel\n`);
398
-
399
- const rl = createInterface({ input: process.stdin, output: process.stdout });
400
- rl.question('Select a preset (number or name): ', (answer) => {
401
- rl.close();
402
- const trimmed = answer.trim();
500
+ /**
501
+ * Resolve a safe folder name: compName -> compName-jux -> compName-1, compName-2, ...
502
+ */
503
+ function resolveDestFolderName(parentDir, baseName) {
504
+ const first = path.join(parentDir, baseName);
505
+ if (!fs.existsSync(first)) return baseName;
403
506
 
404
- // By number
405
- const num = parseInt(trimmed, 10);
406
- if (num === 0) return resolve(null);
407
- if (num >= 1 && num <= presets.length) return resolve(presets[num - 1]);
507
+ const juxName = baseName + '-jux';
508
+ const second = path.join(parentDir, juxName);
509
+ if (!fs.existsSync(second)) return juxName;
408
510
 
409
- // By name
410
- if (presets.includes(trimmed)) return resolve(trimmed);
511
+ for (let i = 1; i <= 99; i++) {
512
+ const numbered = `${baseName}-${i}`;
513
+ if (!fs.existsSync(path.join(parentDir, numbered))) return numbered;
514
+ }
411
515
 
412
- console.error(`❌ Invalid selection: "${trimmed}"`);
413
- resolve(null);
414
- });
415
- });
516
+ // Last resort — timestamp
517
+ return `${baseName}-${Date.now()}`;
416
518
  }
417
519
 
418
- function promptYesNo(question) {
419
- return new Promise((resolve) => {
420
- const rl = createInterface({ input: process.stdin, output: process.stdout });
421
- rl.question(`${question} (y/N): `, (answer) => {
422
- rl.close();
423
- resolve(answer.trim().toLowerCase() === 'y');
424
- });
425
- });
520
+ /**
521
+ * List all files in a directory recursively, returning relative paths
522
+ */
523
+ function listFilesRecursive(dir, base = '') {
524
+ const results = [];
525
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
526
+
527
+ for (const entry of entries) {
528
+ if (entry.name.startsWith('.')) continue;
529
+ const rel = base ? path.join(base, entry.name) : entry.name;
530
+
531
+ if (entry.isDirectory()) {
532
+ results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
533
+ } else {
534
+ results.push(rel);
535
+ }
536
+ }
537
+
538
+ return results;
426
539
  }
427
540
 
428
541
  // ═══════════════════════════════════════════════════════════════
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.368",
3
+ "version": "1.1.370",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "./dist/lib/index.js",
@@ -1,5 +1,5 @@
1
1
  import { jux } from 'juxscript';
2
- import { juxSidebar } from './sidebar.jux';
2
+ import { juxSidebar } from './index.jux';
3
3
  const allRoutes = jux.routes.all();
4
4
 
5
5
  juxSidebar('my-sidebar', allRoutes,
File without changes