webspresso 0.0.13 → 0.0.14

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.
Files changed (41) hide show
  1. package/README.md +4 -7
  2. package/bin/commands/add-tailwind.js +151 -0
  3. package/bin/commands/api.js +70 -0
  4. package/bin/commands/db-make.js +76 -0
  5. package/bin/commands/db-migrate.js +43 -0
  6. package/bin/commands/db-rollback.js +48 -0
  7. package/bin/commands/db-status.js +53 -0
  8. package/bin/commands/dev.js +73 -0
  9. package/bin/commands/new.js +634 -0
  10. package/bin/commands/page.js +134 -0
  11. package/bin/commands/seed.js +154 -0
  12. package/bin/commands/start.js +30 -0
  13. package/bin/utils/db.js +54 -0
  14. package/bin/utils/migration.js +36 -0
  15. package/bin/utils/project.js +97 -0
  16. package/bin/utils/seed.js +112 -0
  17. package/bin/webspresso.js +24 -1696
  18. package/core/orm/index.js +14 -1
  19. package/core/orm/migrations/scaffold.js +5 -0
  20. package/core/orm/model.js +8 -0
  21. package/core/orm/schema-helpers.js +39 -1
  22. package/core/orm/seeder.js +56 -3
  23. package/core/orm/types.js +28 -1
  24. package/index.js +2 -1
  25. package/package.json +1 -1
  26. package/plugins/admin-panel/admin-user-model.js +42 -0
  27. package/plugins/admin-panel/api.js +436 -0
  28. package/plugins/admin-panel/app.js +68 -0
  29. package/plugins/admin-panel/auth.js +157 -0
  30. package/plugins/admin-panel/components.js +359 -0
  31. package/plugins/admin-panel/field-renderers/array.js +57 -0
  32. package/plugins/admin-panel/field-renderers/basic.js +205 -0
  33. package/plugins/admin-panel/field-renderers/file-upload.js +124 -0
  34. package/plugins/admin-panel/field-renderers/index.js +93 -0
  35. package/plugins/admin-panel/field-renderers/json.js +52 -0
  36. package/plugins/admin-panel/field-renderers/relations.js +96 -0
  37. package/plugins/admin-panel/field-renderers/rich-text.js +83 -0
  38. package/plugins/admin-panel/index.js +187 -0
  39. package/plugins/admin-panel/migration-template.js +39 -0
  40. package/plugins/admin-panel/styles.js +9 -0
  41. package/plugins/index.js +2 -0
