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
|
-
//
|
|
346
|
-
const
|
|
347
|
-
|
|
436
|
+
// Resolve a safe destination folder name
|
|
437
|
+
const destFolderName = resolveDestFolderName(targetSrcDir, compName);
|
|
438
|
+
const destDir = path.join(targetSrcDir, destFolderName);
|
|
348
439
|
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
369
|
-
}
|
|
464
|
+
// Copy the entire preset folder
|
|
465
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
466
|
+
let copied = 0;
|
|
467
|
+
let backed = 0;
|
|
370
468
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
469
|
+
for (const relFile of presetFiles) {
|
|
470
|
+
const srcPath = path.join(presetDir, relFile);
|
|
471
|
+
const destPath = path.join(destDir, relFile);
|
|
374
472
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
});
|
|
415
|
-
});
|
|
516
|
+
// Last resort — timestamp
|
|
517
|
+
return `${baseName}-${Date.now()}`;
|
|
416
518
|
}
|
|
417
519
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
File without changes
|