@nclamvn/vibecode-cli 2.1.0 → 2.2.1
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/vibecode.js +40 -4
- package/package.json +3 -1
- package/src/commands/go.js +291 -5
- package/src/commands/preview.js +554 -0
- package/src/commands/templates.js +397 -0
- package/src/index.js +15 -0
- package/src/templates/index.js +724 -0
package/bin/vibecode.js
CHANGED
|
@@ -33,6 +33,9 @@ import {
|
|
|
33
33
|
securityCommand,
|
|
34
34
|
askCommand,
|
|
35
35
|
migrateCommand,
|
|
36
|
+
// Phase M Commands
|
|
37
|
+
templatesCommand,
|
|
38
|
+
previewCommand,
|
|
36
39
|
VERSION
|
|
37
40
|
} from '../src/index.js';
|
|
38
41
|
|
|
@@ -133,13 +136,46 @@ program
|
|
|
133
136
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
137
|
|
|
135
138
|
program
|
|
136
|
-
.command('go
|
|
139
|
+
.command('go [description...]')
|
|
137
140
|
.description('Magic mode: one command, full automated build')
|
|
138
|
-
.option('-t, --template <
|
|
141
|
+
.option('-t, --template <id>', 'Use a template (run "vibecode templates" to see list)')
|
|
142
|
+
.option('--name <name>', 'Project/Company name')
|
|
143
|
+
.option('--color <color>', 'Primary brand color (hex)')
|
|
139
144
|
.option('-i, --iterate', 'Enable iterative build mode')
|
|
140
145
|
.option('-m, --max <n>', 'Max iterations for iterative mode', parseInt)
|
|
141
|
-
.option('-o, --open', 'Auto-open
|
|
142
|
-
.
|
|
146
|
+
.option('-o, --open', 'Auto-open folder when done')
|
|
147
|
+
.option('-p, --preview', 'Start dev server and open browser after build')
|
|
148
|
+
.option('--port <port>', 'Preview port number', '3000')
|
|
149
|
+
.option('--qr', 'Show QR code for mobile preview')
|
|
150
|
+
.action((description, options) => {
|
|
151
|
+
const desc = Array.isArray(description) ? description.join(' ') : description;
|
|
152
|
+
goCommand(desc, options);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
// Phase M Commands - Templates
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
program
|
|
160
|
+
.command('templates')
|
|
161
|
+
.alias('tpl')
|
|
162
|
+
.description('Browse and use project templates')
|
|
163
|
+
.option('-l, --list', 'List all templates')
|
|
164
|
+
.option('-i, --info <id>', 'Show template details')
|
|
165
|
+
.option('-s, --search <query>', 'Search templates')
|
|
166
|
+
.option('-p, --preview <id>', 'Preview template in browser')
|
|
167
|
+
.option('-q, --quiet', 'Non-interactive mode')
|
|
168
|
+
.action(templatesCommand);
|
|
169
|
+
|
|
170
|
+
program
|
|
171
|
+
.command('preview')
|
|
172
|
+
.description('Start dev server and open in browser')
|
|
173
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
174
|
+
.option('-q, --qr', 'Show QR code for mobile')
|
|
175
|
+
.option('-s, --stop', 'Stop running preview server')
|
|
176
|
+
.option('--no-open', 'Do not open browser')
|
|
177
|
+
.option('-d, --detach', 'Run in background')
|
|
178
|
+
.action(previewCommand);
|
|
143
179
|
|
|
144
180
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
181
|
// Phase F Commands - Agent Mode
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nclamvn/vibecode-cli",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Build software with discipline - AI coding with guardrails",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"fs-extra": "^11.2.0",
|
|
37
37
|
"inquirer": "^9.2.12",
|
|
38
|
+
"open": "^11.0.0",
|
|
38
39
|
"ora": "^8.0.1",
|
|
40
|
+
"qrcode": "^1.5.4",
|
|
39
41
|
"yaml": "^2.3.4"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
package/src/commands/go.js
CHANGED
|
@@ -41,20 +41,31 @@ import {
|
|
|
41
41
|
import { runTests } from '../core/test-runner.js';
|
|
42
42
|
import { analyzeErrors } from '../core/error-analyzer.js';
|
|
43
43
|
import { ensureDir, appendToFile } from '../utils/files.js';
|
|
44
|
+
import { getTemplate, getCategoryIcon } from '../templates/index.js';
|
|
44
45
|
|
|
45
46
|
const execAsync = promisify(exec);
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Magic Mode: One command, full build
|
|
49
50
|
* vibecode go "description" → Everything automated
|
|
51
|
+
* vibecode go --template <id> → Use template
|
|
50
52
|
*/
|
|
51
53
|
export async function goCommand(description, options = {}) {
|
|
54
|
+
// Handle template mode
|
|
55
|
+
if (options.template) {
|
|
56
|
+
return goWithTemplate(options.template, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
const startTime = Date.now();
|
|
53
60
|
|
|
61
|
+
// Handle description as array (from commander variadic)
|
|
62
|
+
const desc = Array.isArray(description) ? description.join(' ') : description;
|
|
63
|
+
|
|
54
64
|
// Validate description
|
|
55
|
-
if (!
|
|
65
|
+
if (!desc || desc.trim().length < 5) {
|
|
56
66
|
printError('Description too short. Please provide more details.');
|
|
57
67
|
console.log(chalk.gray('Example: vibecode go "Landing page for my startup"'));
|
|
68
|
+
console.log(chalk.gray('Or use: vibecode go --template landing-saas'));
|
|
58
69
|
process.exit(1);
|
|
59
70
|
}
|
|
60
71
|
|
|
@@ -67,7 +78,7 @@ export async function goCommand(description, options = {}) {
|
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
// Generate project name
|
|
70
|
-
const projectName = generateProjectName(
|
|
81
|
+
const projectName = options.name ? sanitizeProjectName(options.name) : generateProjectName(desc);
|
|
71
82
|
const projectPath = path.join(process.cwd(), projectName);
|
|
72
83
|
|
|
73
84
|
// Check if directory exists
|
|
@@ -78,7 +89,7 @@ export async function goCommand(description, options = {}) {
|
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
// Show magic header
|
|
81
|
-
showMagicHeader(
|
|
92
|
+
showMagicHeader(desc, projectName);
|
|
82
93
|
|
|
83
94
|
// Create backup of current directory before go command
|
|
84
95
|
// (backup in parent directory since we're creating a new project)
|
|
@@ -248,9 +259,26 @@ export async function goCommand(description, options = {}) {
|
|
|
248
259
|
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
|
|
249
260
|
showMagicSummary(projectName, projectPath, duration, results, options);
|
|
250
261
|
|
|
251
|
-
// Auto
|
|
252
|
-
if (options.
|
|
262
|
+
// Auto preview if requested
|
|
263
|
+
if (options.preview) {
|
|
264
|
+
console.log(chalk.cyan('\n 🚀 Starting preview...\n'));
|
|
265
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for files to settle
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const { autoPreview } = await import('./preview.js');
|
|
269
|
+
await autoPreview(projectPath, {
|
|
270
|
+
qr: options.qr,
|
|
271
|
+
port: options.port
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.log(chalk.yellow(` ⚠️ Preview failed: ${error.message}`));
|
|
275
|
+
console.log(chalk.gray(` Run manually: cd ${projectName} && vibecode preview\n`));
|
|
276
|
+
}
|
|
277
|
+
} else if (options.open) {
|
|
253
278
|
await openProject(projectPath);
|
|
279
|
+
} else {
|
|
280
|
+
// Show preview hint
|
|
281
|
+
console.log(chalk.gray(` 💡 Quick preview: ${chalk.cyan(`cd ${projectName} && vibecode preview`)}\n`));
|
|
254
282
|
}
|
|
255
283
|
|
|
256
284
|
} catch (error) {
|
|
@@ -385,3 +413,261 @@ async function openProject(projectPath) {
|
|
|
385
413
|
console.log(chalk.gray(`Could not auto-open: ${error.message}`));
|
|
386
414
|
}
|
|
387
415
|
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Sanitize project name
|
|
419
|
+
*/
|
|
420
|
+
function sanitizeProjectName(name) {
|
|
421
|
+
return name
|
|
422
|
+
.toLowerCase()
|
|
423
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
424
|
+
.replace(/\s+/g, '-')
|
|
425
|
+
.replace(/-+/g, '-')
|
|
426
|
+
.replace(/^-|-$/g, '')
|
|
427
|
+
.substring(0, 50);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Template Mode: Use pre-defined template
|
|
432
|
+
* vibecode go --template <id>
|
|
433
|
+
*/
|
|
434
|
+
async function goWithTemplate(templateId, options = {}) {
|
|
435
|
+
const template = getTemplate(templateId);
|
|
436
|
+
|
|
437
|
+
if (!template) {
|
|
438
|
+
printError(`Template "${templateId}" not found.`);
|
|
439
|
+
console.log(chalk.gray('Run "vibecode templates" to see available templates.'));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Check Claude Code availability
|
|
444
|
+
const claudeAvailable = await isClaudeCodeAvailable();
|
|
445
|
+
if (!claudeAvailable) {
|
|
446
|
+
printError('Claude Code CLI not found.');
|
|
447
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const startTime = Date.now();
|
|
452
|
+
const icon = getCategoryIcon(template.category);
|
|
453
|
+
|
|
454
|
+
// Generate project name
|
|
455
|
+
const projectName = options.name
|
|
456
|
+
? sanitizeProjectName(options.name)
|
|
457
|
+
: `${templateId}-${Date.now().toString(36)}`;
|
|
458
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
459
|
+
|
|
460
|
+
// Check if directory exists
|
|
461
|
+
if (await fs.pathExists(projectPath)) {
|
|
462
|
+
printError(`Directory already exists: ${projectName}`);
|
|
463
|
+
console.log(chalk.gray('Use --name to specify a different name.'));
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Show template header
|
|
468
|
+
showTemplateHeader(template, projectName, icon);
|
|
469
|
+
|
|
470
|
+
// Build customized prompt from template
|
|
471
|
+
let prompt = template.prompt;
|
|
472
|
+
|
|
473
|
+
// Apply template variables from options
|
|
474
|
+
for (const [key, config] of Object.entries(template.variables)) {
|
|
475
|
+
const value = options[key] !== undefined ? options[key] : config.default;
|
|
476
|
+
// Replace placeholders like {name} in prompt
|
|
477
|
+
prompt = prompt.replace(new RegExp(`\\{${key}\\}`, 'gi'), value);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Add customizations from common options
|
|
481
|
+
if (options.name) {
|
|
482
|
+
prompt += `\n\nProject/Company name: "${options.name}"`;
|
|
483
|
+
}
|
|
484
|
+
if (options.color) {
|
|
485
|
+
prompt += `\n\nPrimary brand color: ${options.color}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Create backup
|
|
489
|
+
const backup = new BackupManager(process.cwd());
|
|
490
|
+
await backup.createBackup('go-template');
|
|
491
|
+
|
|
492
|
+
// Define steps
|
|
493
|
+
const steps = [
|
|
494
|
+
{ name: 'INIT', label: 'Creating project', weight: 5 },
|
|
495
|
+
{ name: 'SETUP', label: 'Setting up template', weight: 5 },
|
|
496
|
+
{ name: 'BUILD', label: `Building ${template.name}`, weight: 80 },
|
|
497
|
+
{ name: 'REVIEW', label: 'Running tests', weight: 10 }
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
const results = {
|
|
501
|
+
templateId,
|
|
502
|
+
templateName: template.name
|
|
503
|
+
};
|
|
504
|
+
let currentProgress = 0;
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
// Step 1: INIT
|
|
508
|
+
await executeStep(steps[0], currentProgress, async () => {
|
|
509
|
+
await fs.ensureDir(projectPath);
|
|
510
|
+
process.chdir(projectPath);
|
|
511
|
+
await createWorkspace();
|
|
512
|
+
results.projectPath = projectPath;
|
|
513
|
+
});
|
|
514
|
+
currentProgress += steps[0].weight;
|
|
515
|
+
|
|
516
|
+
// Step 2: SETUP
|
|
517
|
+
await executeStep(steps[1], currentProgress, async () => {
|
|
518
|
+
const sessionId = await createSession(projectName);
|
|
519
|
+
results.sessionId = sessionId;
|
|
520
|
+
|
|
521
|
+
// Write template info to session
|
|
522
|
+
const templateInfo = `# Template: ${template.name}
|
|
523
|
+
|
|
524
|
+
## ID: ${templateId}
|
|
525
|
+
## Category: ${template.category}
|
|
526
|
+
## Stack: ${template.stack.join(', ')}
|
|
527
|
+
|
|
528
|
+
## Features
|
|
529
|
+
${template.features.map(f => `- ${f}`).join('\n')}
|
|
530
|
+
|
|
531
|
+
## Prompt
|
|
532
|
+
${prompt}
|
|
533
|
+
`;
|
|
534
|
+
await writeSessionFile('template.md', templateInfo);
|
|
535
|
+
await transitionTo(STATES.INTAKE_CAPTURED, 'template_setup');
|
|
536
|
+
});
|
|
537
|
+
currentProgress += steps[1].weight;
|
|
538
|
+
|
|
539
|
+
// Step 3: BUILD
|
|
540
|
+
await executeStep(steps[2], currentProgress, async () => {
|
|
541
|
+
const sessionPath = await getCurrentSessionPath();
|
|
542
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
543
|
+
await ensureDir(evidencePath);
|
|
544
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
545
|
+
|
|
546
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'template_build_start');
|
|
547
|
+
|
|
548
|
+
const fullPrompt = await buildPromptWithContext(prompt, process.cwd());
|
|
549
|
+
|
|
550
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Template Build: ${templateId}\n`);
|
|
551
|
+
await appendToFile(logPath, `Prompt:\n${prompt}\n\n`);
|
|
552
|
+
|
|
553
|
+
const buildResult = await spawnClaudeCode(fullPrompt, {
|
|
554
|
+
cwd: process.cwd(),
|
|
555
|
+
logPath: logPath
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Build completed with code: ${buildResult.code}\n`);
|
|
559
|
+
|
|
560
|
+
// Count files created
|
|
561
|
+
const files = await fs.readdir(process.cwd());
|
|
562
|
+
results.filesCreated = files.filter(f => !f.startsWith('.')).length;
|
|
563
|
+
|
|
564
|
+
const freshState = await loadState();
|
|
565
|
+
freshState.build_completed = new Date().toISOString();
|
|
566
|
+
freshState.template_used = templateId;
|
|
567
|
+
await saveState(freshState);
|
|
568
|
+
|
|
569
|
+
await transitionTo(STATES.BUILD_DONE, 'template_build_done');
|
|
570
|
+
});
|
|
571
|
+
currentProgress += steps[2].weight;
|
|
572
|
+
|
|
573
|
+
// Step 4: REVIEW
|
|
574
|
+
await executeStep(steps[3], currentProgress, async () => {
|
|
575
|
+
const testResult = await runTests(process.cwd());
|
|
576
|
+
results.testsPassed = testResult.summary.passed;
|
|
577
|
+
results.testsTotal = testResult.summary.total;
|
|
578
|
+
results.allPassed = testResult.passed;
|
|
579
|
+
|
|
580
|
+
await transitionTo(STATES.REVIEW_PASSED, 'template_review');
|
|
581
|
+
});
|
|
582
|
+
currentProgress = 100;
|
|
583
|
+
|
|
584
|
+
// Show final progress
|
|
585
|
+
console.log(renderProgressBar(100));
|
|
586
|
+
console.log();
|
|
587
|
+
|
|
588
|
+
// Show summary
|
|
589
|
+
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
|
|
590
|
+
showTemplateSummary(template, projectName, projectPath, duration, results, options);
|
|
591
|
+
|
|
592
|
+
// Auto preview if requested
|
|
593
|
+
if (options.preview) {
|
|
594
|
+
console.log(chalk.cyan('\n 🚀 Starting preview...\n'));
|
|
595
|
+
await sleep(1000); // Wait for files to settle
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
const { autoPreview } = await import('./preview.js');
|
|
599
|
+
await autoPreview(projectPath, {
|
|
600
|
+
qr: options.qr,
|
|
601
|
+
port: options.port
|
|
602
|
+
});
|
|
603
|
+
} catch (error) {
|
|
604
|
+
console.log(chalk.yellow(` ⚠️ Preview failed: ${error.message}`));
|
|
605
|
+
console.log(chalk.gray(` Run manually: cd ${projectName} && vibecode preview\n`));
|
|
606
|
+
}
|
|
607
|
+
} else if (options.open) {
|
|
608
|
+
await openProject(projectPath);
|
|
609
|
+
} else {
|
|
610
|
+
// Show preview hint
|
|
611
|
+
console.log(chalk.gray(` 💡 Quick preview: ${chalk.cyan(`cd ${projectName} && vibecode preview`)}\n`));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
} catch (error) {
|
|
615
|
+
console.log();
|
|
616
|
+
printError(`Template build failed: ${error.message}`);
|
|
617
|
+
console.log(chalk.gray(`Project location: ${projectPath}`));
|
|
618
|
+
console.log(chalk.gray('Check .vibecode/sessions/*/evidence/build.log for details.'));
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function sleep(ms) {
|
|
624
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Show template mode header
|
|
629
|
+
*/
|
|
630
|
+
function showTemplateHeader(template, projectName, icon) {
|
|
631
|
+
console.log();
|
|
632
|
+
console.log(chalk.magenta('╭' + '─'.repeat(68) + '╮'));
|
|
633
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
634
|
+
console.log(chalk.magenta('│') + chalk.bold.white(` ${icon} TEMPLATE: ${template.name.toUpperCase()}`) + ' '.repeat(Math.max(0, 53 - template.name.length)) + chalk.magenta('│'));
|
|
635
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
636
|
+
|
|
637
|
+
const desc = template.description.substring(0, 55);
|
|
638
|
+
console.log(chalk.magenta('│') + chalk.gray(` ${desc}`) + ' '.repeat(Math.max(0, 65 - desc.length)) + chalk.magenta('│'));
|
|
639
|
+
|
|
640
|
+
console.log(chalk.magenta('│') + chalk.gray(` Stack: ${template.stack.join(', ').substring(0, 52)}`) + ' '.repeat(Math.max(0, 55 - template.stack.join(', ').length)) + chalk.magenta('│'));
|
|
641
|
+
console.log(chalk.magenta('│') + chalk.gray(` → ${projectName}`) + ' '.repeat(Math.max(0, 63 - projectName.length)) + chalk.magenta('│'));
|
|
642
|
+
|
|
643
|
+
console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
|
|
644
|
+
console.log(chalk.magenta('╰' + '─'.repeat(68) + '╯'));
|
|
645
|
+
console.log();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Show template mode summary
|
|
650
|
+
*/
|
|
651
|
+
function showTemplateSummary(template, projectName, projectPath, duration, results, options) {
|
|
652
|
+
const icon = getCategoryIcon(template.category);
|
|
653
|
+
const testsStatus = results.allPassed
|
|
654
|
+
? chalk.green(`${results.testsPassed}/${results.testsTotal} passed`)
|
|
655
|
+
: chalk.yellow(`${results.testsPassed}/${results.testsTotal} passed`);
|
|
656
|
+
|
|
657
|
+
console.log(chalk.green('╭' + '─'.repeat(68) + '╮'));
|
|
658
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
659
|
+
console.log(chalk.green('│') + chalk.bold.white(` ${icon} ${template.name.toUpperCase()} - COMPLETE!`) + ' '.repeat(Math.max(0, 48 - template.name.length)) + chalk.green('│'));
|
|
660
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
661
|
+
console.log(chalk.green('│') + chalk.white(` 📁 Project: ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
|
|
662
|
+
console.log(chalk.green('│') + chalk.white(` 📦 Template: ${template.id}`) + ' '.repeat(Math.max(0, 51 - template.id.length)) + chalk.green('│'));
|
|
663
|
+
console.log(chalk.green('│') + chalk.white(` 📄 Files: ${results.filesCreated} created`) + ' '.repeat(42) + chalk.green('│'));
|
|
664
|
+
console.log(chalk.green('│') + chalk.white(` 🧪 Tests: `) + testsStatus + ' '.repeat(Math.max(0, 40)) + chalk.green('│'));
|
|
665
|
+
console.log(chalk.green('│') + chalk.white(` ⏱️ Duration: ${duration} minutes`) + ' '.repeat(Math.max(0, 44 - duration.length)) + chalk.green('│'));
|
|
666
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
667
|
+
console.log(chalk.green('│') + chalk.gray(` 💡 Next steps:`) + ' '.repeat(50) + chalk.green('│'));
|
|
668
|
+
console.log(chalk.green('│') + chalk.gray(` cd ${projectName}`) + ' '.repeat(Math.max(0, 57 - projectName.length)) + chalk.green('│'));
|
|
669
|
+
console.log(chalk.green('│') + chalk.gray(` npm install && npm run dev`) + ' '.repeat(33) + chalk.green('│'));
|
|
670
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
671
|
+
console.log(chalk.green('╰' + '─'.repeat(68) + '╯'));
|
|
672
|
+
console.log();
|
|
673
|
+
}
|