@@ -0,0 +1,634 @@
1
+ /**
2
+ * New Project Command
3
+ * Creates a new Webspresso project
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { runInstallation, startDevServer } = require('../utils/project');
10
+ const { getSeedFileTemplate } = require('../utils/seed');
11
+
12
+ function registerCommand(program) {
13
+ program
14
+ .command('new [project-name]')
15
+ .description('Create a new Webspresso project')
16
+ .option('-t, --template <template>', 'Template to use (minimal, full)', 'minimal')
17
+ .option('--no-tailwind', 'Skip Tailwind CSS setup')
18
+ .option('-i, --install', 'Auto install dependencies and build CSS')
19
+ .action(async (projectNameArg, options) => {
20
+ const useTailwind = options.tailwind !== false;
21
+ const autoInstall = options.install === true;
22
+
23
+ let projectName;
24
+ let projectPath;
25
+ let useCurrentDir = false;
26
+
27
+ if (!projectNameArg) {
28
+ // No project name provided - ask if they want to use current directory
29
+ const currentDirName = path.basename(process.cwd());
30
+ const currentDirFiles = fs.readdirSync(process.cwd());
31
+ const hasExistingFiles = currentDirFiles.some(f => !f.startsWith('.'));
32
+
33
+ const { useCurrent } = await inquirer.prompt([
34
+ {
35
+ type: 'confirm',
36
+ name: 'useCurrent',
37
+ message: `Install in current directory (${currentDirName})?`,
38
+ default: true
39
+ }
40
+ ]);
41
+
42
+ if (useCurrent) {
43
+ useCurrentDir = true;
44
+ projectPath = process.cwd();
45
+
46
+ // Check for existing Webspresso files
47
+ if (fs.existsSync(path.join(projectPath, 'server.js')) ||
48
+ fs.existsSync(path.join(projectPath, 'pages'))) {
49
+ console.error('❌ Current directory already contains a Webspresso project!');
50
+ process.exit(1);
51
+ }
52
+
53
+ // Warn if there are existing files
54
+ if (hasExistingFiles) {
55
+ const { continueAnyway } = await inquirer.prompt([
56
+ {
57
+ type: 'confirm',
58
+ name: 'continueAnyway',
59
+ message: '⚠️ Current directory is not empty. Continue anyway?',
60
+ default: false
61
+ }
62
+ ]);
63
+
64
+ if (!continueAnyway) {
65
+ console.log('Cancelled.');
66
+ process.exit(0);
67
+ }
68
+ }
69
+
70
+ // Ask for project name (for package.json)
71
+ const { name } = await inquirer.prompt([
72
+ {
73
+ type: 'input',
74
+ name: 'name',
75
+ message: 'Project name:',
76
+ default: currentDirName,
77
+ validate: (input) => {
78
+ if (!input.trim()) return 'Project name is required';
79
+ if (!/^[a-z0-9-_]+$/i.test(input)) return 'Project name can only contain letters, numbers, hyphens, and underscores';
80
+ return true;
81
+ }
82
+ }
83
+ ]);
84
+
85
+ projectName = name;
86
+ } else {
87
+ // Ask for directory name
88
+ const { dirName } = await inquirer.prompt([
89
+ {
90
+ type: 'input',
91
+ name: 'dirName',
92
+ message: 'Project directory name:',
93
+ validate: (input) => {
94
+ if (!input.trim()) return 'Directory name is required';
95
+ if (!/^[a-z0-9-_]+$/i.test(input)) return 'Directory name can only contain letters, numbers, hyphens, and underscores';
96
+ if (fs.existsSync(path.resolve(input))) return `Directory ${input} already exists!`;
97
+ return true;
98
+ }
99
+ }
100
+ ]);
101
+
102
+ projectName = dirName;
103
+ projectPath = path.resolve(dirName);
104
+ }
105
+ } else {
106
+ projectName = projectNameArg;
107
+ projectPath = path.resolve(projectNameArg);
108
+
109
+ if (fs.existsSync(projectPath)) {
110
+ console.error(`❌ Directory ${projectName} already exists!`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ console.log(`\n🚀 Creating new Webspresso project: ${projectName}\n`);
116
+
117
+ // Ask about database
118
+ const { useDatabase, databaseType } = await inquirer.prompt([
119
+ {
120
+ type: 'confirm',
121
+ name: 'useDatabase',
122
+ message: 'Will you use a database?',
123
+ default: false
124
+ },
125
+ {
126
+ type: 'list',
127
+ name: 'databaseType',
128
+ message: 'Which database?',
129
+ choices: [
130
+ { name: 'SQLite (better-sqlite3)', value: 'better-sqlite3' },
131
+ { name: 'PostgreSQL (pg)', value: 'pg' },
132
+ { name: 'MySQL (mysql2)', value: 'mysql2' },
133
+ { name: 'None', value: null }
134
+ ],
135
+ default: 'better-sqlite3',
136
+ when: (answers) => answers.useDatabase
137
+ }
138
+ ]);
139
+
140
+ // Ask about seed data if database is selected
141
+ let useSeed = false;
142
+ if (useDatabase && databaseType) {
143
+ const { generateSeed } = await inquirer.prompt([
144
+ {
145
+ type: 'confirm',
146
+ name: 'generateSeed',
147
+ message: 'Generate seed data based on existing models?',
148
+ default: false
149
+ }
150
+ ]);
151
+ useSeed = generateSeed;
152
+ }
153
+
154
+ // Create directory structure (skip root if using current dir)
155
+ if (!useCurrentDir) {
156
+ fs.mkdirSync(projectPath, { recursive: true });
157
+ }
158
+ fs.mkdirSync(path.join(projectPath, 'pages'), { recursive: true });
159
+ fs.mkdirSync(path.join(projectPath, 'pages', 'locales'), { recursive: true });
160
+ fs.mkdirSync(path.join(projectPath, 'views'), { recursive: true });
161
+ fs.mkdirSync(path.join(projectPath, 'public'), { recursive: true });
162
+
163
+ // Create models directory if database is selected
164
+ if (useDatabase && databaseType) {
165
+ fs.mkdirSync(path.join(projectPath, 'models'), { recursive: true });
166
+ }
167
+
168
+ // Create package.json
169
+ const packageJson = {
170
+ name: projectName,
171
+ version: '1.0.0',
172
+ description: 'Webspresso project',
173
+ main: 'server.js',
174
+ scripts: {
175
+ dev: 'node --watch server.js',
176
+ start: 'NODE_ENV=production node server.js'
177
+ },
178
+ dependencies: {
179
+ webspresso: '*',
180
+ dotenv: '^16.3.1'
181
+ }
182
+ };
183
+
184
+ // Add database driver if selected
185
+ if (useDatabase && databaseType) {
186
+ const dbDrivers = {
187
+ 'better-sqlite3': '^9.0.0',
188
+ 'pg': '^8.0.0',
189
+ 'mysql2': '^3.0.0'
190
+ };
191
+
192
+ packageJson.dependencies[databaseType] = dbDrivers[databaseType];
193
+ }
194
+
195
+ // Add faker if seed is selected
196
+ if (useSeed) {
197
+ packageJson.dependencies['@faker-js/faker'] = '^8.0.0';
198
+ packageJson.scripts.seed = 'node seeds/index.js';
199
+ }
200
+
201
+ fs.writeFileSync(
202
+ path.join(projectPath, 'package.json'),
203
+ JSON.stringify(packageJson, null, 2) + '\n'
204
+ );
205
+
206
+ // Create server.js
207
+ const serverJs = `require('dotenv').config();
208
+ const { createApp } = require('webspresso');
209
+ const path = require('path');
210
+
211
+ const { app } = createApp({
212
+ pagesDir: path.join(__dirname, 'pages'),
213
+ viewsDir: path.join(__dirname, 'views'),
214
+ publicDir: path.join(__dirname, 'public')
215
+ });
216
+
217
+ const PORT = process.env.PORT || 3000;
218
+
219
+ app.listen(PORT, () => {
220
+ console.log(\`🚀 Server running at http://localhost:\${PORT}\`);
221
+ });
222
+ `;
223
+
224
+ fs.writeFileSync(path.join(projectPath, 'server.js'), serverJs);
225
+
226
+ // Create .env.example
227
+ let envExample = `PORT=3000
228
+ NODE_ENV=development
229
+ DEFAULT_LOCALE=en
230
+ SUPPORTED_LOCALES=en,tr
231
+ BASE_URL=http://localhost:3000
232
+ `;
233
+
234
+ if (useDatabase && databaseType) {
235
+ if (databaseType === 'better-sqlite3') {
236
+ envExample += `DATABASE_URL=sqlite:./database.sqlite
237
+ `;
238
+ } else if (databaseType === 'pg') {
239
+ envExample += `DATABASE_URL=postgresql://user:password@localhost:5432/dbname
240
+ `;
241
+ } else if (databaseType === 'mysql2') {
242
+ envExample += `DATABASE_URL=mysql://user:password@localhost:3306/dbname
243
+ `;
244
+ }
245
+ }
246
+
247
+ fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
248
+
249
+ // Create database config if database is selected
250
+ if (useDatabase && databaseType) {
251
+ let dbConfig = '';
252
+
253
+ if (databaseType === 'better-sqlite3') {
254
+ dbConfig = `module.exports = {
255
+ client: 'better-sqlite3',
256
+ connection: {
257
+ filename: process.env.DATABASE_URL?.replace('sqlite:', '') || './database.sqlite'
258
+ },
259
+ migrations: {
260
+ directory: './migrations',
261
+ tableName: 'knex_migrations',
262
+ },
263
+ useNullAsDefault: true
264
+ };
265
+ `;
266
+ } else if (databaseType === 'pg') {
267
+ dbConfig = `module.exports = {
268
+ client: 'pg',
269
+ connection: process.env.DATABASE_URL,
270
+ migrations: {
271
+ directory: './migrations',
272
+ tableName: 'knex_migrations',
273
+ },
274
+ pool: {
275
+ min: 2,
276
+ max: 10
277
+ }
278
+ };
279
+ `;
280
+ } else if (databaseType === 'mysql2') {
281
+ dbConfig = `module.exports = {
282
+ client: 'mysql2',
283
+ connection: process.env.DATABASE_URL,
284
+ migrations: {
285
+ directory: './migrations',
286
+ tableName: 'knex_migrations',
287
+ },
288
+ pool: {
289
+ min: 2,
290
+ max: 10
291
+ }
292
+ };
293
+ `;
294
+ }
295
+
296
+ fs.writeFileSync(path.join(projectPath, 'webspresso.db.js'), dbConfig);
297
+
298
+ // Create migrations directory
299
+ fs.mkdirSync(path.join(projectPath, 'migrations'), { recursive: true });
300
+ }
301
+
302
+ // Create seed files if seed is selected
303
+ if (useSeed) {
304
+ fs.mkdirSync(path.join(projectPath, 'seeds'), { recursive: true });
305
+
306
+ const seedIndex = getSeedFileTemplate();
307
+
308
+ fs.writeFileSync(path.join(projectPath, 'seeds', 'index.js'), seedIndex);
309
+ }
310
+
311
+ // Create .gitignore
312
+ const gitignore = `node_modules/
313
+ .env
314
+ .env.local
315
+ .DS_Store
316
+ coverage/
317
+ *.log
318
+ `;
319
+
320
+ fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
321
+
322
+ // Create default layout
323
+ let layoutNjk;
324
+
325
+ if (useTailwind) {
326
+ layoutNjk = `<!DOCTYPE html>
327
+ <html lang="{{ locale or 'en' }}">
328
+ <head>
329
+ <meta charset="UTF-8">
330
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
331
+ <title>{{ meta.title or 'Webspresso' }}</title>
332
+ {% if meta.description %}
333
+ <meta name="description" content="{{ meta.description }}">
334
+ {% endif %}
335
+ {% if meta.canonical %}
336
+ <link rel="canonical" href="{{ meta.canonical }}">
337
+ {% else %}
338
+ <link rel="canonical" href="{{ fsy.canonical() }}">
339
+ {% endif %}
340
+ <link rel="stylesheet" href="/css/style.css">
341
+ </head>
342
+ <body class="min-h-screen flex flex-col bg-gray-50">
343
+ <nav class="bg-white shadow-sm border-b border-gray-200">
344
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
345
+ <div class="flex justify-between h-16">
346
+ <div class="flex items-center">
347
+ <a href="/" class="text-xl font-bold text-gray-900 hover:text-gray-700">
348
+ {{ t('site.name') or 'Webspresso' }}
349
+ </a>
350
+ </div>
351
+ <div class="flex items-center space-x-4">
352
+ <a href="/" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium {% if fsy.isPath('/') %}text-blue-600 bg-blue-50{% endif %}">
353
+ {{ t('nav.home') or 'Home' }}
354
+ </a>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </nav>
359
+
360
+ <main class="flex-1 max-w-7xl mx-auto py-6 sm:px-6 lg:px-8 w-full">
361
+ {% block content %}{% endblock %}
362
+ </main>
363
+
364
+ <footer class="bg-white border-t border-gray-200">
365
+ <div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
366
+ <p class="text-center text-sm text-gray-500">
367
+ {{ t('footer.copyright') or '© 2025 Webspresso. All rights reserved.' }}
368
+ </p>
369
+ </div>
370
+ </footer>
371
+ </body>
372
+ </html>
373
+ `;
374
+ } else {
375
+ layoutNjk = `<!DOCTYPE html>
376
+ <html lang="{{ locale or 'en' }}">
377
+ <head>
378
+ <meta charset="UTF-8">
379
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
380
+ <title>{{ meta.title or 'Webspresso' }}</title>
381
+ {% if meta.description %}
382
+ <meta name="description" content="{{ meta.description }}">
383
+ {% endif %}
384
+ <script src="https://cdn.tailwindcss.com"></script>
385
+ </head>
386
+ <body>
387
+ <main>
388
+ {% block content %}{% endblock %}
389
+ </main>
390
+ </body>
391
+ </html>
392
+ `;
393
+ }
394
+
395
+ fs.writeFileSync(path.join(projectPath, 'views', 'layout.njk'), layoutNjk);
396
+
397
+ // Create index page
398
+ let indexNjk;
399
+
400
+ if (useTailwind) {
401
+ indexNjk = `{% extends "layout.njk" %}
402
+
403
+ {% block content %}
404
+ <div class="px-4 py-16 sm:px-6 lg:px-8">
405
+ <div class="text-center">
406
+ <h1 class="text-4xl font-bold text-gray-900 sm:text-5xl md:text-6xl">
407
+ {{ t('welcome') or 'Welcome to Webspresso' }}
408
+ </h1>
409
+ <p class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
410
+ {{ t('description') or 'Start building your SSR app!' }}
411
+ </p>
412
+ <div class="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
413
+ <div class="rounded-md shadow">
414
+ <a href="/" class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 md:py-4 md:text-lg md:px-10">
415
+ Get started
416
+ </a>
417
+ </div>
418
+ </div>
419
+ </div>
420
+ </div>
421
+ {% endblock %}
422
+ `;
423
+ } else {
424
+ indexNjk = `{% extends "layout.njk" %}
425
+
426
+ {% block content %}
427
+ <div>
428
+ <h1>{{ t('welcome') or 'Welcome to Webspresso' }}</h1>
429
+ <p>{{ t('description') or 'Start building your SSR app!' }}</p>
430
+ </div>
431
+ {% endblock %}
432
+ `;
433
+ }
434
+
435
+ fs.writeFileSync(path.join(projectPath, 'pages', 'index.njk'), indexNjk);
436
+
437
+ // Create locales
438
+ const enJson = {
439
+ site: {
440
+ name: 'Webspresso'
441
+ },
442
+ nav: {
443
+ home: 'Home'
444
+ },
445
+ footer: {
446
+ copyright: '© 2025 Webspresso. All rights reserved.'
447
+ },
448
+ welcome: 'Welcome to Webspresso',
449
+ description: 'Start building your SSR app!'
450
+ };
451
+
452
+ fs.writeFileSync(
453
+ path.join(projectPath, 'pages', 'locales', 'en.json'),
454
+ JSON.stringify(enJson, null, 2) + '\n'
455
+ );
456
+
457
+ const trJson = {
458
+ site: {
459
+ name: 'Webspresso'
460
+ },
461
+ nav: {
462
+ home: 'Ana Sayfa'
463
+ },
464
+ footer: {
465
+ copyright: '© 2025 Webspresso. Tüm hakları saklıdır.'
466
+ },
467
+ welcome: 'Webspresso\'ya Hoş Geldiniz',
468
+ description: 'SSR uygulamanızı oluşturmaya başlayın!'
469
+ };
470
+
471
+ fs.writeFileSync(
472
+ path.join(projectPath, 'pages', 'locales', 'tr.json'),
473
+ JSON.stringify(trJson, null, 2) + '\n'
474
+ );
475
+
476
+ // Create README
477
+ const readme = `# ${projectName}
478
+
479
+ Webspresso project
480
+
481
+ ## Getting Started
482
+
483
+ \`\`\`bash
484
+ npm install
485
+ npm run dev
486
+ \`\`\`
487
+
488
+ Visit http://localhost:3000
489
+ `;
490
+
491
+ fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
492
+
493
+ // Add Tailwind if requested (default: true)
494
+ if (useTailwind) {
495
+ console.log('\n🎨 Setting up Tailwind CSS...\n');
496
+
497
+ // Create src directory
498
+ fs.mkdirSync(path.join(projectPath, 'src'), { recursive: true });
499
+
500
+ // Create input.css
501
+ const inputCss = `@tailwind base;
502
+ @tailwind components;
503
+ @tailwind utilities;
504
+ `;
505
+ fs.writeFileSync(path.join(projectPath, 'src', 'input.css'), inputCss);
506
+
507
+ // Create tailwind.config.js
508
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
509
+ module.exports = {
510
+ content: [
511
+ './pages/**/*.{njk,js}',
512
+ './views/**/*.njk',
513
+ './src/**/*.js'
514
+ ],
515
+ theme: {
516
+ extend: {},
517
+ },
518
+ plugins: [],
519
+ }
520
+ `;
521
+ fs.writeFileSync(path.join(projectPath, 'tailwind.config.js'), tailwindConfig);
522
+
523
+ // Create postcss.config.js
524
+ const postcssConfig = `module.exports = {
525
+ plugins: {
526
+ tailwindcss: {},
527
+ autoprefixer: {},
528
+ },
529
+ }
530
+ `;
531
+ fs.writeFileSync(path.join(projectPath, 'postcss.config.js'), postcssConfig);
532
+
533
+ // Create public/css directory
534
+ fs.mkdirSync(path.join(projectPath, 'public', 'css'), { recursive: true });
535
+
536
+ // Create placeholder CSS (will be replaced by build)
537
+ fs.writeFileSync(path.join(projectPath, 'public', 'css', 'style.css'), '/* Run npm run build:css */\n');
538
+
539
+ // Update package.json
540
+ const updatedPackageJson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
541
+ updatedPackageJson.devDependencies = updatedPackageJson.devDependencies || {};
542
+ updatedPackageJson.devDependencies['tailwindcss'] = '^3.4.1';
543
+ updatedPackageJson.devDependencies['postcss'] = '^8.4.35';
544
+ updatedPackageJson.devDependencies['autoprefixer'] = '^10.4.17';
545
+
546
+ updatedPackageJson.scripts['build:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --minify';
547
+ updatedPackageJson.scripts['watch:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --watch';
548
+ updatedPackageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
549
+ updatedPackageJson.scripts.start = 'npm run build:css && NODE_ENV=production node server.js';
550
+
551
+ fs.writeFileSync(
552
+ path.join(projectPath, 'package.json'),
553
+ JSON.stringify(updatedPackageJson, null, 2) + '\n'
554
+ );
555
+
556
+ console.log('✅ Tailwind CSS setup complete!');
557
+ }
558
+
559
+
560
+ // Auto install if requested or ask interactively
561
+ if (autoInstall) {
562
+ await runInstallation(projectPath, useTailwind);
563
+
564
+ // Ask if user wants to start dev server
565
+ const { shouldStartDev } = await inquirer.prompt([
566
+ {
567
+ type: 'confirm',
568
+ name: 'shouldStartDev',
569
+ message: 'Start development server?',
570
+ default: true
571
+ }
572
+ ]);
573
+
574
+ if (shouldStartDev) {
575
+ startDevServer(projectPath, useTailwind);
576
+ } else {
577
+ console.log('✅ Project ready!\n');
578
+ console.log('Start developing:');
579
+ if (!useCurrentDir) {
580
+ console.log(` cd ${projectName}`);
581
+ }
582
+ console.log(' npm run dev\n');
583
+ }
584
+ } else {
585
+ // Ask if user wants to install dependencies
586
+ const { shouldInstall } = await inquirer.prompt([
587
+ {
588
+ type: 'confirm',
589
+ name: 'shouldInstall',
590
+ message: 'Install dependencies and build CSS now?',
591
+ default: true
592
+ }
593
+ ]);
594
+
595
+ if (shouldInstall) {
596
+ await runInstallation(projectPath, useTailwind);
597
+
598
+ // Ask if user wants to start dev server
599
+ const { shouldStartDev } = await inquirer.prompt([
600
+ {
601
+ type: 'confirm',
602
+ name: 'shouldStartDev',
603
+ message: 'Start development server?',
604
+ default: true
605
+ }
606
+ ]);
607
+
608
+ if (shouldStartDev) {
609
+ startDevServer(projectPath, useTailwind);
610
+ } else {
611
+ console.log('\n✅ Project ready!\n');
612
+ console.log('Start developing:');
613
+ if (!useCurrentDir) {
614
+ console.log(` cd ${projectName}`);
615
+ }
616
+ console.log(' npm run dev\n');
617
+ }
618
+ } else {
619
+ console.log('\n✅ Project created successfully!\n');
620
+ console.log('Next steps:');
621
+ if (!useCurrentDir) {
622
+ console.log(` cd ${projectName}`);
623
+ }
624
+ console.log(' npm install');
625
+ if (useTailwind) {
626
+ console.log(' npm run build:css');
627
+ }
628
+ console.log(' npm run dev\n');
629
+ }
630
+ }
631
+ });
632
+ }
633
+
634
+ module.exports = { registerCommand };